Merge commit '7910f19ab8d52e9969806c1eb99e922480131c50' into dev-release
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
new file mode 100644
index 0000000..5e35cf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -0,0 +1,172 @@
+// 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 com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Lazy Java class file resource provider loading class files from a JDK.
+ *
+ * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
+ * resources in the descriptor set will then force the read of JDK content.
+ *
+ * <p>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ */
+@Keep
+public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
+  private Origin origin;
+  private final Set<String> descriptors = new HashSet<>();
+  private final Map<String, String> descriptorToModule = new HashMap<>();
+  private URLClassLoader jrtFsJarLoader;
+  private FileSystem jrtFs;
+
+  /**
+   * Creates a lazy class-file program-resource provider for the system modules of the running Java
+   * VM.
+   *
+   * <p>Only supported if the current VM is of version 9 or higher.
+   */
+  public static ClassFileResourceProvider fromSystemJdk() throws IOException {
+    return new JdkClassFileProvider();
+  }
+
+  /**
+   * Creates a lazy class-file program-resource provider for the system modules of a JDK.
+   *
+   * @param home Location of the JDK to read the program-resources from.
+   *     <p>Only supported if the JDK to read is of version 9 or higher.
+   */
+  public static ClassFileResourceProvider fromSystemModulesJdk(Path home) throws IOException {
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    if (!Files.exists(jrtFsJar)) {
+      throw new NoSuchFileException(jrtFsJar.toString());
+    }
+    return new JdkClassFileProvider(home);
+  }
+
+  /**
+   * Creates a lazy class-file program-resource provider for the runtime of a JDK.
+   *
+   * <p>This will load the program-resources form the system modules for JDK of version 9 or higher.
+   *
+   * <p>This will load <code>rt.jar</code> for JDK of version 8 and lower.
+   *
+   * @param home Location of the JDK to read the program-resources from.
+   */
+  public static ClassFileResourceProvider fromJdkHome(Path home) throws IOException {
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    if (Files.exists(jrtFsJar)) {
+      return JdkClassFileProvider.fromSystemModulesJdk(home);
+    }
+    // JDK has rt.jar in jre/lib/rt.jar.
+    Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return fromJavaRuntimeJar(rtJar);
+    }
+    // JRE has rt.jar in lib/rt.jar.
+    rtJar = home.resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return fromJavaRuntimeJar(rtJar);
+    }
+    throw new IOException("Path " + home + " does not look like a Java home");
+  }
+
+  public static ClassFileResourceProvider fromJavaRuntimeJar(Path archive) throws IOException {
+    return new ArchiveClassFileProvider(archive);
+  }
+
+  private JdkClassFileProvider() throws IOException {
+    origin = Origin.unknown();
+    collectDescriptors(FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()));
+  }
+
+  /**
+   * Creates a lazy class-file program-resource provider for the system modules of a JDK.
+   *
+   * @param home Location of the JDK to read the program-resources from.
+   *     <p>Only supported if the JDK to read is of version 9 or higher.
+   */
+  private JdkClassFileProvider(Path home) throws IOException {
+    origin = new PathOrigin(home);
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    assert Files.exists(jrtFsJar);
+    jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()});
+    FileSystem jrtFs =
+        FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap(), jrtFsJarLoader);
+    collectDescriptors(jrtFs);
+  }
+
+  private void collectDescriptors(FileSystem jrtFs) throws IOException {
+    this.jrtFs = jrtFs;
+    Files.walk(jrtFs.getPath("/modules"))
+        .forEach(
+            path -> {
+              if (FileUtils.isClassFile(path)) {
+                DescriptorUtils.ModuleAndDescriptor moduleAndDescriptor =
+                    DescriptorUtils.guessJrtModuleAndTypeDescriptor(path.toString());
+                descriptorToModule.put(
+                    moduleAndDescriptor.getDescriptor(), moduleAndDescriptor.getModule());
+                descriptors.add(moduleAndDescriptor.getDescriptor());
+              }
+            });
+  }
+
+  @Override
+  public Set<String> getClassDescriptors() {
+    return Collections.unmodifiableSet(descriptors);
+  }
+
+  @Override
+  public ProgramResource getProgramResource(String descriptor) {
+    if (!descriptors.contains(descriptor)) {
+      return null;
+    }
+    try {
+      return ProgramResource.fromBytes(
+          Origin.unknown(),
+          ProgramResource.Kind.CF,
+          Files.readAllBytes(
+              jrtFs.getPath(
+                  "modules",
+                  descriptorToModule.get(descriptor),
+                  DescriptorUtils.getPathFromDescriptor(descriptor))),
+          Collections.singleton(descriptor));
+    } catch (IOException e) {
+      throw new CompilationError("Failed to read '" + descriptor, origin);
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    close();
+    super.finalize();
+  }
+
+  @Override
+  public void close() throws IOException {
+    jrtFs.close();
+    if (jrtFsJarLoader != null) {
+      jrtFsJarLoader.close();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4403375..dcb4f9a 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -90,11 +89,10 @@
             desugar(app, options, executorService);
           });
       if (shrink) {
-        options.reporter.warning(
-            new StringDiagnostic("Shrinking of desugared library is work in progress."));
         InternalOptions r8Options = r8Command.getInternalOptions();
-        r8Options.testing.keepInheritedInterfaceMethods =
-            options.testing.keepInheritedInterfaceMethods;
+        // TODO(b/143590191): Remove the hack and make it work by default.
+        // Temporary hack so that keeping an interface keeps the superinterfaces.
+        r8Options.testing.keepInheritedInterfaceMethods = true;
         // Disable outlining for R8 when called from L8.
         r8Options.outline.enabled = false;
         R8.runForTesting(r8Command.getInputApp(), r8Options);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 8fb3ac2..d96a5a1 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,10 +163,8 @@
     }
 
     public boolean isShrinking() {
-      // TODO(b/143431384): Re-enable shrinking.
-      return false;
       // Answers true if keep rules, even empty, are provided.
-      // return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
+      return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index ec6710b..93fc650 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -229,8 +229,7 @@
       // Find a free register that is not used by an inactive interval that overlaps with
       // unhandledInterval.
       if (tryHint(unhandledInterval)) {
-        assignRegisterToUnhandledInterval(
-            unhandledInterval, unhandledInterval.getHint().getRegister());
+        assignRegisterToUnhandledInterval(unhandledInterval, unhandledInterval.getHint());
       } else {
         boolean wide = unhandledInterval.getType().isWide();
         int register;
@@ -304,9 +303,9 @@
   private void updateHints(LiveIntervals intervals) {
     for (Phi phi : intervals.getValue().uniquePhiUsers()) {
       if (!phi.isValueOnStack()) {
-        phi.getLiveIntervals().setHint(intervals);
+        phi.getLiveIntervals().setHint(intervals, unhandled);
         for (Value value : phi.getOperands()) {
-          value.getLiveIntervals().setHint(intervals);
+          value.getLiveIntervals().setHint(intervals, unhandled);
         }
       }
     }
@@ -317,7 +316,7 @@
       return false;
     }
     boolean isWide = unhandled.getType().isWide();
-    int hintRegister = unhandled.getHint().getRegister();
+    int hintRegister = unhandled.getHint();
     if (freeRegisters.contains(hintRegister)
         && (!isWide || freeRegisters.contains(hintRegister + 1))) {
       for (LiveIntervals inactive : inactive) {
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 6c8d4cf..3066b4d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -6,13 +6,11 @@
 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.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;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.util.ArrayList;
@@ -211,8 +209,7 @@
   public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
     assert checkIfObsolete();
     // Make sure we are not chasing NotFoundError.
-    ResolutionResult resolutionResult = resolveMethod(method.holder, method);
-    if (resolutionResult.asListOfTargets().isEmpty()) {
+    if (resolveMethod(method.holder, method).getSingleTarget() == null) {
       return null;
     }
     // According to
@@ -226,7 +223,7 @@
     if (contextClass == null || contextClass.superType == null) {
       return null;
     }
-    resolutionResult = resolveMethod(contextClass.superType, method);
+    ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
     DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target == null || !target.isStatic() ? target : null;
   }
@@ -699,8 +696,7 @@
       if (nonAbstractMethods.size() == 1) {
         return new SingleResolutionResult(nonAbstractMethods.get(0));
       }
-      // TODO(b/144085169): In the case of multiple non-abstract methods resolution should fail.
-      return new MultiResolutionResult(ImmutableList.copyOf(nonAbstractMethods));
+      return new IncompatibleClassResult(Collections.emptyList(), nonAbstractMethods);
     }
   }
 
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 078410a..90cc0f4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -77,7 +77,7 @@
     this.options = options;
     this.rewritePrefix = mapper;
 
-    if (enableWholeProgramOptimizations() && options.enableCallSiteOptimizationInfoPropagation) {
+    if (enableWholeProgramOptimizations() && options.enablePropagationOfDynamicTypesAtCallSites) {
       this.callSiteOptimizationInfoPropagator =
           new CallSiteOptimizationInfoPropagator(withLiveness());
     } else {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 988bf75..0acef70 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -560,6 +560,11 @@
     return lookupTarget(directMethods, method);
   }
 
+  /** Find direct method in this class matching {@param predicate}. */
+  public DexEncodedMethod lookupDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    return PredicateUtils.findFirst(directMethods, predicate);
+  }
+
   /** Find virtual method in this class matching {@param method}. */
   public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
     return lookupTarget(virtualMethods, method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index b1a96c1..1764634 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -3,10 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.ir.code.ConstInstruction;
+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.SingleValue;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
@@ -152,7 +158,7 @@
    *
    * <p>NOTE: It is the responsibility of the caller to check if this field is pinned or not.
    */
-  public ConstInstruction valueAsConstInstruction(
+  public Instruction valueAsConstInstruction(
       IRCode code, DebugLocalInfo local, AppView<AppInfoWithLiveness> appView) {
     boolean isWritten = appView.appInfo().isFieldWrittenByFieldPutInstruction(this);
     if (!isWritten) {
@@ -161,8 +167,19 @@
       return value.asConstInstruction(appView, code, local);
     }
 
-    // The only way to figure out whether the DexValue contains the final value is ensure the value
-    // is not the default or check that <clinit> is not present.
+    // Check if we have a single value for the field according to the field optimization info.
+    AbstractValue abstractValue = getOptimizationInfo().getAbstractValue();
+    if (abstractValue.isSingleValue()) {
+      SingleValue singleValue = abstractValue.asSingleValue();
+      if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+        TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+        return singleValue.createMaterializingInstruction(
+            appView, code, TypeAndLocalInfoSupplier.create(type, local));
+      }
+    }
+
+    // The only way to figure out whether the static value contains the final value is ensure the
+    // value is not the default or check that <clinit> is not present.
     if (accessFlags.isFinal() && isStatic()) {
       DexClass clazz = appView.definitionFor(field.holder);
       if (clazz == null || clazz.hasClassInitializer()) {
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 23b8d02..acc5942 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -17,6 +18,10 @@
     return false;
   }
 
+  public FailedResolutionResult asFailedResolution() {
+    return null;
+  }
+
   // TODO(b/140214802): Remove this method as its usage is questionable.
   public abstract DexEncodedMethod asResultOfResolve();
 
@@ -304,6 +309,17 @@
     }
 
     @Override
+    public FailedResolutionResult asFailedResolution() {
+      return this;
+    }
+
+    public void forEachFailureDependency(
+        Consumer<DexProgramClass> classesCausingFailure,
+        Consumer<DexEncodedMethod> methodsCausingFailure) {
+      // Default failure has no dependencies.
+    }
+
+    @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       return false;
     }
@@ -325,8 +341,26 @@
   public static class IncompatibleClassResult extends FailedResolutionResult {
     static final IncompatibleClassResult INSTANCE = new IncompatibleClassResult();
 
+    private final Collection<DexProgramClass> classesCausingError;
+    private final Collection<DexEncodedMethod> methodsCausingError;
+
     private IncompatibleClassResult() {
-      // Intentionally left empty.
+      this(Collections.emptyList(), Collections.emptyList());
+    }
+
+    IncompatibleClassResult(
+        Collection<DexProgramClass> classesCausingError,
+        Collection<DexEncodedMethod> methodsCausingError) {
+      this.classesCausingError = classesCausingError;
+      this.methodsCausingError = methodsCausingError;
+    }
+
+    @Override
+    public void forEachFailureDependency(
+        Consumer<DexProgramClass> classesCausingFailure,
+        Consumer<DexEncodedMethod> methodsCausingFailure) {
+      this.classesCausingError.forEach(classesCausingFailure);
+      this.methodsCausingError.forEach(methodsCausingFailure);
     }
   }
 
@@ -336,5 +370,8 @@
     private NoSuchMethodResult() {
       // Intentionally left empty.
     }
+
+    // TODO(b/144085169): Consider if the resolution resulting in a NoSuchMethodError should
+    // be preserved by ensuring its class is marked. Otherwise, the error may become ClassNotFound.
   }
 }
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 1079da1..35f6661 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
@@ -25,12 +25,12 @@
 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.FieldInstruction;
 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;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,9 +56,11 @@
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
       OptimizationFeedback feedback,
+      DexProgramClass clazz,
       DexEncodedMethod method) {
+    assert clazz.type == method.method.holder;
     this.appView = appView;
-    this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    this.clazz = clazz;
     this.code = code;
     this.feedback = feedback;
     this.method = method;
@@ -67,11 +69,26 @@
 
   public static void run(
       AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
-    if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) {
-      assert appView.appInfo().hasLiveness();
-      new FieldValueAnalysis(appView.withLiveness(), code, feedback, method)
-          .computeFieldOptimizationInfo();
+    if (!appView.enableWholeProgramOptimizations()) {
+      return;
     }
+    assert appView.appInfo().hasLiveness();
+    if (!method.isInitializer()) {
+      return;
+    }
+    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    if (method.isInstanceInitializer()) {
+      DexEncodedMethod otherInstanceInitializer =
+          clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
+      if (otherInstanceInitializer != null) {
+        // Conservatively bail out.
+        // TODO(b/125282093): Handle multiple instance initializers on the same class.
+        return;
+      }
+    }
+
+    new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
+        .computeFieldOptimizationInfo();
   }
 
   private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -86,55 +103,51 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DominatorTree dominatorTree = null;
 
-    if (method.isClassInitializer()) {
-      DexType context = method.method.holder;
+    DexType context = method.method.holder;
 
-      // Find all the static-put instructions that assign a field in the enclosing class which is
-      // guaranteed to be assigned only in the current initializer.
-      boolean isStraightLineCode = true;
-      Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>();
-      for (Instruction instruction : code.instructions()) {
-        if (instruction.isStaticPut()) {
-          StaticPut staticPut = instruction.asStaticPut();
-          DexField field = staticPut.getField();
-          DexEncodedField encodedField = appInfo.resolveField(field);
-          if (encodedField != null
-              && encodedField.field.holder == context
-              && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) {
-            staticPutsPerField
-                .computeIfAbsent(encodedField, ignore -> new LinkedList<>())
-                .add(staticPut);
-          }
-        }
-        if (instruction.isJumpInstruction()) {
-          if (!instruction.isGoto() && !instruction.isReturn()) {
-            isStraightLineCode = false;
-          }
+    // Find all the static-put instructions that assign a field in the enclosing class which is
+    // guaranteed to be assigned only in the current initializer.
+    boolean isStraightLineCode = true;
+    Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+    for (Instruction instruction : code.instructions()) {
+      if (instruction.isFieldPut()) {
+        FieldInstruction fieldPut = instruction.asFieldInstruction();
+        DexField field = fieldPut.getField();
+        DexEncodedField encodedField = appInfo.resolveField(field);
+        if (encodedField != null
+            && encodedField.field.holder == context
+            && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+          putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
         }
       }
-
-      List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-      for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) {
-        DexEncodedField encodedField = entry.getKey();
-        LinkedList<StaticPut> staticPuts = entry.getValue();
-        if (staticPuts.size() > 1) {
-          continue;
+      if (instruction.isJumpInstruction()) {
+        if (!instruction.isGoto() && !instruction.isReturn()) {
+          isStraightLineCode = false;
         }
-        StaticPut staticPut = staticPuts.getFirst();
-        if (!isStraightLineCode) {
-          if (dominatorTree == null) {
-            dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
-          }
-          if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) {
-            continue;
-          }
-        }
-        if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) {
-          continue;
-        }
-        updateFieldOptimizationInfo(encodedField, staticPut.value());
       }
     }
+
+    List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+    for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
+      DexEncodedField encodedField = entry.getKey();
+      LinkedList<FieldInstruction> fieldPuts = entry.getValue();
+      if (fieldPuts.size() > 1) {
+        continue;
+      }
+      FieldInstruction fieldPut = fieldPuts.getFirst();
+      if (!isStraightLineCode) {
+        if (dominatorTree == null) {
+          dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+        }
+        if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+          continue;
+        }
+      }
+      if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+        continue;
+      }
+      updateFieldOptimizationInfo(encodedField, fieldPut.value());
+    }
   }
 
   private boolean fieldMaybeReadBeforeInstruction(
@@ -263,7 +276,7 @@
     Value root = value.getAliasedValue();
     AbstractValue abstractValue = computeAbstractValue(root);
     if (!abstractValue.isUnknown()) {
-      feedback.recordFieldHasAbstractValue(field, abstractValue);
+      feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
     }
 
     // Dynamic upper bound type.
@@ -290,6 +303,9 @@
         return singleEnumValue;
       }
     }
+    if (!value.isPhi()) {
+      return value.definition.getAbstractValue(appView, clazz.type);
+    }
     return UnknownValue.getInstance();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index eae4fc8..f560b29 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -6,6 +6,8 @@
 
 public abstract class AbstractValue {
 
+  public abstract boolean isNonTrivial();
+
   /**
    * Returns true if this abstract value represents a single concrete value (i.e., the
    * concretization of this abstract value has size 1).
@@ -46,9 +48,19 @@
     return false;
   }
 
+  public AbstractValue join(AbstractValue other) {
+    if (this.equals(other)) {
+      return this;
+    }
+    return UnknownValue.getInstance();
+  }
+
   @Override
   public abstract boolean equals(Object o);
 
   @Override
   public abstract int hashCode();
+
+  @Override
+  public abstract String toString();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
index 8573c36..1f7a76b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
@@ -4,13 +4,20 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
-import com.android.tools.r8.errors.Unreachable;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
+
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
 
 public class SingleEnumValue extends SingleValue {
 
@@ -42,10 +49,25 @@
   }
 
   @Override
+  public String toString() {
+    return "SingleEnumValue(" + field.toSourceString() + ")";
+  }
+
+  @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
       TypeAndLocalInfoSupplier info) {
-    throw new Unreachable("unless we store single enum as a method's returned value.");
+    TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+    assert type.lessThanOrEqual(info.getTypeLattice(), appView);
+    Value outValue = code.createValue(type, info.getLocalInfo());
+    return new StaticGet(outValue, field);
+  }
+
+  @Override
+  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+    DexEncodedField encodedField = appView.appInfo().resolveField(field);
+    return isMemberVisibleFromOriginalContext(
+        appView, context, encodedField.field.holder, encodedField.accessFlags);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 949dbb5..28ac1a2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -48,6 +49,11 @@
   }
 
   @Override
+  public String toString() {
+    return "SingleNumberValue(" + value + ")";
+  }
+
+  @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
     TypeLatticeElement typeLattice = info.getTypeLattice();
@@ -58,4 +64,9 @@
             typeLattice.isReference() ? TypeLatticeElement.NULL : typeLattice, debugLocalInfo);
     return new ConstNumber(returnedValue, value);
   }
+
+  @Override
+  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index d2a8726..417fe19 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -53,6 +54,11 @@
   }
 
   @Override
+  public String toString() {
+    return "SingleStringValue(" + string + ")";
+  }
+
+  @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
@@ -73,4 +79,9 @@
     assert !instruction.instructionInstanceCanThrow();
     return instruction;
   }
+
+  @Override
+  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 9972328..44aa581 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
@@ -13,6 +14,11 @@
 public abstract class SingleValue extends AbstractValue {
 
   @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
   public boolean isSingleValue() {
     return true;
   }
@@ -22,8 +28,12 @@
     return this;
   }
 
+  /**
+   * Note that calls to this method should generally be guarded by {@link
+   * #isMaterializableInContext}.
+   */
   public abstract Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      TypeAndLocalInfoSupplier info);
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info);
+
+  public abstract boolean isMaterializableInContext(AppView<?> appView, DexType context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
index bbae7bd..216ea99 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -15,6 +15,11 @@
   }
 
   @Override
+  public boolean isNonTrivial() {
+    return false;
+  }
+
+  @Override
   public boolean isUnknown() {
     return true;
   }
@@ -28,4 +33,9 @@
   public int hashCode() {
     return System.identityHashCode(this);
   }
+
+  @Override
+  public String toString() {
+    return "UnknownValue";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index e7eb6bd..84f33f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.InternalOutputMode;
@@ -330,4 +331,9 @@
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
     return this.value == 0 || this.value == 1;
   }
+
+  @Override
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    return appView.abstractValueFactory().createSingleNumberValue(value);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index a08ea04..a80bc66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 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.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -159,4 +161,12 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
     return false;
   }
+
+  @Override
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    if (!instructionInstanceCanThrow()) {
+      return appView.abstractValueFactory().createSingleStringValue(value);
+    }
+    return UnknownValue.getInstance();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 4e1e00a..453a564 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -75,4 +75,11 @@
   public boolean isAllowedAfterThrowingInstruction() {
     return true;
   }
+
+  @Override
+  public boolean verifyTypes(AppView<?> appView) {
+    super.verifyTypes(appView);
+    assert src().getTypeLattice().lessThanOrEqual(outValue().getTypeLattice(), appView);
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 0e0c975..3cae1e4 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
@@ -18,6 +18,8 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
 import java.util.List;
@@ -201,4 +203,14 @@
 
     return false;
   }
+
+  @Override
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    assert isFieldGet();
+    DexEncodedField field = appView.appInfo().resolveField(getField());
+    if (field != null) {
+      return field.getOptimizationInfo().getAbstractValue();
+    }
+    return UnknownValue.getInstance();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 7452a66..228dc2b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.Assume.NoAssumption;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
@@ -138,6 +140,11 @@
     return oldOutValue;
   }
 
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    assert hasOutValue();
+    return UnknownValue.getInstance();
+  }
+
   @Override
   public TypeLatticeElement getTypeLattice() {
     if (hasOutValue()) {
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 c6f919d..484c4e3 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
@@ -15,6 +15,8 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -187,4 +189,14 @@
   public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
     return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory());
   }
+
+  @Override
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    assert hasOutValue();
+    DexEncodedMethod method = lookupSingleTarget(appView, context);
+    if (method != null) {
+      return method.getOptimizationInfo().getAbstractReturnValue();
+    }
+    return UnknownValue.getInstance();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 9054d07..3478645 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -126,7 +126,10 @@
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     super.verifyTypes(appView);
-    assert src().getTypeLattice().equals(outValue().getTypeLattice());
+    // DebugLocalWrite defines it's own verification of types but should be allowed to call super.
+    if (!this.isDebugLocalWrite()) {
+      assert src().getTypeLattice().equals(outValue().getTypeLattice());
+    }
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
index f5ca688..0d5cfd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
@@ -10,4 +10,19 @@
 public interface TypeAndLocalInfoSupplier {
   DebugLocalInfo getLocalInfo();
   TypeLatticeElement getTypeLattice();
+
+  static TypeAndLocalInfoSupplier create(TypeLatticeElement type, DebugLocalInfo local) {
+    return new TypeAndLocalInfoSupplier() {
+
+      @Override
+      public DebugLocalInfo getLocalInfo() {
+        return local;
+      }
+
+      @Override
+      public TypeLatticeElement getTypeLattice() {
+        return type;
+      }
+    };
+  }
 }
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 ceaf87e..99c2933 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
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.graph.AppView;
 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;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public interface FieldOptimizationFeedback {
 
@@ -21,5 +23,6 @@
 
   void markFieldBitsRead(DexEncodedField field, int bitsRead);
 
-  void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue);
+  void recordFieldHasAbstractValue(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 1ffe1ad..6e96719 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -7,13 +7,14 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
 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.ir.optimize.info.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
 
@@ -30,11 +31,8 @@
 
   void methodReturnsArgument(DexEncodedMethod method, int argument);
 
-  void methodReturnsConstantNumber(
-      DexEncodedMethod method, AppView<?> appView, long value);
-
-  void methodReturnsConstantString(
-      DexEncodedMethod method, AppView<?> appView, DexString value);
+  void methodReturnsAbstractValue(
+      DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
   void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index d52f02e..511b971 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -47,6 +47,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -1232,6 +1233,10 @@
               if (!encodedMethod.isStatic()) {
                 virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
                 virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+                if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                  // In this case interface method rewriter takes care of it.
+                  continue;
+                }
               }
               DexProto proto = encodedMethod.method.proto;
               DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
@@ -1244,6 +1249,37 @@
       }
     }
 
+    private boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
+      // Answers true if this method is already managed through emulated interface dispatch.
+      Map<DexType, DexType> emulateLibraryInterface =
+          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+      if (emulateLibraryInterface.isEmpty()) {
+        return false;
+      }
+      DexMethod methodToFind = method.method;
+
+      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+      // the method, answers true.
+      LinkedList<DexType> workList = new LinkedList<>();
+      workList.add(methodToFind.holder);
+      while (!workList.isEmpty()) {
+        DexType dexType = workList.removeFirst();
+        DexClass dexClass = appView.definitionFor(dexType);
+        assert dexClass != null; // It is a library class, or we are doing L8 compilation.
+        if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
+          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
+          if (dexEncodedMethod != null) {
+            return true;
+          }
+        }
+        Collections.addAll(workList, dexClass.interfaces.values);
+        if (dexClass.superType != appView.dexItemFactory().objectType) {
+          workList.add(dexClass.superType);
+        }
+      }
+      return false;
+    }
+
     private List<DexEncodedMethod> findDexEncodedMethodsWithName(
         DexString methodName, DexClass clazz) {
       List<DexEncodedMethod> found = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 688e751..e321f56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -6,19 +6,24 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.DefaultMethodCandidates;
+import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -47,16 +52,8 @@
     return createdMethods.keySet();
   }
 
-  final void process(DexClass clazz) {
+  final void process(DexProgramClass clazz) {
     assert !clazz.isInterface();
-    if (clazz.isNotProgramClass()) {
-      // We assume that library classes don't need to be processed, since they
-      // are provided by a runtime not supporting default interface methods.
-      // We also skip classpath classes, which results in sub-optimal behavior
-      // in case classpath superclass when processed adds a default method which
-      // could have been reused in this class otherwise.
-      return;
-    }
     if (!processedClasses.add(clazz)) {
       return; // Has already been processed.
     }
@@ -73,7 +70,14 @@
         throw new CompilationError("Interface `" + superClass.toSourceString()
             + "` used as super class of `" + clazz.toSourceString() + "`.");
       }
-      process(superClass);
+      // We assume that library classes don't need to be processed, since they
+      // are provided by a runtime not supporting default interface methods.  We
+      // also skip classpath classes, which results in sub-optimal behavior in
+      // case classpath superclass when processed adds a default method which
+      // could have been reused in this class otherwise.
+      if (superClass.isProgramClass()) {
+        process(superClass.asProgramClass());
+      }
     }
 
     // When inheriting from a library class, the library class may implement interfaces to
@@ -93,7 +97,7 @@
     }
 
     // Collect the default interface methods to be added to this class.
-    List<DexEncodedMethod> methodsToImplement =
+    DefaultMethodCandidates methodsToImplement =
         collectMethodsToImplement(clazz, desugaredLibraryLookup);
     if (methodsToImplement.isEmpty()) {
       return;
@@ -101,15 +105,35 @@
 
     // Add the methods.
     List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
-    for (DexEncodedMethod method : methodsToImplement) {
+    for (DexEncodedMethod method : methodsToImplement.candidates) {
       assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
       DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
       newForwardingMethods.add(newMethod);
       createdMethods.put(newMethod, method);
     }
+    for (DexEncodedMethod conflict : methodsToImplement.conflicts.keySet()) {
+      assert conflict.accessFlags.isPublic() && !conflict.accessFlags.isAbstract();
+      DexEncodedMethod newMethod = addICCEThrowingMethod(conflict, clazz);
+      newForwardingMethods.add(newMethod);
+      createdMethods.put(newMethod, conflict);
+    }
     clazz.appendVirtualMethods(newForwardingMethods);
   }
 
+  private DexEncodedMethod addICCEThrowingMethod(DexEncodedMethod method, DexClass clazz) {
+    DexMethod newMethod =
+        dexItemFactory.createMethod(clazz.type, method.method.proto, method.method.name);
+    return new DexEncodedMethod(
+        newMethod,
+        method.accessFlags.copy(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        new SynthesizedCode(
+            callerPosition ->
+                new ExceptionThrowingSourceCode(
+                    clazz.type, method.method, callerPosition, dexItemFactory.icceType)));
+  }
+
   private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
     DexMethod method = defaultMethod.method;
     DexClass target = appView.definitionFor(method.holder);
@@ -157,7 +181,7 @@
   // For a given class `clazz` inspects all interfaces it implements directly or
   // indirectly and collect a set of all default methods to be implemented
   // in this class.
-  private List<DexEncodedMethod> collectMethodsToImplement(
+  private DefaultMethodCandidates collectMethodsToImplement(
       DexClass clazz, boolean desugaredLibraryLookup) {
     DefaultMethodsHelper helper = new DefaultMethodsHelper();
     DexClass current = clazz;
@@ -196,7 +220,7 @@
           && !desugaredLibraryLookup) {
         // No interface with default in direct hierarchy, nothing to do: super already has all that
         // is needed.
-        return Collections.emptyList();
+        return DefaultMethodCandidates.empty();
       }
 
       if (current.superType == null) {
@@ -225,12 +249,13 @@
       }
     }
 
-    List<DexEncodedMethod> candidates = helper.createCandidatesList();
-    if (candidates.isEmpty()) {
-      return candidates;
+    DefaultMethodCandidates candidateSet = helper.createCandidatesList();
+    if (candidateSet.isEmpty()) {
+      return candidateSet;
     }
 
     // Remove from candidates methods defined in class or any of its superclasses.
+    List<DexEncodedMethod> candidates = candidateSet.candidates;
     List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
     current = clazz;
     Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
@@ -262,7 +287,7 @@
       // Hide candidates by virtual method of the class.
       hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
       if (candidates.isEmpty()) {
-        return toBeImplemented;
+        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
       }
 
       DexType superType = current.superType;
@@ -279,14 +304,16 @@
         // Everything still in candidate list is not hidden.
         toBeImplemented.addAll(candidates);
 
-        return toBeImplemented;
+        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
       }
       current = superClass;
     }
   }
 
-  private void hideCandidates(List<DexEncodedMethod> virtualMethods,
-      List<DexEncodedMethod> candidates, List<DexEncodedMethod> toBeImplemented) {
+  private void hideCandidates(
+      List<DexEncodedMethod> virtualMethods,
+      Collection<DexEncodedMethod> candidates,
+      List<DexEncodedMethod> toBeImplemented) {
     Iterator<DexEncodedMethod> it = candidates.iterator();
     while (it.hasNext()) {
       DexEncodedMethod candidate = it.next();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index fb051aa..2484113 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -6,17 +6,66 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 
 // Helper class implementing bunch of default interface method handling operations.
 final class DefaultMethodsHelper {
+
+  // Collection of default methods that need to have generated forwarding methods.
+  public static class DefaultMethodCandidates {
+    final List<DexEncodedMethod> candidates;
+    final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
+
+    private static final DefaultMethodCandidates EMPTY =
+        new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
+
+    public static DefaultMethodCandidates empty() {
+      return EMPTY;
+    }
+
+    public DefaultMethodCandidates(
+        List<DexEncodedMethod> candidates,
+        Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
+      this.candidates = candidates;
+      this.conflicts = conflicts;
+    }
+
+    public int size() {
+      return candidates.size() + conflicts.size();
+    }
+
+    public boolean isEmpty() {
+      return candidates.isEmpty() && conflicts.isEmpty();
+    }
+  }
+
+  // Equivalence wrapper for comparing two method signatures modulo holder type.
+  private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
+
+    @Override
+    protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
+      return method1.method.match(method2.method);
+    }
+
+    @Override
+    protected int doHash(DexEncodedMethod method) {
+      return Objects.hash(method.method.name, method.method.proto);
+    }
+  }
+
   // Current set of default interface methods, may overlap with `hidden`.
   private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
   // Current set of known hidden default interface methods.
@@ -78,45 +127,49 @@
     candidates.add(encoded);
   }
 
-  // Creates a list of default method candidates to be implemented in the class.
-  final List<DexEncodedMethod> createCandidatesList() {
-    this.candidates.removeAll(hidden);
-    if (this.candidates.isEmpty()) {
-      return Collections.emptyList();
+  final DefaultMethodCandidates createCandidatesList() {
+    // The common cases is for no default methods or a single one.
+    if (candidates.isEmpty()) {
+      return DefaultMethodCandidates.empty();
     }
-
-    // The list of non-hidden default methods. The list is not expected to be big,
-    // since it only consists of default methods which are maximally specific
-    // interface method of a particular class.
-    List<DexEncodedMethod> candidates = new LinkedList<>();
-
-    // Note that it is possible for a class to have more than one maximally specific
-    // interface method. But runtime requires that when a method is called, there must be
-    // found *only one* maximally specific interface method and this method should be
-    // non-abstract, otherwise a runtime error is generated.
-    //
-    // This code assumes that if such erroneous case exist for particular name/signature,
-    // a method with this name/signature must be defined in class or one of its superclasses,
-    // or otherwise it should never be called. This means that if we see two default method
-    // candidates with same name/signature, it is safe to assume that we don't need to add
-    // these method to the class, because if it was missing in class and its superclasses
-    // but still called in the original code, this call would have resulted in runtime error.
-    // So we are just leaving it unimplemented with the same effect (with a different runtime
-    // exception though).
-    for (DexEncodedMethod candidate : this.candidates) {
-      Iterator<DexEncodedMethod> it = candidates.iterator();
-      boolean conflict = false;
-      while (it.hasNext()) {
-        if (candidate.method.match(it.next())) {
-          conflict = true;
-          it.remove();
-        }
+    if (candidates.size() == 1 && hidden.isEmpty()) {
+      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+    }
+    // In case there are more we need to check for potential duplicates and treat them specially
+    // to preserve the IncompatibleClassChangeError that would arise at runtime.
+    int maxSize = candidates.size();
+    SignatureEquivalence equivalence = new SignatureEquivalence();
+    Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
+    boolean foundConflicts = false;
+    for (DexEncodedMethod candidate : candidates) {
+      if (hidden.contains(candidate)) {
+        continue;
       }
-      if (!conflict) {
-        candidates.add(candidate);
+      Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
+      List<DexEncodedMethod> conflicts = groups.get(key);
+      if (conflicts != null) {
+        foundConflicts = true;
+      } else {
+        conflicts = new ArrayList<>(maxSize);
+        groups.put(key, conflicts);
+      }
+      conflicts.add(candidate);
+    }
+    // In the fast path we don't expect any conflicts or hidden candidates.
+    if (!foundConflicts && hidden.isEmpty()) {
+      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+    }
+    // Slow case in the case of conflicts or hidden candidates build the result.
+    List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
+    Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
+    for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
+      if (entry.getValue().size() == 1) {
+        actualCandidates.add(entry.getKey().get());
+      } else {
+        conflicts.put(entry.getKey().get(), entry.getValue());
       }
     }
-    return candidates;
+    return new DefaultMethodCandidates(actualCandidates, conflicts);
   }
 
   final List<DexEncodedMethod> createFullList() {
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 54ac5c0..eb855d1 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
@@ -49,6 +49,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -254,27 +255,6 @@
             // exception but we can not report it as error since it can also be the intended
             // behavior.
             warnMissingType(encodedMethod.method, invokedMethod.holder);
-          } else if (clazz.isInterface() && clazz.isLibraryClass() && isInDesugaredLibrary(clazz)) {
-            // Here we try to avoid doing the expensive look-up on all invokes.
-            boolean rewritten = false;
-            if (emulatedMethods.contains(invokedMethod.name)) {
-              DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
-              if (dexType != null) {
-                rewriteCurrentInstructionToEmulatedInterfaceCall(
-                    dexType, invokedMethod, invokeSuper, instructions);
-                rewritten = true;
-              }
-            }
-            if (!rewritten) {
-              DexMethod amendedMethod =
-                  amendDefaultMethod(
-                      appInfo.definitionFor(encodedMethod.method.holder), invokedMethod);
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(
-                      defaultAsMethodOfCompanionClass(amendedMethod),
-                      invokeSuper.outValue(),
-                      invokeSuper.arguments()));
-            }
           } else if (clazz.isInterface() && !clazz.isLibraryClass()) {
             // NOTE: we intentionally don't desugar super calls into interface methods
             // coming from android.jar since it is only possible in case v24+ version
@@ -291,6 +271,54 @@
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
+          } else {
+            DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+            if (dexType != null) {
+              // That invoke super may not resolve since the super method may not be present
+              // since it's in the emulated interface. We need to force resolution. If it resolves
+              // to a library method, then it needs to be rewritten.
+              // If it resolves to a program overrides, the invoke-super can remain.
+              DexEncodedMethod dexEncodedMethod =
+                  appView
+                      .appInfo()
+                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
+              if (dexEncodedMethod != null) {
+                DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
+                if (dexClass != null && dexClass.isLibraryClass()) {
+                  // Rewriting is required because the super invoke resolves into a missing
+                  // method (method is on desugared library). Find out if it needs to be
+                  // retarget or if it just calls a companion class method and rewrite.
+                  Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+                      options.desugaredLibraryConfiguration.getRetargetCoreLibMember();
+                  Map<DexType, DexType> typeMap =
+                      retargetCoreLibMember.get(dexEncodedMethod.method.name);
+                  if (typeMap == null || !typeMap.containsKey(dexEncodedMethod.method.holder)) {
+                    DexMethod originalCompanionMethod =
+                        instanceAsMethodOfCompanionClass(
+                            dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
+                    DexMethod companionMethod =
+                        factory.createMethod(
+                            getCompanionClassType(dexType),
+                            factory.protoWithDifferentFirstParameter(
+                                originalCompanionMethod.proto, dexType),
+                            originalCompanionMethod.name);
+                    instructions.replaceCurrentInstruction(
+                        new InvokeStatic(
+                            companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+                  } else {
+                    DexMethod retargetMethod =
+                        factory.createMethod(
+                            typeMap.get(dexEncodedMethod.method.holder),
+                            factory.prependTypeToProto(
+                                dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+                            dexEncodedMethod.method.name);
+                    instructions.replaceCurrentInstruction(
+                        new InvokeStatic(
+                            retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+                  }
+                }
+              }
+            }
           }
           continue;
         }
@@ -345,39 +373,42 @@
         if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
           InvokeMethod invokeMethod = instruction.asInvokeMethod();
           DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          // Here we try to avoid doing the expensive look-up on all invokes.
-          if (!emulatedMethods.contains(invokedMethod.name)) {
-            continue;
+          DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+          if (dexType != null) {
+            rewriteCurrentInstructionToEmulatedInterfaceCall(
+                dexType, invokedMethod, invokeMethod, instructions);
           }
-          DexClass dexClass = appView.definitionFor(invokedMethod.holder);
-          // We cannot rewrite the invoke we do not know what the class is.
-          if (dexClass == null) {
-            continue;
-          }
-          // TODO(b/120884788): Make sure program class are looked up before library class for
-          // CoreLib compilation or look again into all desugared library emulation.
-          // Outside of core libraries, only library classes are rewritten. In core libraries,
-          // some classes are present both as program and library class, and definitionFor
-          // answers the program class so this is not true.
-          if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
-            continue;
-          }
-          // We always rewrite interfaces, but classes are rewritten only if they are not already
-          // desugared (CoreLibrary classes efficient implementation).
-          if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
-            continue;
-          }
-          DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
-          if (dexType == null) {
-            continue;
-          }
-          rewriteCurrentInstructionToEmulatedInterfaceCall(
-              dexType, invokedMethod, invokeMethod, instructions);
         }
       }
     }
   }
 
+  private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+    // Here we try to avoid doing the expensive look-up on all invokes.
+    if (!emulatedMethods.contains(invokedMethod.name)) {
+      return null;
+    }
+    DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+    // We cannot rewrite the invoke we do not know what the class is.
+    if (dexClass == null) {
+      return null;
+    }
+    // TODO(b/120884788): Make sure program class are looked up before library class for
+    // CoreLib compilation or look again into all desugared library emulation.
+    // Outside of core libraries, only library classes are rewritten. In core libraries,
+    // some classes are present both as program and library class, and definitionFor
+    // answers the program class so this is not true.
+    if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
+      return null;
+    }
+    // We always rewrite interfaces, but classes are rewritten only if they are not already
+    // desugared (CoreLibrary classes efficient implementation).
+    if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+      return null;
+    }
+    return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+  }
+
   private void rewriteCurrentInstructionToEmulatedInterfaceCall(
       DexType emulatedItf,
       DexMethod invokedMethod,
@@ -933,14 +964,29 @@
 
   private void duplicateEmulatedInterfaces() {
     // All classes implementing an emulated interface now implements the interface and the
-    // emulated one.
+    // emulated one, as well as hidden overrides, for correct emulated dispatch.
     for (DexClass clazz : appView.appInfo().classes()) {
+      if (clazz.type == appView.dexItemFactory().objectType) {
+        continue;
+      }
       List<DexType> extraInterfaces = new ArrayList<>();
       for (DexType type : clazz.interfaces.values) {
         if (emulatedInterfaces.containsKey(type)) {
           extraInterfaces.add(emulatedInterfaces.get(type));
         }
       }
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        assert clazz.superType != null;
+        DexClass superClazz = appView.definitionFor(clazz.superType);
+        if (superClazz != null && superClazz.isLibraryClass()) {
+          List<DexType> itfs = emulatedInterfacesOf(superClazz);
+          for (DexType itf : itfs) {
+            extraInterfaces.add(emulatedInterfaces.get(itf));
+          }
+        }
+        // Remove duplicates.
+        extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
+      }
       if (!extraInterfaces.isEmpty()) {
         DexType[] newInterfaces =
             Arrays.copyOf(
@@ -953,6 +999,32 @@
     }
   }
 
+  private List<DexType> emulatedInterfacesOf(DexClass superClazz) {
+    if (superClazz.type == factory.objectType) {
+      return Collections.emptyList();
+    }
+    ArrayList<DexType> itfs = new ArrayList<>();
+    LinkedList<DexType> workList = new LinkedList<>();
+    workList.add(superClazz.type);
+    while (!workList.isEmpty()) {
+      DexType dexType = workList.removeFirst();
+      DexClass dexClass = appView.definitionFor(dexType);
+      if (dexClass != null) {
+        if (dexClass.superType != factory.objectType) {
+          workList.add(dexClass.superType);
+        }
+        for (DexType itf : dexClass.interfaces.values) {
+          if (emulatedInterfaces.containsKey(itf)) {
+            itfs.add(itf);
+          } else {
+            workList.add(itf);
+          }
+        }
+      }
+    }
+    return itfs;
+  }
+
   /**
    * Move static and default interface methods to companion classes, add missing methods to forward
    * to moved default methods implementation.
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 ab931cc..4da2dcd 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
@@ -10,10 +10,11 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -171,7 +172,7 @@
         .hasUsefulOptimizationInfo(appView, code.method);
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     List<Assume<?>> assumeInstructions = new LinkedList<>();
-    List<ConstInstruction> constants = new LinkedList<>();
+    List<Instruction> constants = new LinkedList<>();
     int argumentsSeen = 0;
     InstructionListIterator iterator = code.entryBlock().listIterator(code);
     while (iterator.hasNext()) {
@@ -184,8 +185,23 @@
       if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
         continue;
       }
+      int argIndex = argumentsSeen - 1;
+      AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
+      if (abstractValue.isSingleValue()) {
+        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        SingleValue singleValue = abstractValue.asSingleValue();
+        if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+          Instruction replacement =
+              singleValue.createMaterializingInstruction(appView, code, instr);
+          replacement.setPosition(instr.getPosition());
+          affectedValues.addAll(originalArg.affectedValues());
+          originalArg.replaceUsers(replacement.outValue());
+          constants.add(replacement);
+          continue;
+        }
+      }
       TypeLatticeElement dynamicUpperBoundType =
-          callSiteOptimizationInfo.getDynamicUpperBoundType(argumentsSeen - 1);
+          callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
       if (dynamicUpperBoundType == null) {
         continue;
       }
@@ -197,7 +213,6 @@
         constants.add(nullInstruction);
         continue;
       }
-      // TODO(b/69963623): Handle other kinds of constants, e.g. number, string, or class.
       Value specializedArg;
       if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
         specializedArg = code.createValue(originalArg.getTypeLattice());
@@ -261,6 +276,9 @@
         assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
             .hasUsefulOptimizationInfo(appView, method);
         targetsToRevisit.add(method);
+        if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
+          appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
+        }
       }
     }
     if (revisitedMethods != 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 ccf312a..be007bb 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
@@ -99,7 +99,6 @@
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -1370,6 +1369,10 @@
       return;
     }
 
+    if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) {
+      return;
+    }
+
     IRMetadata metadata = code.metadata();
     if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) {
       return;
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 a2c60a3..5df7550 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
@@ -15,8 +15,8 @@
 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.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.InstanceGet;
@@ -148,7 +148,7 @@
         return null;
       }
 
-      ConstInstruction replacement =
+      Instruction replacement =
           staticField.valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
       if (replacement == null) {
         reporter.warning(
@@ -275,21 +275,22 @@
 
     AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
     if (abstractReturnValue.isSingleValue()) {
-      Instruction replacement =
-          abstractReturnValue.asSingleValue()
-              .createMaterializingInstruction(appView, code, current);
-
-      affectedValues.addAll(current.outValue().affectedValues());
-      current.outValue().replaceUsers(replacement.outValue());
-      current.setOutValue(null);
-      replacement.setPosition(current.getPosition());
-      current.moveDebugValues(replacement);
-      if (current.getBlock().hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator(code).add(replacement);
-      } else {
-        iterator.add(replacement);
+      SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
+      if (singleReturnValue.isMaterializableInContext(appView, callingContext)) {
+        Instruction replacement =
+            singleReturnValue.createMaterializingInstruction(appView, code, current);
+        affectedValues.addAll(current.outValue().affectedValues());
+        current.outValue().replaceUsers(replacement.outValue());
+        current.setOutValue(null);
+        replacement.setPosition(current.getPosition());
+        current.moveDebugValues(replacement);
+        if (current.getBlock().hasCatchHandlers()) {
+          iterator.split(code, blocks).listIterator(code).add(replacement);
+        } else {
+          iterator.add(replacement);
+        }
+        target.getMutableOptimizationInfo().markAsPropagated();
       }
-      target.getMutableOptimizationInfo().markAsPropagated();
     }
   }
 
@@ -325,12 +326,13 @@
       return;
     }
 
-    // Check if a this value is known const.
+    // Check if the field is pinned. In that case, it could be written by reflection.
     if (appView.appInfo().isPinned(target.field)) {
       return;
     }
 
-    ConstInstruction replacement =
+    // Check if a this value is known const.
+    Instruction replacement =
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
       affectedValues.addAll(current.outValue().affectedValues());
@@ -372,7 +374,7 @@
     }
 
     // Check if a this value is known const.
-    ConstInstruction replacement =
+    Instruction replacement =
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
       affectedValues.add(replacement.outValue());
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 dfa2f0a..9bad45e 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
@@ -651,38 +651,51 @@
       ClassInlinerEligibility eligibility,
       InvokeMethodWithReceiver invoke,
       Set<Instruction> indirectUsers) {
-    if (!eligibility.returnsReceiver
-        || invoke.outValue() == null
-        || !invoke.outValue().hasAnyUsers()) {
+    if (!eligibility.returnsReceiver) {
       return true;
     }
+
+    Value outValue = invoke.outValue();
+    if (outValue == null || !outValue.hasAnyUsers()) {
+      return true;
+    }
+
     // For CF we no longer perform the code-rewrite in CodeRewriter.rewriteMoveResult that removes
     // out values if they alias to the receiver since that naively produces a lot of popping values
     // from the stack.
-    if (invoke.outValue().numberOfPhiUsers() > 0) {
+    if (outValue.hasPhiUsers() || outValue.hasDebugUsers()) {
       return false;
     }
-    for (Instruction instruction : invoke.outValue().uniqueUsers()) {
-      if (!instruction.isInvokeMethodWithReceiver()) {
-        return false;
-      }
-      InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
-      if (user.getReceiver() != invoke.outValue()) {
-        return false;
-      }
-      int uses = 0;
-      for (Value value : user.inValues()) {
-        if (value == invoke.outValue()) {
-          uses++;
-          if (uses > 1) {
+
+    Set<Instruction> currentUsers = outValue.uniqueUsers();
+    while (!currentUsers.isEmpty()) {
+      Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
+      for (Instruction instruction : currentUsers) {
+        if (instruction.isAssume()) {
+          Value outValueAlias = instruction.outValue();
+          if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
+            return false;
+          }
+          indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
+          continue;
+        }
+        if (!instruction.isInvokeMethodWithReceiver()) {
+          return false;
+        }
+        InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
+        if (user.getReceiver().getAliasedValue() != outValue) {
+          return false;
+        }
+        for (int i = 1; i < user.inValues().size(); i++) {
+          if (user.inValues().get(i).getAliasedValue() == outValue) {
             return false;
           }
         }
       }
+      indirectUsers.addAll(currentUsers);
+      currentUsers = indirectOutValueUsers;
     }
 
-    indirectUsers.addAll(invoke.outValue().uniqueUsers());
-
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index c99f24c..18c709c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.optimize.CallSiteOptimizationInfoPropagator;
 
 // A flat lattice structure:
@@ -63,8 +65,12 @@
 
   // TODO(b/139246447): dynamic lower bound type?
 
-  // TODO(b/69963623): collect constants and if they're all same, propagate it to the callee.
-  //   then, we need to re-run unused argument removal?
+  // TODO(b/69963623): we need to re-run unused argument removal?
+
+  // The index exactly matches with in values of invocation, i.e., even including receiver.
+  public AbstractValue getAbstractArgumentValue(int argIndex) {
+    return UnknownValue.getInstance();
+  }
 
   // TODO(b/139249918): propagate classes that are guaranteed to be initialized.
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index be0bf19..c38c0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -11,9 +11,13 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.code.Value;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.List;
+import java.util.Objects;
 
 // Accumulated optimization info from call sites.
 public class ConcreteCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
@@ -21,26 +25,39 @@
   // inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
   // That is, this information takes into account the receiver as well.
   private final int size;
-  private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
-  // TODO(b/69963623): sparse map from index to ConstantData if any.
+  private final Int2ReferenceMap<TypeLatticeElement> dynamicUpperBoundTypes;
+  private final Int2ReferenceMap<AbstractValue> constants;
 
-  private ConcreteCallSiteOptimizationInfo(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1) > 0;
-    this.size = encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1);
-    this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+  private ConcreteCallSiteOptimizationInfo(
+      DexEncodedMethod encodedMethod, boolean allowConstantPropagation) {
+    this(encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1),
+        allowConstantPropagation);
   }
 
-  private ConcreteCallSiteOptimizationInfo(int size) {
+  private ConcreteCallSiteOptimizationInfo(int size, boolean allowConstantPropagation) {
+    assert size > 0;
     this.size = size;
     this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+    this.constants = allowConstantPropagation ? new Int2ReferenceArrayMap<>(size) : null;
   }
 
   CallSiteOptimizationInfo join(
       ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
     assert this.size == other.size;
-    ConcreteCallSiteOptimizationInfo result = new ConcreteCallSiteOptimizationInfo(this.size);
+    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+    ConcreteCallSiteOptimizationInfo result =
+        new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
     assert result.dynamicUpperBoundTypes != null;
     for (int i = 0; i < result.size; i++) {
+      if (allowConstantPropagation) {
+        assert result.constants != null;
+        AbstractValue abstractValue =
+            getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i));
+        if (abstractValue.isNonTrivial()) {
+          result.constants.put(i, abstractValue);
+        }
+      }
+
       TypeLatticeElement thisUpperBoundType = getDynamicUpperBoundType(i);
       if (thisUpperBoundType == null) {
         // This means the corresponding argument is primitive. The counterpart should be too.
@@ -82,6 +99,12 @@
   public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
     TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
     for (int i = 0; i < size; i++) {
+      AbstractValue abstractValue = getAbstractArgumentValue(i);
+      if (abstractValue.isNonTrivial()) {
+        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        return true;
+      }
+
       if (!staticTypes[i].isReference()) {
         continue;
       }
@@ -89,6 +112,7 @@
       if (dynamicUpperBoundType == null) {
         continue;
       }
+      assert appView.options().enablePropagationOfDynamicTypesAtCallSites;
       // To avoid the full join of type lattices below, separately check if the nullability of
       // arguments is improved, and if so, we can eagerly conclude that we've collected useful
       // call site information for this method.
@@ -109,18 +133,43 @@
   @Override
   public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
     assert 0 <= argIndex && argIndex < size;
+    assert dynamicUpperBoundTypes != null;
     return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
   }
 
+  @Override
+  public AbstractValue getAbstractArgumentValue(int argIndex) {
+    assert 0 <= argIndex && argIndex < size;
+    // TODO(b/69963623): Remove this once enabled.
+    if (constants == null) {
+      return UnknownValue.getInstance();
+    }
+    return constants.getOrDefault(argIndex, UnknownValue.getInstance());
+  }
+
   public static CallSiteOptimizationInfo fromArguments(
       AppView<? extends AppInfoWithSubtyping> appView,
       DexEncodedMethod method,
       List<Value> inValues) {
-    ConcreteCallSiteOptimizationInfo newCallSiteInfo = new ConcreteCallSiteOptimizationInfo(method);
+    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+    ConcreteCallSiteOptimizationInfo newCallSiteInfo =
+        new ConcreteCallSiteOptimizationInfo(method, allowConstantPropagation);
     assert newCallSiteInfo.size == inValues.size();
+    assert newCallSiteInfo.dynamicUpperBoundTypes != null;
     for (int i = 0; i < newCallSiteInfo.size; i++) {
       Value arg = inValues.get(i);
-      // TODO(b/69963623): may need different place to store constants.
+      if (allowConstantPropagation) {
+        assert newCallSiteInfo.constants != null;
+        Value aliasedValue = arg.getAliasedValue();
+        if (!aliasedValue.isPhi()) {
+          AbstractValue abstractValue =
+              aliasedValue.definition.getAbstractValue(appView, method.method.holder);
+          if (abstractValue.isNonTrivial()) {
+            newCallSiteInfo.constants.put(i, abstractValue);
+          }
+        }
+      }
+
       if (arg.getTypeLattice().isPrimitive()) {
         continue;
       }
@@ -151,18 +200,19 @@
       return false;
     }
     ConcreteCallSiteOptimizationInfo otherInfo = (ConcreteCallSiteOptimizationInfo) other;
-    assert this.dynamicUpperBoundTypes != null;
-    return this.dynamicUpperBoundTypes.equals(otherInfo.dynamicUpperBoundTypes);
+    return Objects.equals(this.dynamicUpperBoundTypes, otherInfo.dynamicUpperBoundTypes)
+        && Objects.equals(this.constants, otherInfo.constants);
   }
 
   @Override
   public int hashCode() {
     assert this.dynamicUpperBoundTypes != null;
-    return System.identityHashCode(dynamicUpperBoundTypes);
+    return System.identityHashCode(dynamicUpperBoundTypes) * 7 + System.identityHashCode(constants);
   }
 
   @Override
   public String toString() {
-    return dynamicUpperBoundTypes.toString();
+    return dynamicUpperBoundTypes.toString()
+        + (constants == null ? "" : (System.lineSeparator() + constants.toString()));
   }
 }
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 7f8cfc4..9161f51 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
@@ -24,8 +24,8 @@
 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.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
@@ -242,22 +242,18 @@
     }
     if (returnValue != null) {
       Value aliasedValue = returnValue.getAliasedValue();
-      if (aliasedValue.isArgument()) {
-        // Find the argument number.
-        int index = aliasedValue.computeArgumentPosition(code);
-        assert index >= 0;
-        feedback.methodReturnsArgument(method, index);
-      }
-      if (aliasedValue.isConstant()) {
-        if (aliasedValue.definition.isConstNumber()) {
-          long value = aliasedValue.definition.asConstNumber().getRawValue();
-          feedback.methodReturnsConstantNumber(method, appView, value);
-        } else if (aliasedValue.definition.isConstString()) {
-          ConstString constStringInstruction = aliasedValue.definition.asConstString();
-          if (!constStringInstruction.instructionInstanceCanThrow()) {
-            feedback.methodReturnsConstantString(
-                method, appView, constStringInstruction.getValue());
-          }
+      if (!aliasedValue.isPhi()) {
+        Instruction definition = aliasedValue.definition;
+        if (definition.isArgument()) {
+          // Find the argument number.
+          int index = aliasedValue.computeArgumentPosition(code);
+          assert index >= 0;
+          feedback.methodReturnsArgument(method, index);
+        }
+        DexType context = method.method.holder;
+        AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
+        if (abstractReturnValue.isNonTrivial()) {
+          feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
         }
       }
     }
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 4c3578e..2f9c55b 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
@@ -8,13 +8,13 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
 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.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.BitSet;
@@ -123,8 +123,11 @@
   }
 
   @Override
-  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
-    getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+  public void recordFieldHasAbstractValue(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+    if (appView.appInfo().mayPropagateValueFor(field.field)) {
+      getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+    }
   }
 
   // METHOD OPTIMIZATION INFO:
@@ -157,15 +160,11 @@
   }
 
   @Override
-  public synchronized void methodReturnsConstantNumber(
-      DexEncodedMethod method, AppView<?> appView, long value) {
-    getMethodOptimizationInfoForUpdating(method).markReturnsConstantNumber(appView, value);
-  }
-
-  @Override
-  public synchronized void methodReturnsConstantString(
-      DexEncodedMethod method, AppView<?> appView, DexString value) {
-    getMethodOptimizationInfoForUpdating(method).markReturnsConstantString(appView, value);
+  public synchronized void methodReturnsAbstractValue(
+      DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
+    if (appView.appInfo().mayPropagateValueFor(method.method)) {
+      getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
+    }
   }
 
   @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 1b3e982..1be0c5c 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
@@ -8,13 +8,13 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
 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.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
 
@@ -47,7 +47,8 @@
   public void markFieldBitsRead(DexEncodedField field, int bitsRead) {}
 
   @Override
-  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {}
+  public void recordFieldHasAbstractValue(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {}
 
   // METHOD OPTIMIZATION INFO:
 
@@ -68,12 +69,8 @@
   public void methodReturnsArgument(DexEncodedMethod method, int argument) {}
 
   @Override
-  public void methodReturnsConstantNumber(
-      DexEncodedMethod method, AppView<?> appView, long value) {}
-
-  @Override
-  public void methodReturnsConstantString(
-      DexEncodedMethod method, AppView<?> appView, DexString value) {}
+  public void methodReturnsAbstractValue(
+      DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
 
   @Override
   public void methodReturnsObjectWithUpperBoundType(
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 ade0e2d..1d1dea5 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
@@ -8,13 +8,13 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
 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.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
 
@@ -57,7 +57,8 @@
   }
 
   @Override
-  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
+  public void recordFieldHasAbstractValue(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
     // Ignored.
   }
 
@@ -90,14 +91,8 @@
   }
 
   @Override
-  public void methodReturnsConstantNumber(
-      DexEncodedMethod method, AppView<?> appView, long value) {
-    // Ignored.
-  }
-
-  @Override
-  public void methodReturnsConstantString(
-      DexEncodedMethod method, AppView<?> appView, DexString value) {
+  public void methodReturnsAbstractValue(
+      DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
     // Ignored.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 8bb6fc3..c5a12c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
 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;
@@ -339,22 +338,10 @@
     neverReturnsNormally = true;
   }
 
-  void markReturnsConstantNumber(AppView<?> appView, long value) {
-    assert !abstractReturnValue.isSingleStringValue();
-    assert !abstractReturnValue.isSingleNumberValue()
-            || abstractReturnValue.asSingleNumberValue().getValue() == value
-        : "return constant number changed from "
-            + abstractReturnValue.asSingleNumberValue().getValue() + " to " + value;
-    abstractReturnValue = appView.abstractValueFactory().createSingleNumberValue(value);
-  }
-
-  void markReturnsConstantString(AppView<?> appView, DexString value) {
-    assert !abstractReturnValue.isSingleNumberValue();
-    assert !abstractReturnValue.isSingleStringValue()
-            || abstractReturnValue.asSingleStringValue().getDexString() == value
-        : "return constant string changed from "
-            + abstractReturnValue.asSingleStringValue().getDexString() + " to " + value;
-    abstractReturnValue = appView.abstractValueFactory().createSingleStringValue(value);
+  void markReturnsAbstractValue(AbstractValue value) {
+    assert !abstractReturnValue.isSingleValue() || abstractReturnValue.asSingleValue() == value
+        : "return single value changed from " + abstractReturnValue + " to " + value;
+    abstractReturnValue = value;
   }
 
   void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeLatticeElement type) {
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 b71d862..9a4565c 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -564,7 +566,9 @@
                   ? null
                   : code.createValue(
                       TypeLatticeElement.fromDexType(
-                          returnType, outValue.getTypeLattice().nullability(), appView),
+                          returnType,
+                          outValue == null ? maybeNull() : outValue.getTypeLattice().nullability(),
+                          appView),
                       outValue == null ? null : outValue.getLocalInfo());
           it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
         }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 26fbcee..5dcdbd8 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1059,7 +1059,7 @@
       if (checkcastInput.getLiveIntervals() != null &&
           !checkcastInput.getLiveIntervals().overlaps(unhandledInterval) &&
           checkcastInput.getLocalInfo() == unhandledInterval.getValue().definition.getLocalInfo()) {
-        unhandledInterval.setHint(checkcastInput.getLiveIntervals());
+        unhandledInterval.setHint(checkcastInput.getLiveIntervals(), unhandled);
       }
     }
   }
@@ -1077,13 +1077,13 @@
       assert left != null;
       if (left.getLiveIntervals() != null &&
           !left.getLiveIntervals().overlaps(unhandledInterval)) {
-        unhandledInterval.setHint(left.getLiveIntervals());
+        unhandledInterval.setHint(left.getLiveIntervals(), unhandled);
       } else {
         Value right = binOp.rightValue();
         assert right != null;
         if (binOp.isCommutative() && right.getLiveIntervals() != null &&
             !right.getLiveIntervals().overlaps(unhandledInterval)) {
-          unhandledInterval.setHint(right.getLiveIntervals());
+          unhandledInterval.setHint(right.getLiveIntervals(), unhandled);
         }
       }
     }
@@ -1212,7 +1212,7 @@
       if (!value.isPhi() && value.definition.isMove()) {
         Move move = value.definition.asMove();
         LiveIntervals intervals = move.src().getLiveIntervals();
-        intervals.setHint(current);
+        intervals.setHint(current, unhandled);
       }
       if (current != unhandledInterval) {
         // Only the start of unhandledInterval has been reached at this point. All other live
@@ -1780,11 +1780,9 @@
     // spilling. For phis we also use the hint before looking at the operand registers. The
     // phi could have a hint from an argument moves which it seems more important to honor in
     // practice.
-    LiveIntervals hint = unhandledInterval.getHint();
+    Integer hint = unhandledInterval.getHint();
     if (hint != null) {
-      int register = hint.getRegister();
-      if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair,
-          register)) {
+      if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair, hint)) {
         return true;
       }
     }
@@ -1865,13 +1863,14 @@
     for (Phi phi : value.uniquePhiUsers()) {
       LiveIntervals phiIntervals = phi.getLiveIntervals();
       if (phiIntervals.getHint() == null) {
+        phiIntervals.setHint(intervals, unhandled);
         for (int i = 0; i < phi.getOperands().size(); i++) {
           Value operand = phi.getOperand(i);
           LiveIntervals operandIntervals = operand.getLiveIntervals();
           BasicBlock pred = phi.getBlock().getPredecessors().get(i);
           operandIntervals = operandIntervals.getSplitCovering(pred.exit().getNumber());
           if (operandIntervals.getHint() == null) {
-            operandIntervals.setHint(intervals);
+            operandIntervals.setHint(intervals, unhandled);
           }
         }
       }
@@ -1888,7 +1887,7 @@
         BasicBlock pred = block.getPredecessors().get(i);
         LiveIntervals operandIntervals =
             operand.getLiveIntervals().getSplitCovering(pred.exit().getNumber());
-        operandIntervals.setHint(intervals);
+        operandIntervals.setHint(intervals, unhandled);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 0251687..9cb4c09 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -17,6 +17,7 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.PriorityQueue;
 import java.util.TreeSet;
 import java.util.function.IntConsumer;
 
@@ -36,7 +37,7 @@
   private final TreeSet<LiveIntervalsUse> uses = new TreeSet<>();
   private int numberOfConsecutiveRegisters = -1;
   private int register = NO_REGISTER;
-  private LiveIntervals hint;
+  private Integer hint;
   private boolean spilled = false;
   private boolean usedInMonitorOperations = false;
 
@@ -82,11 +83,20 @@
     return getType().requiredRegisters();
   }
 
-  public void setHint(LiveIntervals intervals) {
-    hint = intervals;
+  public void setHint(LiveIntervals intervals, PriorityQueue<LiveIntervals> unhandled) {
+    // Do not set hints if they cannot be used anyway.
+    if (!overlaps(intervals)) {
+      // The hint is used in sorting the unhandled intervals. Therefore, if the hint changes
+      // we have to remove and reinsert the interval to get the sorting updated.
+      boolean removed = unhandled.remove(this);
+      hint = intervals.getRegister();
+      if (removed) {
+        unhandled.add(this);
+      }
+    }
   }
 
-  public LiveIntervals getHint() {
+  public Integer getHint() {
     return hint;
   }
 
@@ -537,8 +547,23 @@
 
   @Override
   public int compareTo(LiveIntervals other) {
+    // Sort by interval start instruction number.
     int startDiff = getStart() - other.getStart();
-    return startDiff != 0 ? startDiff : (value.getNumber() - other.value.getNumber());
+    if (startDiff != 0) return startDiff;
+    // Then sort by register number of hints to make sure that a phi
+    // does not take a low register that is the hint for another phi.
+    if (hint != null && other.hint != null) {
+      int registerDiff = hint - other.hint;
+      if (registerDiff != 0) return registerDiff;
+    }
+    // Intervals with hints go first so intervals without hints
+    // do not take registers from intervals with hints.
+    if (hint != null && other.hint == null) return -1;
+    if (hint == null && other.hint != null) return 1;
+    // Tie-breaker: no values have equal numbers.
+    int result = value.getNumber() - other.value.getNumber();
+    assert result != 0;
+    return result;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
new file mode 100644
index 0000000..bec9df8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
@@ -0,0 +1,47 @@
+// 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.synthetic;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.Collections;
+
+// Source code representing simple forwarding method.
+public final class ExceptionThrowingSourceCode extends SyntheticSourceCode {
+
+  private static final int register = 0;
+  private final DexType exceptionType;
+
+  public ExceptionThrowingSourceCode(
+      DexType receiver, DexMethod method, Position callerPosition, DexType exceptionType) {
+    super(receiver, method, callerPosition);
+    this.exceptionType = exceptionType;
+  }
+
+  @Override
+  protected void prepareInstructions() {
+    add(
+        builder -> {
+          DexItemFactory factory = builder.appView.dexItemFactory();
+          DexProto initProto = factory.createProto(factory.voidType);
+          DexMethod initMethod =
+              factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
+          builder.addNewInstance(register, exceptionType);
+          builder.addInvoke(
+              Type.DIRECT,
+              initMethod,
+              initMethod.proto,
+              Collections.singletonList(ValueType.OBJECT),
+              Collections.singletonList(register),
+              false /* isInterface */);
+          builder.addThrow(register);
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 53f457f..7423075 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
@@ -22,7 +23,9 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -91,7 +94,34 @@
 
   @Override
   public DexString lookupName(DexMethod method) {
-    return renaming.getOrDefault(method, method.name);
+    DexString renamed = renaming.get(method);
+    if (renamed != null) {
+      return renamed;
+    }
+    // TODO(b/144339115): Don't allocate in the item factory during resolution!
+    if (method.holder == appView.dexItemFactory().methodHandleType) {
+      return method.name;
+    }
+    // If the method does not have a direct renaming, return the resolutions mapping.
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    if (resolutionResult.hasSingleTarget()) {
+      return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
+    }
+    // If resolution fails, the method must be renamed consistently with the targets that give rise
+    // to the failure.
+    if (resolutionResult.isFailedResolution()) {
+      List<DexEncodedMethod> targets = new ArrayList<>();
+      resolutionResult.asFailedResolution().forEachFailureDependency(clazz -> {}, targets::add);
+      if (!targets.isEmpty()) {
+        DexString firstRename = renaming.get(targets.get(0).method);
+        assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename);
+        if (firstRename != null) {
+          return firstRename;
+        }
+      }
+    }
+    // If no renaming can be found the default is the methods name.
+    return method.name;
   }
 
   @Override
@@ -160,7 +190,7 @@
         directTarget != null ? appView.definitionFor(directTarget.method.holder) : null;
     DexClass virtualTargetHolder =
         virtualTarget != null ? appView.definitionFor(virtualTarget.method.holder) : null;
-    return (directTarget == null && staticTarget == null && virtualTarget == null)
+    assert (directTarget == null && staticTarget == null && virtualTarget == null)
         || (virtualTarget != null && virtualTarget.method == item)
         || (directTarget != null && directTarget.method == item)
         || (staticTarget != null && staticTarget.method == item)
@@ -168,6 +198,7 @@
         || (virtualTargetHolder != null && virtualTargetHolder.isNotProgramClass())
         || (staticTargetHolder != null && staticTargetHolder.isNotProgramClass())
         || appView.unneededVisibilityBridgeMethods().contains(item);
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index e45ef21..0a89715 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 
@@ -21,13 +20,13 @@
 public final class MethodReference {
   private final ClassReference holderClass;
   private final String methodName;
-  private final ImmutableList<TypeReference> formalTypes;
+  private final List<TypeReference> formalTypes;
   private final TypeReference returnType;
 
   MethodReference(
       ClassReference holderClass,
       String methodName,
-      ImmutableList<TypeReference> formalTypes,
+      List<TypeReference> formalTypes,
       TypeReference returnType) {
     assert holderClass != null;
     assert methodName != null;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f5b0de8..319cbaf 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -758,22 +758,26 @@
     return false;
   }
 
-  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+  public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     if (!isPinned(field.field)) {
-      DexEncodedMethod staticInitializer =
-          definitionFor(field.field.holder).asProgramClass().getClassInitializer();
-      if (staticInitializer != null) {
-        FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-        return fieldAccessInfo != null
-            && fieldAccessInfo.isWritten()
-            && !fieldAccessInfo.isWrittenOutside(staticInitializer);
-      }
+      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+      return fieldAccessInfo != null
+          && fieldAccessInfo.isWritten()
+          && !fieldAccessInfo.isWrittenOutside(method);
     }
     return false;
   }
 
+  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+    assert checkIfObsolete();
+    assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
+    DexEncodedMethod staticInitializer =
+        definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+    return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
+  }
+
   public boolean mayPropagateValueFor(DexReference reference) {
     assert checkIfObsolete();
     return !isPinned(reference) && !neverPropagateValue.contains(reference);
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 400ce93..24108b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -38,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.FailedResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
@@ -60,6 +62,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -178,6 +181,9 @@
    */
   private final SetWithReportedReason<DexProgramClass> liveTypes;
 
+  /** Set of types whose class initializer may execute. */
+  private final SetWithReportedReason<DexProgramClass> initializedTypes;
+
   /** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
   private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
 
@@ -314,6 +320,7 @@
 
     liveTypes = new SetWithReportedReason<>();
     liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
+    initializedTypes = new SetWithReportedReason<>();
     instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
     targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
     // This set is only populated in edge cases due to multiple default interface methods.
@@ -1186,16 +1193,6 @@
     // CF libraries can be used by Android apps. See b/136698023 for more information.
     ensureMethodsContinueToWidenAccess(holder, seen, reason);
 
-    // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
-    // static field initialization (and other class-load-time sideeffects) will not happen.
-    if (holder.hasClassInitializer()) {
-      DexEncodedMethod clinit = holder.getClassInitializer();
-      if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
-        assert clinit.method.holder == holder.type;
-        markDirectStaticOrConstructorMethodAsLive(holder, clinit, reason);
-      }
-    }
-
     if (holder.isSerializable(appView)) {
       enqueueFirstNonSerializableClassInitializer(holder, reason);
     }
@@ -1317,17 +1314,45 @@
 
     // Only mark methods for which invocation will succeed at runtime live.
     if (encodedMethod.isStatic()) {
-      registerClassInitializer(clazz, reason);
+      markDirectAndIndirectClassInitializersAsLive(clazz);
       markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
     }
   }
 
-  private void registerClassInitializer(DexProgramClass definition, KeepReason reason) {
-    if (definition.hasClassInitializer()) {
-      graphReporter.registerMethod(definition.getClassInitializer(), reason);
+  private void markDirectAndIndirectClassInitializersAsLive(DexProgramClass clazz) {
+    Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(clazz);
+    Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(clazz);
+    while (!worklist.isEmpty()) {
+      DexProgramClass current = worklist.removeFirst();
+      assert visited.contains(current);
+
+      if (!markDirectClassInitializerAsLive(current)) {
+        continue;
+      }
+
+      // Mark all class initializers in all super types as live.
+      for (DexType superType : clazz.allImmediateSupertypes()) {
+        DexProgramClass superClass = getProgramClassOrNull(superType);
+        if (superClass != null && visited.add(superClass)) {
+          worklist.add(superClass);
+        }
+      }
     }
   }
 
+  /** Returns true if the class initializer became live for the first time. */
+  private boolean markDirectClassInitializerAsLive(DexProgramClass clazz) {
+    DexEncodedMethod clinit = clazz.getClassInitializer();
+    KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
+    if (!initializedTypes.add(clazz, witness)) {
+      return false;
+    }
+    if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
+      markDirectStaticOrConstructorMethodAsLive(clazz, clinit, witness);
+    }
+    return true;
+  }
+
   // Package protected due to entry point from worklist.
   void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
     handleInvokeOfDirectTarget(method, reason);
@@ -1463,6 +1488,8 @@
     }
     // This class becomes live, so it and all its supertypes become live types.
     markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
+    // Instantiation triggers class initialization.
+    markDirectAndIndirectClassInitializersAsLive(clazz);
     // For all methods of the class, if we have seen a call, mark the method live.
     // We only do this for virtual calls, as the other ones will be done directly.
     transitionMethodsForInstantiatedClass(clazz);
@@ -1708,7 +1735,7 @@
       return;
     }
 
-    registerClassInitializer(clazz, reason);
+    markDirectAndIndirectClassInitializersAsLive(clazz);
 
     // This field might be an instance field reachable from a static context, e.g. a getStatic that
     // resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
@@ -2014,13 +2041,18 @@
       DexMethod method, boolean interfaceInvoke, KeepReason reason) {
     ResolutionResult resolutionResult =
         appInfo.resolveMethod(method.holder, method, interfaceInvoke);
-    if (!resolutionResult.hasSingleTarget()) {
+    if (resolutionResult.isFailedResolution()) {
       // If the resolution fails, mark each dependency causing a failure.
-      markFailedResolutionTargets(resolutionResult, reason);
+      markFailedResolutionTargets(resolutionResult.asFailedResolution(), reason);
       return MarkedResolutionTarget.unresolved();
     }
 
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    if (resolutionTarget == null) {
+      reportMissingMethod(method);
+      return MarkedResolutionTarget.unresolved();
+    }
+
     DexClass resolutionTargetClass = appInfo.definitionFor(resolutionTarget.method.holder);
     if (resolutionTargetClass == null) {
       reportMissingClass(resolutionTarget.method.holder);
@@ -2042,8 +2074,12 @@
     return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
   }
 
-  private void markFailedResolutionTargets(ResolutionResult failedResolution, KeepReason reason) {
-    failedResolution.forEachTarget(
+  private void markFailedResolutionTargets(
+      FailedResolutionResult failedResolution, KeepReason reason) {
+    failedResolution.forEachFailureDependency(
+        clazz -> {
+          throw new Unimplemented();
+        },
         method -> {
           DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
           if (clazz != null) {
@@ -2460,6 +2496,10 @@
       return;
     }
 
+    if (method.isStatic()) {
+      markDirectAndIndirectClassInitializersAsLive(clazz);
+    }
+
     Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
     if (superCallTargets != null) {
       for (DexEncodedMethod superCallTarget : superCallTargets) {
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 521b90c..cf820bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -227,6 +227,22 @@
     return KeepReasonWitness.INSTANCE;
   }
 
+  public KeepReasonWitness reportReachableClassInitializer(
+      DexProgramClass clazz, DexEncodedMethod initializer) {
+    if (initializer != null) {
+      assert clazz.type == initializer.method.holder;
+      assert initializer.isClassInitializer();
+      if (keptGraphConsumer != null) {
+        ClassGraphNode source = getClassGraphNode(clazz.type);
+        MethodGraphNode target = getMethodGraphNode(initializer.method);
+        return reportEdge(source, target, EdgeKind.ReachableFromLiveType);
+      }
+    } else {
+      assert !clazz.hasClassInitializer();
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
   public KeepReasonWitness reportReachableMethodAsLive(
       DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
     if (keptGraphConsumer != null) {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index af056a8..adbaad4 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -429,6 +430,46 @@
     return 'L' + descriptor + ';';
   }
 
+  public static class ModuleAndDescriptor {
+    private final String module;
+    private final String descriptor;
+
+    ModuleAndDescriptor(String module, String descriptor) {
+      this.module = module;
+      this.descriptor = descriptor;
+    }
+
+    public String getModule() {
+      return module;
+    }
+
+    public String getDescriptor() {
+      return descriptor;
+    }
+  }
+
+  /**
+   * Guess module and class descriptor from the location of a class file in a jrt file system.
+   *
+   * @param name the location in a jrt file system of the class file to convert to descriptor
+   * @return module and java class descriptor
+   */
+  public static ModuleAndDescriptor guessJrtModuleAndTypeDescriptor(String name) {
+    assert name != null;
+    assert name.endsWith(CLASS_EXTENSION)
+        : "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
+    assert name.startsWith(MODULES_PREFIX)
+        : "Name " + name + " must have " + MODULES_PREFIX + " prefix";
+    assert name.charAt(MODULES_PREFIX.length()) == '/';
+    int moduleNameEnd = name.indexOf('/', MODULES_PREFIX.length() + 1);
+    String module = name.substring(MODULES_PREFIX.length() + 1, moduleNameEnd);
+    String descriptor = name.substring(moduleNameEnd + 1, name.length() - CLASS_EXTENSION.length());
+    if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) {
+      throw new CompilationError("Unexpected class file name: " + name);
+    }
+    return new ModuleAndDescriptor(module, 'L' + descriptor + ';');
+  }
+
   public static String getPathFromDescriptor(String descriptor) {
     // We are quite loose on names here to support testing illegal names, too.
     assert descriptor.startsWith("L");
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 24c35fb..19fc10a 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -30,6 +30,7 @@
   public static final String JAVA_EXTENSION = ".java";
   public static final String KT_EXTENSION = ".kt";
   public static final String MODULE_INFO_CLASS = "module-info.class";
+  public static final String MODULES_PREFIX = "/modules";
 
   public static final boolean isAndroid =
       System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
diff --git a/src/main/java/com/android/tools/r8/utils/HeapUtils.java b/src/main/java/com/android/tools/r8/utils/HeapUtils.java
new file mode 100644
index 0000000..6834f66
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/HeapUtils.java
@@ -0,0 +1,38 @@
+// 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.utils;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import javax.management.MBeanServer;
+
+public class HeapUtils {
+
+  private static final String HOTSPOT_MBEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
+  private static volatile HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean;
+
+  private static void initHotSpotMBean() throws IOException {
+    if (hotSpotDiagnosticMXBean == null) {
+      synchronized (HeapUtils.class) {
+        if (hotSpotDiagnosticMXBean == null) {
+          hotSpotDiagnosticMXBean = getHotSpotDiagnosticMXBean();
+        }
+      }
+    }
+  }
+
+  private static HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {
+    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+    return ManagementFactory.newPlatformMXBeanProxy(
+        server, HOTSPOT_MBEAN_NAME, HotSpotDiagnosticMXBean.class);
+  }
+
+  public static void dumpHeap(Path fileName, boolean live) throws IOException {
+    initHotSpotMBean();
+    hotSpotDiagnosticMXBean.dumpHeap(fileName.toString(), live);
+  }
+}
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 2815cb8..595ac28 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -169,7 +169,8 @@
     enableValuePropagation = false;
     enableSideEffectAnalysis = false;
     enableTreeShakingOfLibraryMethodOverrides = false;
-    enableCallSiteOptimizationInfoPropagation = false;
+    enablePropagationOfDynamicTypesAtCallSites = false;
+    enablePropagationOfConstantsAtCallSites = false;
   }
 
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -216,7 +217,9 @@
   public boolean enableNameReflectionOptimization = true;
   public boolean enableStringConcatenationOptimization = true;
   public boolean enableTreeShakingOfLibraryMethodOverrides = false;
-  public boolean enableCallSiteOptimizationInfoPropagation = true;
+  public boolean enablePropagationOfDynamicTypesAtCallSites = true;
+  // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
+  public boolean enablePropagationOfConstantsAtCallSites = false;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
 
@@ -971,6 +974,7 @@
     public boolean allowUnusedProguardConfigurationRules = true;
     public boolean reportUnusedProguardConfigurationRules = false;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
+    public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableSwitchToIfRewriting = true;
     public boolean forceRedundantConstNumberRemoval = false;
@@ -1037,6 +1041,8 @@
       public int numberOfProguardIfRuleClassEvaluations = 0;
       public int numberOfProguardIfRuleMemberEvaluations = 0;
     }
+
+    public Consumer<DexEncodedMethod> callSiteOptimizationInfoInspector = null;
   }
 
   @VisibleForTesting
@@ -1046,6 +1052,13 @@
     enableNameReflectionOptimization = false;
   }
 
+  // TODO(b/69963623): Remove this once enabled.
+  @VisibleForTesting
+  public void enablePropagationOfConstantsAtCallSites() {
+    assert !enablePropagationOfConstantsAtCallSites;
+    enablePropagationOfConstantsAtCallSites = true;
+  }
+
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 89ef8f3..acaf11f 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -20,6 +20,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -27,6 +28,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import java.util.zip.CRC32;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -71,6 +73,21 @@
     }
   }
 
+  public static void zip(Path zipFile, Path inputDirectory) throws IOException {
+    try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zipFile))) {
+      List<Path> files =
+          Files.walk(inputDirectory)
+              .filter(path -> !Files.isDirectory(path))
+              .collect(Collectors.toList());
+      for (Path path : files) {
+        ZipEntry zipEntry = new ZipEntry(inputDirectory.relativize(path).toString());
+        stream.putNextEntry(zipEntry);
+        Files.copy(path, stream);
+        stream.closeEntry();
+      }
+    }
+  }
+
   public static List<File> unzip(String zipFile, File outDirectory) throws IOException {
     return unzip(zipFile, outDirectory, (entry) -> true);
   }
diff --git a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
new file mode 100644
index 0000000..1ad06e0
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class DoubleSummaryStatisticsConversions {
+
+  private static final Field JAVA_LONG_COUNT_FIELD;
+  private static final Field JAVA_DOUBLE_SUM_FIELD;
+  private static final Field JAVA_DOUBLE_MIN_FIELD;
+  private static final Field JAVA_DOUBLE_MAX_FIELD;
+  private static final Field JD_LONG_COUNT_FIELD;
+  private static final Field JD_DOUBLE_SUM_FIELD;
+  private static final Field JD_DOUBLE_MIN_FIELD;
+  private static final Field JD_DOUBLE_MAX_FIELD;
+
+  static {
+    Class<?> javaDoubleSummaryStatisticsClass = java.util.DoubleSummaryStatistics.class;
+    JAVA_LONG_COUNT_FIELD = getField(javaDoubleSummaryStatisticsClass, "count");
+    JAVA_LONG_COUNT_FIELD.setAccessible(true);
+    JAVA_DOUBLE_SUM_FIELD = getField(javaDoubleSummaryStatisticsClass, "sum");
+    JAVA_DOUBLE_SUM_FIELD.setAccessible(true);
+    JAVA_DOUBLE_MIN_FIELD = getField(javaDoubleSummaryStatisticsClass, "min");
+    JAVA_DOUBLE_MIN_FIELD.setAccessible(true);
+    JAVA_DOUBLE_MAX_FIELD = getField(javaDoubleSummaryStatisticsClass, "max");
+    JAVA_DOUBLE_MAX_FIELD.setAccessible(true);
+
+    Class<?> jdDoubleSummaryStatisticsClass = j$.util.DoubleSummaryStatistics.class;
+    JD_LONG_COUNT_FIELD = getField(jdDoubleSummaryStatisticsClass, "count");
+    JD_LONG_COUNT_FIELD.setAccessible(true);
+    JD_DOUBLE_SUM_FIELD = getField(jdDoubleSummaryStatisticsClass, "sum");
+    JD_DOUBLE_SUM_FIELD.setAccessible(true);
+    JD_DOUBLE_MIN_FIELD = getField(jdDoubleSummaryStatisticsClass, "min");
+    JD_DOUBLE_MIN_FIELD.setAccessible(true);
+    JD_DOUBLE_MAX_FIELD = getField(jdDoubleSummaryStatisticsClass, "max");
+    JD_DOUBLE_MAX_FIELD.setAccessible(true);
+  }
+
+  private static Field getField(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException e) {
+      throw new Error("Failed summary statistics set-up.", e);
+    }
+  }
+
+  public static j$.util.DoubleSummaryStatistics convert(java.util.DoubleSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    j$.util.DoubleSummaryStatistics newInstance = new j$.util.DoubleSummaryStatistics();
+    try {
+      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JD_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
+      JD_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
+      JD_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+
+  public static java.util.DoubleSummaryStatistics convert(j$.util.DoubleSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    java.util.DoubleSummaryStatistics newInstance = new java.util.DoubleSummaryStatistics();
+    try {
+      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JAVA_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
+      JAVA_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
+      JAVA_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+}
diff --git a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
new file mode 100644
index 0000000..8351704
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class IntSummaryStatisticsConversions {
+
+  private static final Field JAVA_LONG_COUNT_FIELD;
+  private static final Field JAVA_LONG_SUM_FIELD;
+  private static final Field JAVA_INT_MIN_FIELD;
+  private static final Field JAVA_INT_MAX_FIELD;
+  private static final Field JD_LONG_COUNT_FIELD;
+  private static final Field JD_LONG_SUM_FIELD;
+  private static final Field JD_INT_MIN_FIELD;
+  private static final Field JD_INT_MAX_FIELD;
+
+  static {
+    Class<?> javaIntSummaryStatisticsClass = java.util.IntSummaryStatistics.class;
+    JAVA_LONG_COUNT_FIELD = getField(javaIntSummaryStatisticsClass, "count");
+    JAVA_LONG_COUNT_FIELD.setAccessible(true);
+    JAVA_LONG_SUM_FIELD = getField(javaIntSummaryStatisticsClass, "sum");
+    JAVA_LONG_SUM_FIELD.setAccessible(true);
+    JAVA_INT_MIN_FIELD = getField(javaIntSummaryStatisticsClass, "min");
+    JAVA_INT_MIN_FIELD.setAccessible(true);
+    JAVA_INT_MAX_FIELD = getField(javaIntSummaryStatisticsClass, "max");
+    JAVA_INT_MAX_FIELD.setAccessible(true);
+
+    Class<?> jdIntSummaryStatisticsClass = j$.util.IntSummaryStatistics.class;
+    JD_LONG_COUNT_FIELD = getField(jdIntSummaryStatisticsClass, "count");
+    JD_LONG_COUNT_FIELD.setAccessible(true);
+    JD_LONG_SUM_FIELD = getField(jdIntSummaryStatisticsClass, "sum");
+    JD_LONG_SUM_FIELD.setAccessible(true);
+    JD_INT_MIN_FIELD = getField(jdIntSummaryStatisticsClass, "min");
+    JD_INT_MIN_FIELD.setAccessible(true);
+    JD_INT_MAX_FIELD = getField(jdIntSummaryStatisticsClass, "max");
+    JD_INT_MAX_FIELD.setAccessible(true);
+  }
+
+  private static Field getField(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException e) {
+      throw new Error("Failed summary statistics set-up.", e);
+    }
+  }
+
+  public static j$.util.IntSummaryStatistics convert(java.util.IntSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    j$.util.IntSummaryStatistics newInstance = new j$.util.IntSummaryStatistics();
+    try {
+      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+      JD_INT_MIN_FIELD.set(newInstance, stats.getMin());
+      JD_INT_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+
+  public static java.util.IntSummaryStatistics convert(j$.util.IntSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    java.util.IntSummaryStatistics newInstance = new java.util.IntSummaryStatistics();
+    try {
+      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+      JAVA_INT_MIN_FIELD.set(newInstance, stats.getMin());
+      JAVA_INT_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+}
diff --git a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
new file mode 100644
index 0000000..7096a2a
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class LongSummaryStatisticsConversions {
+
+  private static final Field JAVA_LONG_COUNT_FIELD;
+  private static final Field JAVA_LONG_SUM_FIELD;
+  private static final Field JAVA_LONG_MIN_FIELD;
+  private static final Field JAVA_LONG_MAX_FIELD;
+  private static final Field JD_LONG_COUNT_FIELD;
+  private static final Field JD_LONG_SUM_FIELD;
+  private static final Field JD_LONG_MIN_FIELD;
+  private static final Field JD_LONG_MAX_FIELD;
+
+  static {
+    Class<?> javaLongSummaryStatisticsClass = java.util.LongSummaryStatistics.class;
+    JAVA_LONG_COUNT_FIELD = getField(javaLongSummaryStatisticsClass, "count");
+    JAVA_LONG_COUNT_FIELD.setAccessible(true);
+    JAVA_LONG_SUM_FIELD = getField(javaLongSummaryStatisticsClass, "sum");
+    JAVA_LONG_SUM_FIELD.setAccessible(true);
+    JAVA_LONG_MIN_FIELD = getField(javaLongSummaryStatisticsClass, "min");
+    JAVA_LONG_MIN_FIELD.setAccessible(true);
+    JAVA_LONG_MAX_FIELD = getField(javaLongSummaryStatisticsClass, "max");
+    JAVA_LONG_MAX_FIELD.setAccessible(true);
+
+    Class<?> jdLongSummaryStatisticsClass = j$.util.LongSummaryStatistics.class;
+    JD_LONG_COUNT_FIELD = getField(jdLongSummaryStatisticsClass, "count");
+    JD_LONG_COUNT_FIELD.setAccessible(true);
+    JD_LONG_SUM_FIELD = getField(jdLongSummaryStatisticsClass, "sum");
+    JD_LONG_SUM_FIELD.setAccessible(true);
+    JD_LONG_MIN_FIELD = getField(jdLongSummaryStatisticsClass, "min");
+    JD_LONG_MIN_FIELD.setAccessible(true);
+    JD_LONG_MAX_FIELD = getField(jdLongSummaryStatisticsClass, "max");
+    JD_LONG_MAX_FIELD.setAccessible(true);
+  }
+
+  private static Field getField(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException e) {
+      throw new Error("Failed summary statistics set-up.", e);
+    }
+  }
+
+  public static j$.util.LongSummaryStatistics convert(java.util.LongSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    j$.util.LongSummaryStatistics newInstance = new j$.util.LongSummaryStatistics();
+    try {
+      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+      JD_LONG_MIN_FIELD.set(newInstance, stats.getMin());
+      JD_LONG_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+
+  public static java.util.LongSummaryStatistics convert(j$.util.LongSummaryStatistics stats) {
+    if (stats == null) {
+      return null;
+    }
+    java.util.LongSummaryStatistics newInstance = new java.util.LongSummaryStatistics();
+    try {
+      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+      JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+      JAVA_LONG_MIN_FIELD.set(newInstance, stats.getMin());
+      JAVA_LONG_MAX_FIELD.set(newInstance, stats.getMax());
+    } catch (IllegalAccessException e) {
+      throw new Error("Failed summary statistics conversion.", e);
+    }
+    return newInstance;
+  }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java
new file mode 100644
index 0000000..33710da
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java
@@ -0,0 +1,24 @@
+// 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 j$.util;
+
+public class DoubleSummaryStatistics {
+
+  public long getCount() {
+    return 0L;
+  }
+
+  public double getSum() {
+    return 0.0;
+  }
+
+  public double getMin() {
+    return 0.0;
+  }
+
+  public double getMax() {
+    return 0.0;
+  }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java
new file mode 100644
index 0000000..2359f8d
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java
@@ -0,0 +1,24 @@
+// 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 j$.util;
+
+public class IntSummaryStatistics {
+
+  public long getCount() {
+    return 0;
+  }
+
+  public long getSum() {
+    return 0;
+  }
+
+  public int getMin() {
+    return 0;
+  }
+
+  public int getMax() {
+    return 0;
+  }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java
new file mode 100644
index 0000000..bd993b0
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java
@@ -0,0 +1,24 @@
+// 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 j$.util;
+
+public class LongSummaryStatistics {
+
+  public long getCount() {
+    return 0L;
+  }
+
+  public long getSum() {
+    return 0L;
+  }
+
+  public long getMin() {
+    return 0L;
+  }
+
+  public long getMax() {
+    return 0L;
+  }
+}
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
index edb9d77..7012340 100644
--- a/src/test/examples/shaking18/Options.java
+++ b/src/test/examples/shaking18/Options.java
@@ -4,9 +4,7 @@
 package shaking18;
 
 public class Options {
-  // TODO(b/138913138): member value propagation can behave same with and without initialization.
-  // public boolean alwaysFalse = false;
-  public boolean alwaysFalse;
+  public boolean alwaysFalse = false;
   public boolean dummy = false;
 
   public Options() {}
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
new file mode 100644
index 0000000..1754bff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -0,0 +1,655 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+import org.hamcrest.core.StringContains;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class CompileWithJdkClassFileProviderTest extends TestBase implements Opcodes {
+
+  @Parameters(name = "{0}, library: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimes().build(), CfVm.values());
+  }
+
+  private final TestParameters parameters;
+  private final CfVm library;
+
+  public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfVm library) {
+    this.parameters = parameters;
+    this.library = library;
+  }
+
+  @Test
+  public void compileSimpleCodeWithJdkLibrary() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+    testForR8(parameters.getBackend())
+        .addLibraryProvider(provider)
+        .addProgramClasses(TestRunner.class)
+        .addKeepMainRule(TestRunner.class)
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), TestRunner.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void compileSimpleCodeWithSystemJdk() throws Exception {
+    // Don't run duplicate tests (library is not used by the test).
+    assumeTrue(library == CfVm.JDK8);
+
+    ClassFileResourceProvider provider = JdkClassFileProvider.fromSystemJdk();
+
+    testForR8(parameters.getBackend())
+        .addLibraryProvider(provider)
+        .addProgramClasses(TestRunner.class)
+        .addKeepMainRule(TestRunner.class)
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), TestRunner.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void compileCodeWithJava9APIUsage() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+    TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
+        testForR8(parameters.getBackend())
+            .addLibraryProvider(provider)
+            .addProgramClassFileData(dumpClassWhichUseJava9Flow())
+            .addKeepMainRule("MySubscriber");
+
+    if (library == CfVm.JDK8) {
+      try {
+        // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
+        testBuilder.compileWithExpectedDiagnostics(
+            diagnotics -> {
+              diagnotics.assertErrorsCount(1);
+              diagnotics.assertWarningsCount(1);
+              diagnotics.assertInfosCount(0);
+              assertThat(
+                  diagnotics.getErrors().get(0).getDiagnosticMessage(),
+                  StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+              assertThat(
+                  diagnotics.getWarnings().get(0).getDiagnosticMessage(),
+                  StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+            });
+      } catch (CompilationFailedException e) {
+        return;
+      }
+      fail("Expected compilation error");
+    } else {
+      if (parameters.getRuntime().isDex()) {
+        // java.util.concurrent.Flow$Subscriber is not present on Android.
+        testBuilder
+            .run(parameters.getRuntime(), "MySubscriber")
+            .assertFailureWithErrorThatMatches(
+                anyOf(
+                    // Dalvik 4.0.4
+                    containsString("java.lang.NoClassDefFoundError: MySubscriber"),
+                    // Other Dalviks.
+                    containsString(
+                        "java.lang.ClassNotFoundException: Didn't find class \"MySubscriber\""),
+                    // Art.
+                    containsString(
+                        "java.lang.NoClassDefFoundError: "
+                            + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;")));
+      } else {
+        if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
+          // java.util.concurrent.Flow$Subscriber not present in JDK8.
+          testBuilder
+              .run(parameters.getRuntime(), "MySubscriber")
+              .assertFailureWithErrorThatMatches(
+                  containsString("Could not find or load main class MySubscriber"));
+
+        } else {
+          // java.util.concurrent.Flow$Subscriber present in JDK9+.
+          testBuilder
+              .run(parameters.getRuntime(), "MySubscriber")
+              .disassemble()
+              .assertSuccessWithOutputLines("Got : 1", "Got : 2", "Got : 3", "Done");
+        }
+      }
+    }
+
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  /*
+   * The code below is from compiling the following source with javac from OpenJDK 9.0.4 with
+   * options:
+   *
+   *    -source 1.8 -target 1.8
+   *
+   * Note that -source 1.8 on on Java 9 will use the builtin boot class path (including
+   * java.util.concurrent.Flow) if no explicit boot classpath is specified.
+   *
+   * import java.util.List;
+   * import java.lang.Thread;
+   * import java.util.concurrent.Flow.Subscriber;
+   * import java.util.concurrent.Flow.Subscription;
+   * import java.util.concurrent.SubmissionPublisher;
+   * import java.util.concurrent.locks.Condition;
+   * import java.util.concurrent.locks.Lock;
+   * import java.util.concurrent.locks.ReentrantLock;
+   *
+   * public class MySubscriber<T> implements Subscriber<T> {
+   *   final static Lock lock = new ReentrantLock();
+   *   final static Condition done  = lock.newCondition();
+   *
+   *   private Subscription subscription;
+   *
+   *   @Override
+   *   public void onSubscribe(Subscription subscription) {
+   *     this.subscription = subscription;
+   *     subscription.request(1);
+   *   }
+   *
+   *   @Override
+   *   public void onNext(T item) {
+   *     System.out.println("Got : " + item);
+   *     subscription.request(1);
+   *   }
+   *   @Override
+   *   public void onError(Throwable t) {
+   *     t.printStackTrace();
+   *   }
+   *
+   *   @Override
+   *   public void onComplete() {
+   *     System.out.println("Done");
+   *     signalCondition(done);
+   *   }
+   *
+   *   public static void awaitCondition(Condition condition) throws Exception {
+   *     lock.lock();
+   *     try {
+   *       condition.await();
+   *     } finally {
+   *       lock.unlock();
+   *     }
+   *   }
+   *
+   *   public static void signalCondition(Condition condition) {
+   *     lock.lock();
+   *     try {
+   *       condition.signal();
+   *     } finally {
+   *       lock.unlock();
+   *     }
+   *   }
+   *
+   *   public static void main(String[] args) throws Exception {
+   *     SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
+   *     MySubscriber<String> subscriber = new MySubscriber<>();
+   *     publisher.subscribe(subscriber);
+   *     List<String> items = List.of("1", "2", "3");
+   *
+   *     items.forEach(publisher::submit);
+   *     publisher.close();
+   *
+   *     awaitCondition(done);
+   *   }
+   * }
+   *
+   */
+  public static byte[] dumpClassWhichUseJava9Flow() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "MySubscriber",
+        "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/concurrent/Flow$Subscriber<TT;>;",
+        "java/lang/Object",
+        new String[] {"java/util/concurrent/Flow$Subscriber"});
+
+    classWriter.visitSource("MySubscriber.java", null);
+
+    classWriter.visitInnerClass(
+        "java/util/concurrent/Flow$Subscription",
+        "java/util/concurrent/Flow",
+        "Subscription",
+        ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+    classWriter.visitInnerClass(
+        "java/util/concurrent/Flow$Subscriber",
+        "java/util/concurrent/Flow",
+        "Subscriber",
+        ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+    classWriter.visitInnerClass(
+        "java/lang/invoke/MethodHandles$Lookup",
+        "java/lang/invoke/MethodHandles",
+        "Lookup",
+        ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_FINAL | ACC_STATIC, "lock", "Ljava/util/concurrent/locks/Lock;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_FINAL | ACC_STATIC, "done", "Ljava/util/concurrent/locks/Condition;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PRIVATE, "subscription", "Ljava/util/concurrent/Flow$Subscription;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(10, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC, "onSubscribe", "(Ljava/util/concurrent/Flow$Subscription;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitFieldInsn(
+          PUTFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitInsn(LCONST_1);
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(20, label2);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC, "onNext", "(Ljava/lang/Object;)V", "(TT;)V", null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(24, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      methodVisitor.visitLdcInsn("Got : ");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(25, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(
+          GETFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+      methodVisitor.visitInsn(LCONST_1);
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(26, label2);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC, "onError", "(Ljava/lang/Throwable;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(29, label0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(30, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "onComplete", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(34, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("Done");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(35, label1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "MySubscriber",
+          "signalCondition",
+          "(Ljava/util/concurrent/locks/Condition;)V",
+          false);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(36, label2);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "awaitCondition",
+              "(Ljava/util/concurrent/locks/Condition;)V",
+              null,
+              new String[] {"java/lang/Exception"});
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(39, label3);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(41, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "await", "()V", true);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(43, label1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(44, label4);
+      Label label5 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label5);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(43, label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(45, label5);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "signalCondition",
+              "(Ljava/util/concurrent/locks/Condition;)V",
+              null,
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(48, label3);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(50, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "signal", "()V", true);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(52, label1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(53, label4);
+      Label label5 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label5);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(52, label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(54, label5);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "main",
+              "([Ljava/lang/String;)V",
+              null,
+              new String[] {"java/lang/Exception"});
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(57, label0);
+      methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/SubmissionPublisher");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/util/concurrent/SubmissionPublisher", "<init>", "()V", false);
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(58, label1);
+      methodVisitor.visitTypeInsn(NEW, "MySubscriber");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "MySubscriber", "<init>", "()V", false);
+      methodVisitor.visitVarInsn(ASTORE, 2);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(59, label2);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitVarInsn(ALOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/util/concurrent/SubmissionPublisher",
+          "subscribe",
+          "(Ljava/util/concurrent/Flow$Subscriber;)V",
+          false);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(60, label3);
+      methodVisitor.visitLdcInsn("1");
+      methodVisitor.visitLdcInsn("2");
+      methodVisitor.visitLdcInsn("3");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/List",
+          "of",
+          "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;",
+          true);
+      methodVisitor.visitVarInsn(ASTORE, 3);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(62, label4);
+      methodVisitor.visitVarInsn(ALOAD, 3);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/Objects",
+          "requireNonNull",
+          "(Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitInvokeDynamicInsn(
+          "accept",
+          "(Ljava/util/concurrent/SubmissionPublisher;)Ljava/util/function/Consumer;",
+          new Handle(
+              Opcodes.H_INVOKESTATIC,
+              "java/lang/invoke/LambdaMetafactory",
+              "metafactory",
+              "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+              false),
+          new Object[] {
+            Type.getType("(Ljava/lang/Object;)V"),
+            new Handle(
+                Opcodes.H_INVOKEVIRTUAL,
+                "java/util/concurrent/SubmissionPublisher",
+                "submit",
+                "(Ljava/lang/Object;)I",
+                false),
+            Type.getType("(Ljava/lang/String;)V")
+          });
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE, "java/util/List", "forEach", "(Ljava/util/function/Consumer;)V", true);
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(63, label5);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/util/concurrent/SubmissionPublisher", "close", "()V", false);
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(65, label6);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "MySubscriber",
+          "awaitCondition",
+          "(Ljava/util/concurrent/locks/Condition;)V",
+          false);
+      Label label7 = new Label();
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitLineNumber(66, label7);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 4);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/locks/ReentrantLock");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/util/concurrent/locks/ReentrantLock", "<init>", "()V", false);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(12, label1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE,
+          "java/util/concurrent/locks/Lock",
+          "newCondition",
+          "()Ljava/util/concurrent/locks/Condition;",
+          true);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static class TestRunner {
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
new file mode 100644
index 0000000..d527670
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -0,0 +1,131 @@
+// 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 com.android.tools.r8.ToolHelper.isWindows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.rules.TemporaryFolder;
+
+public class JavaCompilerTool {
+
+  private final CfVm jdk;
+  private final TestState state;
+  private final List<Path> sources = new ArrayList<>();
+  private final List<Path> classpath = new ArrayList<>();
+  private final List<String> options = new ArrayList<>();
+  private Path output = null;
+
+  private JavaCompilerTool(CfVm jdk, TestState state) {
+    this.jdk = jdk;
+    this.state = state;
+  }
+
+  public static JavaCompilerTool create(CfVm jdk, TemporaryFolder temp) {
+    assert temp != null;
+    return create(jdk, new TestState(temp));
+  }
+
+  public static JavaCompilerTool create(CfVm jdk, TestState state) {
+    assert state != null;
+    return new JavaCompilerTool(jdk, state);
+  }
+
+  public JavaCompilerTool addOptions(String... options) {
+    return addOptions(Arrays.asList(options));
+  }
+
+  public JavaCompilerTool addOptions(Collection<String> options) {
+    this.options.addAll(options);
+    return this;
+  }
+
+  public JavaCompilerTool addSourceFiles(Path... files) {
+    return addSourceFiles(Arrays.asList(files));
+  }
+
+  public JavaCompilerTool addSourceFiles(Collection<Path> files) {
+    sources.addAll(files);
+    return this;
+  }
+
+  public JavaCompilerTool addClasspathFiles(Path... files) {
+    return addClasspathFiles(Arrays.asList(files));
+  }
+
+  public JavaCompilerTool addClasspathFiles(Collection<Path> files) {
+    classpath.addAll(files);
+    return this;
+  }
+
+  /** Set the output. Must be to an existing directory or to an non-existing jar file. */
+  public JavaCompilerTool setOutputPath(Path file) {
+    assert (Files.exists(file) && Files.isDirectory(file))
+        || (!Files.exists(file) && FileUtils.isJarFile(file) && Files.exists(file.getParent()));
+    this.output = file;
+    return this;
+  }
+
+  private Path getOrCreateOutputPath() throws IOException {
+    return output != null ? output : state.getNewTempFolder().resolve("out.jar");
+  }
+
+  /** Compile and return the compilations process result object. */
+  public ProcessResult compileRaw() throws IOException {
+    assertNotNull("An output path must be specified prior to compilation.", output);
+    return compileInternal(output);
+  }
+
+  /**
+   * Compile asserting success and return the path to the output file.
+   *
+   * <p>If no output file has been set, the output is compiled to a zip archive in a temporary
+   * directory.
+   */
+  public Path compile() throws IOException {
+    Path output = getOrCreateOutputPath();
+    ProcessResult result = compileInternal(output);
+    assertEquals(result.toString(), result.exitCode, 0);
+    return output;
+  }
+
+  private ProcessResult compileInternal(Path output) throws IOException {
+    Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(ToolHelper.getJavaExecutable(jdk) + "c");
+    cmdline.addAll(options);
+    if (!classpath.isEmpty()) {
+      cmdline.add("-cp");
+      cmdline.add(
+          classpath.stream()
+              .map(Path::toString)
+              .collect(Collectors.joining(isWindows() ? ";" : ":")));
+    }
+    cmdline.add("-d");
+    cmdline.add(outdir.toString());
+    for (Path source : sources) {
+      cmdline.add(source.toString());
+    }
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    ProcessResult javacResult = ToolHelper.runProcess(builder);
+    if (FileUtils.isJarFile(output)) {
+      assert !outdir.equals(output);
+      ZipUtils.zip(output, outdir);
+    }
+    return javacResult;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
new file mode 100644
index 0000000..d6f38af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+public class JdkClassFileProviderTest extends TestBase implements Opcodes {
+
+  @Test
+  public void testInvalid8RuntimeClassPath() throws Exception {
+    Path path = temp.newFolder().toPath();
+    try {
+      JdkClassFileProvider.fromJdkHome(path);
+      fail("Not supposed to succeed");
+    } catch (IOException e) {
+      assertThat(e.toString(), containsString(path.toString()));
+      assertThat(e.toString(), containsString("does not look like a Java home"));
+    }
+  }
+
+  @Test
+  public void testJdk8JavHome() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+    assertJavaLangObject(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void testJdk8RuntimeClassPath() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJavaRuntimeJar(
+            ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8)
+                .resolve("jre")
+                .resolve("lib")
+                .resolve("rt.jar"));
+    assertJavaLangObject(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void testJdk8SystemModules() throws Exception {
+    try {
+      JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+      fail("Not supposed to succeed");
+    } catch (NoSuchFileException e) {
+      assertThat(e.toString(), containsString("lib/jrt-fs.jar"));
+    }
+  }
+
+  @Test
+  public void testJdk9JavaHome() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+    assertJavaLangObject(provider);
+    assertJavaUtilConcurrentFlowSubscriber(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void testJdk9SystemModules() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+    assertJavaLangObject(provider);
+    assertJavaUtilConcurrentFlowSubscriber(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void testJdk11JavaHome() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+    assertJavaLangObject(provider);
+    assertJavaUtilConcurrentFlowSubscriber(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  @Test
+  public void testJdk11SystemModules() throws Exception {
+    ClassFileResourceProvider provider =
+        JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+    assertJavaLangObject(provider);
+    assertJavaUtilConcurrentFlowSubscriber(provider);
+    assert provider instanceof AutoCloseable;
+    ((AutoCloseable) provider).close();
+  }
+
+  private void assertJavaLangObject(ClassFileResourceProvider provider) throws Exception {
+    assertTrue(provider.getClassDescriptors().contains("Ljava/lang/Object;"));
+    assertTrue(
+        ByteStreams.toByteArray(provider.getProgramResource("Ljava/lang/Object;").getByteStream())
+                .length
+            > 0);
+  }
+
+  private void assertJavaUtilConcurrentFlowSubscriber(ClassFileResourceProvider provider)
+      throws Exception {
+    assertTrue(provider.getClassDescriptors().contains("Ljava/util/concurrent/Flow$Subscriber;"));
+    assertTrue(
+        ByteStreams.toByteArray(
+                    provider
+                        .getProgramResource("Ljava/util/concurrent/Flow$Subscriber;")
+                        .getByteStream())
+                .length
+            > 0);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java b/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
deleted file mode 100644
index 7fa1ba1..0000000
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-public class KotlinCompilerTestBuilder
-    extends TestCompilerBuilder<
-        R8Command,
-        Builder,
-        KotlinTestCompileResult,
-        KotlinTestRunResult,
-        KotlinCompilerTestBuilder> {
-
-  private List<Path> ktPaths;
-  private List<Path> classPaths;
-
-  private KotlinCompilerTestBuilder(TestState state, Builder builder) {
-    super(state, builder, Backend.CF);
-  }
-
-  public static KotlinCompilerTestBuilder create(TestState state) {
-    return new KotlinCompilerTestBuilder(state, R8Command.builder());
-  }
-
-  @Override
-  KotlinCompilerTestBuilder self() {
-    return this;
-  }
-
-  @Override
-  KotlinTestCompileResult internalCompile(
-      Builder builder,
-      Consumer<InternalOptions> optionsConsumer,
-      Supplier<AndroidApp> app)
-      throws CompilationFailedException {
-    try {
-      Path outputFolder = getState().getNewTempFolder();
-      Path outputJar = outputFolder.resolve("output.jar");
-      ProcessResult processResult =
-          ToolHelper.runKotlinc(
-              null,
-              classPaths,
-              outputJar,
-              null, // extra options
-              ktPaths == null ? ToolHelper.EMPTY_PATH : ktPaths.toArray(ToolHelper.EMPTY_PATH)
-          );
-      return new KotlinTestCompileResult(getState(), outputJar, processResult);
-    } catch (IOException e) {
-      throw new CompilationFailedException(e);
-    }
-  }
-
-  @Override
-  public KotlinCompilerTestBuilder addClasspathClasses(Collection<Class<?>> classes) {
-    throw new Unimplemented("No support for adding classpath classes directly");
-  }
-
-  @Override
-  public KotlinCompilerTestBuilder addClasspathFiles(Collection<Path> files) {
-    if (classPaths == null) {
-      classPaths = new ArrayList<>();
-    }
-    classPaths.addAll(files);
-    return self();
-  }
-
-  public KotlinCompilerTestBuilder addSourceFiles(Path... files) {
-    if (ktPaths == null) {
-      ktPaths = new ArrayList<>();
-    }
-    ktPaths.addAll(Arrays.asList(files));
-    return self();
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
new file mode 100644
index 0000000..e66a3de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -0,0 +1,124 @@
+// 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 com.android.tools.r8.ToolHelper.KT_COMPILER;
+import static com.android.tools.r8.ToolHelper.KT_PRELOADER;
+import static com.android.tools.r8.ToolHelper.isWindows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.rules.TemporaryFolder;
+
+public class KotlinCompilerTool {
+
+  private final CfRuntime jdk;
+  private final TestState state;
+  private final List<Path> sources = new ArrayList<>();
+  private final List<Path> classpath = new ArrayList<>();
+  private Path output = null;
+
+  private KotlinCompilerTool(CfRuntime jdk, TestState state) {
+    this.jdk = jdk;
+    this.state = state;
+  }
+
+  public static KotlinCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
+    return create(jdk, new TestState(temp));
+  }
+
+  public static KotlinCompilerTool create(CfRuntime jdk, TestState state) {
+    return new KotlinCompilerTool(jdk, state);
+  }
+
+  public KotlinCompilerTool addSourceFiles(Path files) {
+    return addSourceFiles(Arrays.asList(files));
+  }
+
+  public KotlinCompilerTool addSourceFiles(Collection<Path> files) {
+    sources.addAll(files);
+    return this;
+  }
+
+  public KotlinCompilerTool addClasspathFiles(Path... files) {
+    return addClasspathFiles(Arrays.asList(files));
+  }
+
+  public KotlinCompilerTool addClasspathFiles(Collection<Path> files) {
+    classpath.addAll(files);
+    return this;
+  }
+
+  public KotlinCompilerTool setOutputPath(Path file) {
+    assertTrue("Output path must be an existing directory or a non-existing jar file",
+        (!Files.exists(file) && FileUtils.isJarFile(file) && Files.exists(file.getParent()))
+            || (Files.exists(file) && Files.isDirectory(file)));
+    this.output = file;
+    return this;
+  }
+
+  private Path getOrCreateOutputPath() throws IOException {
+    return output != null
+        ? output
+        : state.getNewTempFolder().resolve("out.jar");
+  }
+
+  /** Compile and return the compilations process result object. */
+  public ProcessResult compileRaw() throws IOException {
+    assertNotNull("An output path must be specified prior to compilation.", output);
+    return compileInternal(output);
+  }
+
+  /** Compile asserting success and return the output path. */
+  public Path compile() throws IOException {
+    Path output = getOrCreateOutputPath();
+    ProcessResult result = compileInternal(output);
+    assertEquals(result.toString(), result.exitCode, 0);
+    return output;
+  }
+
+  private ProcessResult compileInternal(Path output) throws IOException {
+    Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(ToolHelper.getJavaExecutable(jdk.getVm()));
+    cmdline.add("-jar");
+    cmdline.add(KT_PRELOADER);
+    cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
+    cmdline.add("-cp");
+    cmdline.add(KT_COMPILER);
+    cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+    for (Path source : sources) {
+      cmdline.add(source.toString());
+    }
+    cmdline.add("-d");
+    cmdline.add(output.toString());
+    if (!classpath.isEmpty()) {
+      cmdline.add("-cp");
+      cmdline.add(classpath
+          .stream()
+          .map(Path::toString)
+          .collect(Collectors.joining(isWindows() ? ";" : ":")));
+    }
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    ProcessResult kotlincResult = ToolHelper.runProcess(builder);
+    if (FileUtils.isJarFile(output)) {
+      assert !outdir.equals(output);
+      ZipUtils.zip(output, outdir);
+    }
+    return kotlincResult;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java b/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
deleted file mode 100644
index 550e72c..0000000
--- a/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-import java.nio.file.Path;
-
-public class KotlinTestCompileResult
-    extends TestCompileResult<KotlinTestCompileResult, KotlinTestRunResult> {
-
-  private final Path outputJar;
-  private final ProcessResult processResult;
-
-  KotlinTestCompileResult(TestState state, Path outputJar, ProcessResult processResult) {
-    super(state, AndroidApp.builder().addProgramFile(outputJar).build(), OutputMode.ClassFile);
-    this.outputJar = outputJar;
-    this.processResult = processResult;
-  }
-
-  public Path outputJar() {
-    return outputJar;
-  }
-
-  public int exitCode() {
-    return processResult.exitCode;
-  }
-
-  public String stdout() {
-    return processResult.stdout;
-  }
-
-  public String stderr() {
-    return processResult.stderr;
-  }
-
-  @Override
-  public KotlinTestCompileResult self() {
-    return this;
-  }
-
-  @Override
-  public TestDiagnosticMessages getDiagnosticMessages() {
-    throw new UnsupportedOperationException("No diagnostics messages from kotlinc");
-  }
-
-  @Override
-  public KotlinTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new KotlinTestRunResult(app, runtime, result);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestRunResult.java b/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
deleted file mode 100644
index a4e413c..0000000
--- a/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-
-public class KotlinTestRunResult extends TestRunResult<KotlinTestRunResult> {
-
-  KotlinTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
-    super(app, runtime, result);
-  }
-
-  @Override
-  protected KotlinTestRunResult self() {
-    return this;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1fc315b..1d3ce80 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -11,6 +11,8 @@
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -26,6 +28,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
+import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -111,10 +114,6 @@
     return JvmTestBuilder.create(new TestState(temp));
   }
 
-  public static KotlinCompilerTestBuilder testForKotlin(TemporaryFolder temp) {
-    return KotlinCompilerTestBuilder.create(new TestState(temp));
-  }
-
   public static ProguardTestBuilder testForProguard(TemporaryFolder temp) {
     return ProguardTestBuilder.create(new TestState(temp));
   }
@@ -151,10 +150,6 @@
     return testForJvm(temp);
   }
 
-  public KotlinCompilerTestBuilder testForKotlin() {
-    return testForKotlin(temp);
-  }
-
   public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(
       TestRuntime runtime, AndroidApiLevel apiLevel) {
     if (runtime.isCf()) {
@@ -179,6 +174,26 @@
     return testForMainDexListGenerator(temp);
   }
 
+  public JavaCompilerTool javac(CfRuntime jdk) {
+    return JavaCompilerTool.create(jdk.getVm(), temp);
+  }
+
+  public static JavaCompilerTool javac(CfVm jdk, TemporaryFolder temp) {
+    return JavaCompilerTool.create(jdk, temp);
+  }
+
+  public KotlinCompilerTool kotlinc(CfRuntime jdk) {
+    return KotlinCompilerTool.create(jdk, temp);
+  }
+
+  public static KotlinCompilerTool kotlinc(CfRuntime jdk, TemporaryFolder temp) {
+    return KotlinCompilerTool.create(jdk, temp);
+  }
+
+  public static ClassFileTransformer transformer(Class<?> clazz) throws IOException {
+    return ClassFileTransformer.create(clazz);
+  }
+
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
   // Actually running r8.jar in a forked process.
@@ -1192,6 +1207,7 @@
     }
   }
 
+  @Deprecated
   public static Path runtimeJar(Backend backend) {
     if (backend == Backend.DEX) {
       return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 4c17ac7..d834ebc 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -69,6 +69,10 @@
       return this.ordinal() <= other.ordinal();
     }
 
+    public boolean hasModularRuntime() {
+      return this != JDK8;
+    }
+
     @Override
     public String toString() {
       return name;
@@ -109,10 +113,12 @@
     return CHECKED_IN_JDKS.containsKey(jdk);
   }
 
-  public static Path getCheckInJDKPathFor(CfVm jdk) {
-    return Paths.get("third_party", "openjdk")
-        .resolve(CHECKED_IN_JDKS.get(jdk))
-        .resolve(Paths.get("bin", "java"));
+  public static Path getCheckedInJDKHome(CfVm jdk) {
+    return Paths.get("third_party", "openjdk").resolve(CHECKED_IN_JDKS.get(jdk));
+  }
+
+  public static Path getCheckedInJDKPathFor(CfVm jdk) {
+    return getCheckedInJDKHome(jdk).resolve(Paths.get("bin", "java"));
   }
 
   public static TestRuntime getDefaultJavaRuntime() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index a84064a..0445166 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1266,53 +1266,6 @@
     return runJava(runtime, ImmutableList.of(), classpath, args);
   }
 
-  public static ProcessResult runJavac(
-      CfVm runtime, List<Path> classPath, Path directoryToCompileInto, Path... classesToCompile)
-      throws IOException {
-    return runJavac(runtime, classPath, directoryToCompileInto, null, classesToCompile);
-  }
-
-  public static ProcessResult runJavac(
-      CfVm runtime,
-      List<Path> classPath,
-      Path directoryToCompileInto,
-      List<String> extraOptions,
-      Path... classesToCompile)
-      throws IOException {
-    String[] strings = Arrays.stream(classesToCompile).map(Path::toString).toArray(String[]::new);
-    List<String> cp = classPath == null ? null : classPath.stream().map(Path::toString).collect(
-        Collectors.toList());
-    return runJavac(runtime, cp, directoryToCompileInto.toString(), extraOptions, strings);
-  }
-
-  public static ProcessResult runJavac(
-      CfVm runtime,
-      List<String> classPath,
-      String directoryToCompileInto,
-      List<String> extraOptions,
-      String... classesToCompile)
-      throws IOException {
-    List<String> cmdline =
-        new ArrayList<>(
-            Collections.singletonList(TestRuntime.getCheckInJDKPathFor(runtime).toString() + "c"));
-    if (extraOptions != null) {
-      cmdline.addAll(extraOptions);
-    }
-    if (classPath != null) {
-      cmdline.add("-cp");
-      if (isWindows()) {
-        cmdline.add(String.join(";", classPath));
-      } else {
-        cmdline.add(String.join(":", classPath));
-      }
-    }
-    cmdline.add("-d");
-    cmdline.add(directoryToCompileInto);
-    Collections.addAll(cmdline, classesToCompile);
-    ProcessBuilder builder = new ProcessBuilder(cmdline);
-    return ToolHelper.runProcess(builder);
-  }
-
   public static ProcessResult runKotlinc(
       CfVm runtime,
       List<Path> classPaths,
@@ -1320,8 +1273,7 @@
       List<String> extraOptions,
       Path... filesToCompile)
       throws IOException {
-    String jvm = runtime == null ? getSystemJavaExecutable() : getJavaExecutable(runtime);
-    List<String> cmdline = new ArrayList<String>(Arrays.asList(jvm));
+    List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable(runtime)));
     cmdline.add("-jar");
     cmdline.add(KT_PRELOADER);
     cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
@@ -1466,7 +1418,7 @@
 
   public static String getJavaExecutable(CfVm runtime) {
     if (TestRuntime.isCheckedInJDK(runtime)) {
-      return TestRuntime.getCheckInJDKPathFor(runtime).toString();
+      return TestRuntime.getCheckedInJDKPathFor(runtime).toString();
     } else {
       // TODO(b/127785410): Always assume a non-null runtime.
       assert runtime == null || TestParametersBuilder.isSystemJdk(runtime);
@@ -1474,6 +1426,10 @@
     }
   }
 
+  public static Path getJavaHome(CfVm runtime) {
+    return TestRuntime.getCheckedInJDKHome(runtime);
+  }
+
   public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
     return runArtProcessRaw(builder);
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 4ff1850..9fe3bb4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -431,9 +432,7 @@
     ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
     assertEquals(0, javaResult.exitCode);
 
-    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
-        internalOptions -> internalOptions.enableInlining = false);
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure);
 
     // Run processed (output) program on ART
     ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
@@ -442,4 +441,13 @@
 
     return processedApp;
   }
+
+  private void configure(InternalOptions options) {
+    // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
+    // bothers what the tests want to check, such as exact instructions in the body that include
+    // invocation kinds, like virtual call to a bridge.
+    options.enablePropagationOfConstantsAtCallSites = false;
+    // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+    options.enableInlining = false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
index 9a08b11..e176fca 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
@@ -82,8 +82,6 @@
             if (disableL8AnnotationRemovalForTesting) {
               options.testing.disableL8AnnotationRemoval = true;
             }
-            // Temporary hack so that keeping an interface keeps the superinterfaces.
-            options.testing.keepInheritedInterfaceMethods = true;
           });
       if (!disableL8AnnotationRemovalForTesting) {
         assertTrue(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
new file mode 100644
index 0000000..5b5b420
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+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 CustomCollectionSuperCallsTest extends CoreLibDesugarTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public CustomCollectionSuperCallsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionSuperCallsD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    D8TestRunResult d8TestRunResult =
+        testForD8()
+            .addInnerClasses(CustomCollectionSuperCallsTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess();
+    assertLines2By2Correct(d8TestRunResult.getStdOut());
+  }
+
+  static class Executor {
+
+    // ArrayList spliterator is Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED.
+
+    public static void main(String[] args) {
+      rawTypes();
+      inheritedTypes();
+    }
+
+    public static void rawTypes() {
+      Spliterator<String> stringSpliterator;
+
+      stringSpliterator = new MyArrayListOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+
+      stringSpliterator = new MyArrayListOverrideSubclass().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(false);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(true);
+
+      stringSpliterator = new MyArrayListNoOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+
+      stringSpliterator = new MyArrayListSubclassNoOverride().superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+    }
+
+    public static void inheritedTypes() {
+      Spliterator<String> stringSpliterator;
+
+      stringSpliterator =
+          ((MyArrayListOverride) new MyArrayListOverrideSubclass()).superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(false);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(true);
+
+      stringSpliterator =
+          ((MyArrayListNoOverride) new MyArrayListSubclassNoOverride()).superSpliterator();
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println(true);
+      System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println(false);
+    }
+  }
+
+  static class MyArrayListOverride extends ArrayList<String> {
+
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE);
+    }
+
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+  }
+
+  static class MyArrayListOverrideSubclass extends MyArrayListOverride {
+
+    @Override
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+
+    // Unused, but prove the super invoke won't resolve into it.
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+    }
+  }
+
+  static class MyArrayListNoOverride extends ArrayList<String> {
+
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+  }
+
+  static class MyArrayListSubclassNoOverride extends MyArrayListNoOverride {
+    public Spliterator<String> superSpliterator() {
+      return super.spliterator();
+    }
+
+    // Unused, but prove the super invoke won't resolve into it.
+    @Override
+    public Spliterator<String> spliterator() {
+      return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index afae3b0..e9f221d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import org.junit.Test;
@@ -43,15 +44,22 @@
     assertLines2By2Correct(stdOut);
   }
 
+  @SuppressWarnings("WeakerAccess")
   static class Executor {
-    @SuppressWarnings("RedundantOperationOnEmptyContainer")
-    public static void main(String[] args) {
-      Spliterator<String> spliterator;
 
+    public static void main(String[] args) {
       // Spliterator of Set is only distinct.
       // Spliterator of LinkedHashSet is distinct and ordered.
       // Spliterator of CustomLinkedHashSetOverride is distinct, ordered and immutable.
       // If an incorrect method is found, characteristics are incorrect.
+      rawTypes();
+      linkedHashSetType();
+      setType();
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void rawTypes() {
+      Spliterator<String> spliterator;
 
       spliterator = new LinkedHashSet<String>().spliterator();
       System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
@@ -92,6 +100,70 @@
       System.out.println("true");
       System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
       System.out.println("true");
+
+      spliterator = new SubclassNoOverride<String>().superSpliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void linkedHashSetType() {
+      System.out.println("-");
+      System.out.println("-");
+      Spliterator<String> spliterator;
+
+      spliterator =
+          ((LinkedHashSet<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("true");
+
+      spliterator =
+          ((LinkedHashSet<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+    }
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void setType() {
+      System.out.println("-");
+      System.out.println("-");
+      Spliterator<String> spliterator;
+
+      spliterator = ((Set<String>) new LinkedHashSet<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
+
+      spliterator = ((Set<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("true");
+
+      spliterator = ((Set<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+      System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+      System.out.println("true");
+      System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+      System.out.println("false");
     }
   }
 
@@ -109,6 +181,7 @@
   }
 
   static class SubclassOverride<E> extends CustomLinkedHashSetOverride<E> {
+
     @Override
     public Spliterator<E> superSpliterator() {
       return super.spliterator();
@@ -117,4 +190,11 @@
 
   @SuppressWarnings("WeakerAccess")
   static class CustomLinkedHashSetNoOverride<E> extends LinkedHashSet<E> {}
+
+  static class SubclassNoOverride<E> extends CustomLinkedHashSetNoOverride<E> {
+
+    public Spliterator<E> superSpliterator() {
+      return super.spliterator();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index f2889ca..59db52d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -159,7 +159,7 @@
     assertInvokeStaticMatching(invokes, 2, "Collection$-EL;->stream");
     assertInvokeInterfaceMatching(invokes, 3, "Set;->iterator");
     assertInvokeStaticMatching(invokes, 4, "Collection$-EL;->stream");
-    assertInvokeStaticMatching(invokes, 5, "DesugarLinkedHashSet;->spliterator");
+    assertInvokeStaticMatching(invokes, 5, "Set$-EL;->spliterator");
     assertInvokeInterfaceMatching(invokes, 9, "Iterator;->remove");
     assertInvokeStaticMatching(invokes, 10, "DesugarArrays;->spliterator");
     assertInvokeStaticMatching(invokes, 11, "DesugarArrays;->spliterator");
@@ -213,9 +213,6 @@
             "    j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
             "}",
             "-keep class j$.util.stream.IntStream",
-            "-keep class j$.util.DesugarLinkedHashSet {",
-            "    j$.util.Spliterator spliterator(java.util.LinkedHashSet);",
-            "}",
             "-keep class j$.util.stream.Stream",
             "-keep class j$.util.Spliterator",
             "-keep class j$.util.function.ToIntFunction { *; }");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index bc6054a..b20a716 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -6,9 +6,12 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import java.time.Instant;
+import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntUnaryOperator;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -65,6 +68,7 @@
 
     public static void main(String[] args) {
       java.sql.Date date = new java.sql.Date(123456789);
+      // The following one is not working on JVMs, but works on Android...
       System.out.println(date.toInstant());
       System.out.println("1970-01-02T10:17:36.789Z");
 
@@ -82,8 +86,12 @@
       MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
       System.out.println(myCalN.toZonedDateTime());
       System.out.println("1990-03-22T00:00Z[GMT]");
+      System.out.println(myCalN.superToZonedDateTime());
+      System.out.println("1990-03-22T00:00Z[GMT]");
       System.out.println(myCalN.toInstant());
       System.out.println("1990-03-22T00:00:00Z");
+      System.out.println(myCalN.superToInstant());
+      System.out.println("1990-03-22T00:00:00Z");
 
       // TODO(b/142846107): Enable overrides of retarget core members.
       // MyDateOverride myDate = new MyDateOverride(123456789);
@@ -93,16 +101,16 @@
       MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
       System.out.println(myDateN.toInstant());
       System.out.println("1970-01-02T10:17:36.789Z");
+      System.out.println(myDateN.superToInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
 
       MyAtomicInteger myAtomicInteger = new MyAtomicInteger(42);
       System.out.println(myAtomicInteger.getAndUpdate(x -> x + 1));
       System.out.println("42");
-      System.out.println(myAtomicInteger.getAndUpdate(x -> x + 5));
+      System.out.println(myAtomicInteger.superGetAndUpdate(x -> x + 2));
       System.out.println("43");
       System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
-      System.out.println("148");
-      System.out.println(myAtomicInteger.updateAndGet(x -> x + 500));
-      System.out.println("648");
+      System.out.println("145");
     }
   }
 
@@ -124,6 +132,14 @@
     public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
       super(year, month, dayOfMonth);
     }
+
+    public Instant superToInstant() {
+      return super.toInstant();
+    }
+
+    public ZonedDateTime superToZonedDateTime() {
+      return super.toZonedDateTime();
+    }
   }
 
   // static class MyDateOverride extends Date {
@@ -143,6 +159,10 @@
     public MyDateNoOverride(long date) {
       super(date);
     }
+
+    public Instant superToInstant() {
+      return super.toInstant();
+    }
   }
 
   static class MyAtomicInteger extends AtomicInteger {
@@ -150,5 +170,9 @@
     public MyAtomicInteger(int initialValue) {
       super(initialValue);
     }
+
+    public int superGetAndUpdate(IntUnaryOperator op) {
+      return super.getAndUpdate(op);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java
deleted file mode 100644
index 752c2e6..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-
-import static org.hamcrest.CoreMatchers.startsWith;
-
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
-import java.util.LongSummaryStatistics;
-import org.junit.Test;
-
-public class APIConversionFinalWarningTest extends APIConversionTestBase {
-
-  @Test
-  public void testFinalMethod() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
-    testForD8()
-        .setMinApi(AndroidApiLevel.B)
-        .addProgramClasses(Executor.class)
-        .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .assertInfoMessageThatMatches(
-            startsWith(
-                "Desugared library API conversion: cannot wrap final methods"
-                    + " [java.util.LongSummaryStatistics"))
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines(
-                "Unsupported conversion for java.util.LongSummaryStatistics. See compilation time"
-                    + " infos for more details."));
-  }
-
-  static class Executor {
-
-    public static void main(String[] args) {
-      LongSummaryStatistics statistics = new LongSummaryStatistics();
-      statistics.accept(3L);
-      try {
-        makeCall(statistics);
-      } catch (RuntimeException e) {
-        System.out.println(e.getMessage());
-      }
-    }
-
-    @SuppressWarnings("ResultOfMethodCallIgnored")
-    static void makeCall(LongSummaryStatistics statistics) {
-      CustomLibClass.call(statistics);
-    }
-  }
-
-  // This class will be put at compilation time as library and on the runtime class path.
-  // This class is convenient for easy testing. Each method plays the role of methods in the
-  // platform APIs for which argument/return values need conversion.
-  static class CustomLibClass {
-
-    public static long call(LongSummaryStatistics stats) {
-      return stats.getMax();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
index 6825616..7d816e5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -25,26 +25,26 @@
         "JDK8 javac is required to avoid dealing with modules and JDK8 is not checked-in on"
             + " windows",
         !ToolHelper.isWindows());
-    File conversionFolder = temp.newFolder("conversions");
-    File stubsFolder = temp.newFolder("stubs");
+
+    CfRuntime runtime = new CfRuntime(CfVm.JDK8);
+    Path conversionFolder = temp.newFolder("conversions").toPath();
 
     // Compile the stubs to be able to compile the conversions.
-    ToolHelper.runJavac(
-        CfVm.JDK8,
-        null,
-        stubsFolder.toPath(),
-        getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs"), "java"));
+    Path stubsJar =
+        javac(runtime)
+            .addSourceFiles(
+                getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs"), "java"))
+            .compile();
 
     // Compile the conversions using the stubs.
-    ArrayList<Path> classPath = new ArrayList<>();
-    classPath.add(stubsFolder.toPath());
-    ToolHelper.runJavac(
-        CfVm.JDK8,
-        classPath,
-        conversionFolder.toPath(),
-        getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("conversions"), "java"));
+    javac(runtime)
+        .setOutputPath(conversionFolder)
+        .addClasspathFiles(stubsJar)
+        .addSourceFiles(
+            getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("conversions"), "java"))
+        .compile();
 
-    Path[] classes = getAllFilesWithSuffixInDirectory(conversionFolder.toPath(), "class");
+    Path[] classes = getAllFilesWithSuffixInDirectory(conversionFolder, "class");
     assert classes.length > 0
         : "Something went wrong during compilation, check the runJavac return value for debugging.";
     return classes;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
new file mode 100644
index 0000000..3e65742
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.DoubleSummaryStatistics;
+import java.util.IntSummaryStatistics;
+import java.util.LongSummaryStatistics;
+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 SummaryStatisticsConversionTest extends APIConversionTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // Below 7 XXXSummaryStatistics are not present and conversions are pointless.
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public SummaryStatisticsConversionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testStats() throws Exception {
+    Path customLib =
+        testForD8()
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(CustomLibClass.class)
+            .compile()
+            .writeToZip();
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, parameters.getApiLevel())
+        .addRunClasspathFiles(customLib)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0"));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      // The realTest represents scenario applicable in Android apps, subsequent tests use
+      // mocked CustomLib to ensure all cases are correct.
+      realTest();
+      longTest();
+      intTest();
+      doubleTest();
+    }
+
+    private static void realTest() {
+      System.out.println("foo".subSequence(0, 2).codePoints().summaryStatistics().getCount());
+    }
+
+    public static void longTest() {
+      long[] longs = new long[1];
+      longs[0] = 42L;
+      LongSummaryStatistics mix =
+          CustomLibClass.mix(Arrays.stream(longs).summaryStatistics(), new LongSummaryStatistics());
+      System.out.println(mix.getCount());
+      System.out.println(mix.getMin());
+      System.out.println(mix.getMax());
+      System.out.println(mix.getSum());
+    }
+
+    public static void intTest() {
+      int[] ints = new int[1];
+      ints[0] = 42;
+      IntSummaryStatistics mix =
+          CustomLibClass.mix(Arrays.stream(ints).summaryStatistics(), new IntSummaryStatistics());
+      System.out.println(mix.getCount());
+      System.out.println(mix.getMin());
+      System.out.println(mix.getMax());
+      System.out.println(mix.getSum());
+    }
+
+    public static void doubleTest() {
+      double[] doubles = new double[1];
+      doubles[0] = 42L;
+      DoubleSummaryStatistics mix =
+          CustomLibClass.mix(
+              Arrays.stream(doubles).summaryStatistics(), new DoubleSummaryStatistics());
+      System.out.println(mix.getCount());
+      System.out.println(mix.getMin());
+      System.out.println(mix.getMax());
+      System.out.println(mix.getSum());
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    public static LongSummaryStatistics mix(
+        LongSummaryStatistics stats1, LongSummaryStatistics stats2) {
+      return stats1;
+    }
+
+    public static IntSummaryStatistics mix(
+        IntSummaryStatistics stats1, IntSummaryStatistics stats2) {
+      return stats1;
+    }
+
+    public static DoubleSummaryStatistics mix(
+        DoubleSummaryStatistics stats1, DoubleSummaryStatistics stats2) {
+      return stats1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
index ab1a617..6e791b6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
 {
   "configuration_format_version": 2,
-  "version": "0.7.0",
+  "version": "0.8.0",
   "required_compilation_api_level": 26,
   "library_flags": [
     {
@@ -21,6 +21,9 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "j$.util.Optional": "java.util.Optional",
+        "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics",
+        "j$.util.IntSummaryStatistics": "java.util.IntSummaryStatistics",
+        "j$.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatistics",
         "java.util.stream.": "j$.util.stream.",
         "java.util.function.": "j$.util.function.",
         "java.util.Comparators": "j$.util.Comparators",
@@ -134,7 +137,10 @@
         "java.util.Optional": "j$.util.OptionalConversions",
         "java.util.OptionalDouble": "j$.util.OptionalConversions",
         "java.util.OptionalInt": "j$.util.OptionalConversions",
-        "java.util.OptionalLong": "j$.util.OptionalConversions"
+        "java.util.OptionalLong": "j$.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
@@ -143,6 +149,9 @@
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
     "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }",
-    "-keeppackagenames j$"
+    "-keeppackagenames j$",
+    "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
+    "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
+    "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }"
   ]
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
index ff60941..db0a09a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -62,11 +63,14 @@
 
   @BeforeClass
   public static void compileAtomicClasses() throws Exception {
-    ToolHelper.runJavac(
-        CfVm.JDK11,
-        Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
-        ATOMIC_COMPILED_TESTS_FOLDER,
-        ATOMIC_TESTS_FILES);
+    File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
+    assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addClasspathFiles(
+            Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+        .addSourceFiles(ATOMIC_TESTS_FILES)
+        .setOutputPath(ATOMIC_COMPILED_TESTS_FOLDER)
+        .compile();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
index b983d33..71458f0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -73,27 +74,35 @@
   }
 
   @BeforeClass
-  public static void compileAtomicClasses() throws Exception {
-    ToolHelper.runJavac(
-        CfVm.JDK11,
-        Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
-        CONCURRENT_COMPILED_TESTS_FOLDER,
-        getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION));
-    List<Path> concHashFilesAndDependencies = new ArrayList<>();
-    Collections.addAll(
-        concHashFilesAndDependencies,
-        getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
-    Collections.addAll(concHashFilesAndDependencies, SUPPORT_LIBS);
-    ToolHelper.runJavac(
-        CfVm.JDK11,
-        Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
-        CONCURRENT_HASH_COMPILED_TESTS_FOLDER,
-        concHashFilesAndDependencies.toArray(new Path[0]));
+  public static void compileConcurrentClasses() throws Exception {
+    File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
+    assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addClasspathFiles(
+            Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+        .addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
+        .setOutputPath(CONCURRENT_COMPILED_TESTS_FOLDER)
+        .compile();
     CONCURRENT_COMPILED_TESTS_FILES =
         getAllFilesWithSuffixInDirectory(CONCURRENT_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+    assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
+
+    List<Path> concurrentHashFilesAndDependencies = new ArrayList<>();
+    Collections.addAll(
+        concurrentHashFilesAndDependencies,
+        getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
+    Collections.addAll(concurrentHashFilesAndDependencies, SUPPORT_LIBS);
+    Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
+    File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
+    assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addClasspathFiles(
+            Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+        .addSourceFiles(classesToCompile)
+        .setOutputPath(CONCURRENT_HASH_COMPILED_TESTS_FOLDER)
+        .compile();
     CONCURRENT_HASH_COMPILED_TESTS_FILES =
         getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
-    assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
     assert CONCURRENT_HASH_COMPILED_TESTS_FILES.length > 0;
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
index f9ee2cc..9a0b63b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
@@ -72,27 +72,46 @@
 
   @BeforeClass
   public static void compileJavaBaseExtensions() throws Exception {
-    if (!new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString()).exists()) {
-      List<String> options =
-          Arrays.asList(
-              "--add-reads",
-              "java.base=ALL-UNNAMED",
-              "--patch-module",
-              "java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
-      ToolHelper.runJavac(
-          CfVm.JDK11,
-          Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
-          JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR,
-          options,
-          getJavaBaseExtensionsFiles());
-    }
+    File extensionClassesDir = new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString());
+    assert extensionClassesDir.exists() || extensionClassesDir.mkdirs();
+    List<String> options =
+        Arrays.asList(
+            "--add-reads",
+            "java.base=ALL-UNNAMED",
+            "--patch-module",
+            "java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
+    javac(CfVm.JDK11, getStaticTemp())
+        .addOptions(options)
+        .addClasspathFiles(
+            Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+        .addSourceFiles(getJavaBaseExtensionsFiles())
+        .setOutputPath(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR)
+        .compile();
     JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES =
         getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR, CLASS_EXTENSION);
     assert JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES.length > 0;
   }
 
-  protected Path buildDesugaredLibraryWithJavaBaseExtension(AndroidApiLevel apiLevel) {
+  private String getTestNGKeepRules() {
+    // Keep data providers and their annotations.
+    return "-keepclasseswithmembers class * {\n"
+        + "    @org.testng.annotations.DataProvider <methods>;\n"
+        + "}\n"
+        + "-keepattributes *Annotation*\n"
+        // Do not even attempt to shrink testNG (unrelated to desugared lib shrinking goal).
+        + "-keep class org.testng.** { *; }\n"
+        // There are missing classes in testNG.
+        + "-dontwarn";
+  }
+
+  protected Path buildDesugaredLibraryWithJavaBaseExtension(
+      AndroidApiLevel apiLevel, String keepRules, boolean shrink) {
+    // there are missing classes from testNG.
+    keepRules = getTestNGKeepRules() + keepRules;
     return buildDesugaredLibrary(
-        apiLevel, "", false, ImmutableList.copyOf(JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES));
+        apiLevel,
+        keepRules,
+        shrink,
+        ImmutableList.copyOf(JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
index 9d84630..9f64237 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Assume;
@@ -65,9 +66,19 @@
 
   @BeforeClass
   public static void compileMathClasses() throws Exception {
-    ToolHelper.runJavac(CfVm.JDK11, null, JDK_11_MATH_TESTS_DIR, JDK_11_MATH_JAVA_FILES);
-    ToolHelper.runJavac(
-        CfVm.JDK11, null, JDK_11_STRICT_MATH_TESTS_DIR, JDK_11_STRICT_MATH_JAVA_FILES);
+    File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
+    assert mathClassesDir.exists() || mathClassesDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addSourceFiles(JDK_11_MATH_JAVA_FILES)
+        .setOutputPath(JDK_11_MATH_TESTS_DIR)
+        .compile();
+
+    File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
+    assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
+        .setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
+        .compile();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
index 39cfe0c..730ee1b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Assume;
@@ -52,11 +53,12 @@
 
   @BeforeClass
   public static void compileObjectsClass() throws Exception {
-    ToolHelper.runJavac(
-        CfVm.JDK11,
-        null,
-        JDK_11_OBJECTS_TESTS_DIR,
-        JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION));
+    File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
+    assert objectsDir.exists() || objectsDir.mkdirs();
+    javac(CfVm.JDK11, getStaticTemp())
+        .addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
+        .setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
+        .compile();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index adc0928..784d320 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -13,11 +13,11 @@
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.File;
 import java.nio.file.Path;
@@ -33,23 +33,28 @@
 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 Jdk11StreamTests extends Jdk11CoreLibTestBase {
 
   private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
     // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required and
     // present only in ART runtimes.
-    return getTestParameters()
-        .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
-        .withAllApiLevels()
-        .build();
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
   }
 
-  public Jdk11StreamTests(TestParameters parameters) {
+  public Jdk11StreamTests(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
@@ -65,80 +70,74 @@
     return files;
   }
 
-  private static String[] RUNNABLE_TESTS =
-      new String[]{
-          // Disabled because explicit cast done on a wrapped value.
-          //"org/openjdk/tests/java/util/SplittableRandomTest.java",
-
-          // Working tests
-          "org/openjdk/tests/java/util/MapTest.java",
-          "org/openjdk/tests/java/util/FillableStringTest.java",
-          "org/openjdk/tests/java/util/stream/ForEachOpTest.java",
-          "org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java",
-          "org/openjdk/tests/java/util/stream/GroupByOpTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/InfiniteStreamWithLimitOpTest.java",
-          "org/openjdk/tests/java/util/stream/PrimitiveAverageOpTest.java",
-          "org/openjdk/tests/java/util/stream/TeeOpTest.java",
-          "org/openjdk/tests/java/util/stream/MinMaxTest.java",
-          "org/openjdk/tests/java/util/stream/ConcatTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/CountLargeTest.java",
-          "org/openjdk/tests/java/util/stream/StreamParSeqTest.java",
-          "org/openjdk/tests/java/util/stream/ReduceByOpTest.java",
-          "org/openjdk/tests/java/util/stream/ConcatOpTest.java",
-          "org/openjdk/tests/java/util/stream/IntReduceTest.java",
-          "org/openjdk/tests/java/util/stream/SortedOpTest.java",
-          "org/openjdk/tests/java/util/stream/MatchOpTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/RangeTest.java",
-          "org/openjdk/tests/java/util/stream/IntSliceOpTest.java",
-          "org/openjdk/tests/java/util/stream/SequentialOpTest.java",
-          "org/openjdk/tests/java/util/stream/PrimitiveSumTest.java",
-          "org/openjdk/tests/java/util/stream/IterateTest.java",
-          "org/openjdk/tests/java/util/stream/ReduceTest.java",
-          "org/openjdk/tests/java/util/stream/IntUniqOpTest.java",
-
-          // J9 failure
-          "org/openjdk/tests/java/util/stream/SpliteratorTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/CollectorsTest.java",
-          "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
-          "org/openjdk/tests/java/util/stream/WhileOpTest.java",
-
-          // J9 security
-          "org/openjdk/tests/java/util/stream/CountTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/FlatMapOpTest.java",
-          "org/openjdk/tests/java/util/stream/StreamCloseTest.java",
-          "org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java",
-          "org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
-
-          // Foreach problem
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/StreamLinkTest.java",
-          "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
-          "org/openjdk/tests/java/util/stream/FindAnyOpTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/StreamBuilderTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/SliceOpTest.java",
-          "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
-          "org/openjdk/tests/java/util/stream/MapOpTest.java",
-          // Disabled because time to run > 1 min.
-          // "org/openjdk/tests/java/util/stream/ToArrayOpTest.java",
-
-          // J9 Random problem
-          "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
-          "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java"
+  private static String[] FAILING_RUNNABLE_TESTS =
+      new String[] {
+        // J9 failure.
+        "org/openjdk/tests/java/util/stream/SpliteratorTest.java",
+        "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
+        "org/openjdk/tests/java/util/stream/IterateTest.java",
+        "org/openjdk/tests/java/util/stream/WhileOpTest.java",
+        // forEach failure
+        "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
+        "org/openjdk/tests/java/util/stream/MapOpTest.java",
+        // Disabled because explicit cast done on a wrapped value.
+        // "org/openjdk/tests/java/util/SplittableRandomTest.java",
+        // Assertion error
+        "org/openjdk/tests/java/util/stream/StreamCloseTest.java",
+        "org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
+        "org/openjdk/tests/java/util/stream/CountTest.java",
+        // J9 Random problem
+        "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
+        "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java",
+        "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
+        "org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java"
       };
 
-  private static Map<String, String> getRunnableTests() {
+  // Disabled because time to run > 1 min for each test.
+  // Can be used for experimentation/testing purposes.
+  private static String[] LONG_RUNNING_TESTS =
+      new String[] {
+        "org/openjdk/tests/java/util/stream/InfiniteStreamWithLimitOpTest.java",
+        "org/openjdk/tests/java/util/stream/CountLargeTest.java",
+        "org/openjdk/tests/java/util/stream/RangeTest.java",
+        "org/openjdk/tests/java/util/stream/CollectorsTest.java",
+        "org/openjdk/tests/java/util/stream/FlatMapOpTest.java",
+        "org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java",
+        "org/openjdk/tests/java/util/stream/StreamLinkTest.java",
+        "org/openjdk/tests/java/util/stream/StreamBuilderTest.java",
+        "org/openjdk/tests/java/util/stream/SliceOpTest.java",
+        "org/openjdk/tests/java/util/stream/ToArrayOpTest.java"
+      };
+
+  private static String[] SUCCESSFUL_RUNNABLE_TESTS =
+      new String[] {
+        "org/openjdk/tests/java/util/MapTest.java",
+        "org/openjdk/tests/java/util/FillableStringTest.java",
+        "org/openjdk/tests/java/util/stream/ForEachOpTest.java",
+        "org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java",
+        "org/openjdk/tests/java/util/stream/GroupByOpTest.java",
+        "org/openjdk/tests/java/util/stream/PrimitiveAverageOpTest.java",
+        "org/openjdk/tests/java/util/stream/TeeOpTest.java",
+        "org/openjdk/tests/java/util/stream/MinMaxTest.java",
+        "org/openjdk/tests/java/util/stream/ConcatTest.java",
+        "org/openjdk/tests/java/util/stream/StreamParSeqTest.java",
+        "org/openjdk/tests/java/util/stream/ReduceByOpTest.java",
+        "org/openjdk/tests/java/util/stream/ConcatOpTest.java",
+        "org/openjdk/tests/java/util/stream/IntReduceTest.java",
+        "org/openjdk/tests/java/util/stream/SortedOpTest.java",
+        "org/openjdk/tests/java/util/stream/MatchOpTest.java",
+        "org/openjdk/tests/java/util/stream/IntSliceOpTest.java",
+        "org/openjdk/tests/java/util/stream/SequentialOpTest.java",
+        "org/openjdk/tests/java/util/stream/PrimitiveSumTest.java",
+        "org/openjdk/tests/java/util/stream/ReduceTest.java",
+        "org/openjdk/tests/java/util/stream/IntUniqOpTest.java",
+        "org/openjdk/tests/java/util/stream/FindAnyOpTest.java"
+      };
+
+  private static Map<String, String> getRunnableTests(String[] tests) {
     IdentityHashMap<String, String> pathToName = new IdentityHashMap<>();
     int javaExtSize = JAVA_EXTENSION.length();
-    for (String runnableTest : RUNNABLE_TESTS) {
+    for (String runnableTest : tests) {
       String nameWithoutJavaExt = runnableTest.substring(0, runnableTest.length() - javaExtSize);
       pathToName.put(
           JDK_11_STREAM_TEST_CLASSES_DIR + "/" + nameWithoutJavaExt + CLASS_EXTENSION,
@@ -165,91 +164,121 @@
 
   @BeforeClass
   public static void compileJdk11StreamTests() throws Exception {
-    if (!new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString()).exists()) {
-      List<String> options =
-          Arrays.asList(
-              "--add-reads",
-              "java.base=ALL-UNNAMED",
-              "--patch-module",
-              "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
-      ToolHelper.runJavac(
-          CfVm.JDK11,
-          Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
-          JDK_11_STREAM_TEST_CLASSES_DIR,
-          options,
-          getJdk11StreamTestFiles());
-    }
+    File streamClassesDir = new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString());
+    assert streamClassesDir.exists() || streamClassesDir.mkdirs();
+    List<String> options =
+        Arrays.asList(
+            "--add-reads",
+            "java.base=ALL-UNNAMED",
+            "--patch-module",
+            "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
+    javac(CfVm.JDK11, getStaticTemp())
+        .addOptions(options)
+        .addClasspathFiles(
+            Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+        .addSourceFiles(getJdk11StreamTestFiles())
+        .setOutputPath(JDK_11_STREAM_TEST_CLASSES_DIR)
+        .compile();
     JDK_11_STREAM_TEST_COMPILED_FILES =
         getAllFilesWithSuffixInDirectory(JDK_11_STREAM_TEST_CLASSES_DIR, CLASS_EXTENSION);
     assert JDK_11_STREAM_TEST_COMPILED_FILES.length > 0;
   }
 
-
   @Test
   public void testStream() throws Exception {
+    Assume.assumeFalse(
+        "getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
+        ToolHelper.isWindows());
     Assume.assumeTrue(
         "Requires Java base extensions, should add it when not desugaring",
         parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel());
-    // TODO(b/137876068): It seems to fail on windows because the method.
-    // getAllFilesWithSuffixInDirectory() finds different files on Windows (To be confirmed), so
-    // compilation is then different and raises an error.
-    Assume.assumeFalse(ToolHelper.isWindows());
-    Map<String, String> runnableTests = getRunnableTests();
-    String verbosity = "2";
+
+    D8TestCompileResult compileResult = compileStreamTestsToDex();
+    runSuccessfulTests(compileResult);
+    runFailingTests(compileResult);
+  }
+
+  private D8TestCompileResult compileStreamTestsToDex() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     List<Path> filesToCompile =
         Arrays.stream(JDK_11_STREAM_TEST_COMPILED_FILES)
             .filter(file -> !file.toString().contains("lang/invoke"))
             .collect(Collectors.toList());
-    D8TestCompileResult compileResult =
-        testForD8()
-            .addProgramFiles(filesToCompile)
-            .addProgramFiles(getPathsFiles())
-            .addProgramFiles(getSafeVarArgsFile())
-            .addProgramFiles(testNGSupportProgramFiles())
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
-            .compile()
-            .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibraryWithJavaBaseExtension, parameters.getApiLevel())
-            .withArtFrameworks()
-            .withArt6Plus64BitsLib();
-    int numSuccesses = 0;
-    int numHardFailures = 0;
+    return testForD8()
+        .addProgramFiles(filesToCompile)
+        .addProgramFiles(getPathsFiles())
+        .addProgramFiles(getSafeVarArgsFile())
+        .addProgramFiles(testNGSupportProgramFiles())
+        .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithJavaBaseExtension,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .withArtFrameworks()
+        .withArt6Plus64BitsLib();
+  }
+
+  private void runSuccessfulTests(D8TestCompileResult compileResult) throws Exception {
+    String verbosity = "2"; // Increase verbosity for debugging.
+    Map<String, String> runnableTests = getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS);
     for (String path : runnableTests.keySet()) {
       assert runnableTests.get(path) != null;
       D8TestRunResult result =
           compileResult.run(
               parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
-      if (result.getStdOut().endsWith(StringUtils.lines("SUCCESS"))) {
+      assertTrue(
+          result
+              .getStdOut()
+              .endsWith(
+                  StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS")));
+    }
+  }
+
+  private void runFailingTests(D8TestCompileResult compileResult) throws Exception {
+    // For failing runnable tests, we just ensure that they do not fail due to desugaring, but
+    // due to an expected failure (missing API, etc.).
+    String verbosity = "2"; // Increase verbosity for debugging.
+    Map<String, String> runnableTests = getRunnableTests(FAILING_RUNNABLE_TESTS);
+    for (String path : runnableTests.keySet()) {
+      assert runnableTests.get(path) != null;
+      D8TestRunResult result =
+          compileResult.run(
+              parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
+      if (result
+          .getStdOut()
+          .endsWith(
+              StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS"))) {
+        // The test succeeds, this can happen on tests succeeding only on high API levels.
         assertTrue(
-            result
-                .getStdOut()
-                .endsWith(
-                    StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS")));
-        numSuccesses++;
+            parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+                >= AndroidApiLevel.N.getLevel());
+      } else if (result.getStdOut().contains("java.lang.NoSuchMethodError")
+          && Arrays.stream(missingDesugaredMethods())
+              .anyMatch(method -> result.getStdOut().contains(method))) {
+        // TODO(b/134732760): support Java 9 APIs.
+      } else if (result
+          .getStdOut()
+          .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
+        // TODO(b/134732760): fix tests no to use Iterable#forEach
+      } else if (result.getStdOut().contains("in class Ljava/util/Random")
+          && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
+        // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
+      } else if (result.getStdOut().contains("java.lang.AssertionError")) {
+        // TODO(b/134732760): Investigate and fix these issues.
+      } else if (result.getStdErr().contains("$r8$wrapper$")) {
+        // Use of high library API on low API device, cannot do anything about this.
+        assertTrue(
+            parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+                < AndroidApiLevel.N.getLevel());
       } else {
-        if (result.getStdOut().contains("java.lang.NoSuchMethodError")
-            && Arrays.stream(missingDesugaredMethods())
-            .anyMatch(method -> result.getStdOut().contains(method))) {
-          // TODO(b/134732760): support Java 9 APIs.
-        } else if (result
-            .getStdOut()
-            .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
-          // TODO(b/134732760): fix tests no to use Iterable#forEach
-        } else if (result.getStdOut().contains("in class Ljava/util/Random")
-            && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
-          // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
-        } else if (result.getStdOut().contains("java.lang.AssertionError")) {
-          // TODO(b/134732760): Investigate and fix these issues.
-          numHardFailures++;
-        } else {
-          String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
-          fail(errorMessage);
-        }
+        String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
+        fail(errorMessage);
       }
     }
-    assertTrue(numSuccesses > 20);
-    assertTrue(numHardFailures <= 6);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index bc2d017..86a4edb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -26,7 +26,6 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Collections;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,13 +43,11 @@
   public static void compilePathBackport() throws Exception {
     assumeTrue("JDK8 is not checked-in on Windows", !ToolHelper.isWindows());
     pathMock = getStaticTemp().newFolder("PathMock").toPath();
-    ProcessResult processResult =
-        ToolHelper.runJavac(
-            CfVm.JDK8,
-            Collections.emptyList(),
-            pathMock,
-            getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"));
-    assertEquals(0, processResult.exitCode);
+    javac(CfVm.JDK8, getStaticTemp())
+        .setOutputPath(pathMock)
+        .addSourceFiles(
+            getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"))
+        .compile();
   }
 
   public static Path[] getPathBackport() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/heap/HeapDumpTest.java b/src/test/java/com/android/tools/r8/heap/HeapDumpTest.java
new file mode 100644
index 0000000..19a3c15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/heap/HeapDumpTest.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.heap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.HeapUtils;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class HeapDumpTest extends TestBase {
+
+  @Test
+  public void testHeapDump() throws Exception {
+    Path heapDumpDir = temp.newFolder().toPath();
+    Path heapDump = heapDumpDir.resolve("test.hprof");
+    HeapUtils.dumpHeap(heapDump, true);
+    assertTrue(heapDump.toFile().exists());
+    String header = "JAVA PROFILE 1.0.2";
+    assertTrue(heapDump.toFile().length() > header.length());
+    try (InputStream is = Files.newInputStream(heapDump)) {
+      byte[] buffer = new byte[header.length()];
+      int bytes = is.read(buffer);
+      assertEquals(buffer.length, bytes);
+      assertEquals(header, new String(buffer));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 506e33a..2071b82 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -78,6 +78,7 @@
             .addProgramFiles(PROGRAM_FILES)
             .addKeepMainRule("proto2.TestClass")
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+            .addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
             .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
             .addOptionsModification(
                 options -> {
@@ -85,18 +86,6 @@
                   options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
                   options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
                   options.enableStringSwitchConversion = true;
-
-                  // TODO(b/144003629): If devirtualization is enabled, then we insert a cast to
-                  // PartiallyUsed$Enum$EnumVerifier in MessageSchema.parseOneofField. This causes
-                  // us to retain PartiallyUsed$Enum$EnumVerifier.<clinit>(), which creates an
-                  // instance of PartiallyUsed$Enum$EnumVerifier, which causes the virtual
-                  // method PartiallyUsed$Enum$EnumVerifier.isInRange() to become live, which in
-                  // turn causes the type PartiallyUsed$Enum to become live.
-                  //
-                  // Note: This is *not* a general problem, since it only manifests if there is only
-                  // a single proto enum in the entire program. In other tests, this issue does not
-                  // appear.
-                  options.enableDevirtualization = false;
                 })
             .allowAccessModification(allowAccessModification)
             .allowUnusedProguardConfigurationRules()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 2a4b79e..7a67427 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -38,7 +38,7 @@
     return buildParameters(
         BooleanUtils.values(),
         BooleanUtils.values(),
-        getTestParameters().withAllRuntimes().build());
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public Proto3ShrinkingTest(
@@ -64,7 +64,7 @@
         .allowAccessModification(allowAccessModification)
         .allowUnusedProguardConfigurationRules()
         .minification(enableMinification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             outputInspector -> {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index 9a88fe1..19b3857 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -69,6 +69,14 @@
     }
   }
 
+  static String allGeneratedMessageLiteSubtypesAreInstantiatedRule() {
+    return StringUtils.lines(
+        "-if class * extends com.google.protobuf.GeneratedMessageLite",
+        "-keep,allowobfuscation class <1> {",
+        "  <init>(...);",
+        "}");
+  }
+
   static String alwaysInlineNewSingularGeneratedExtensionRule() {
     return StringUtils.lines(
         "-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 5e0e77f..a9385a6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -123,7 +123,7 @@
           o.inliningInstructionLimit = 6;
           // Tests depend on nullability of receiver and argument in general. Learning very accurate
           // nullability from actual usage in tests bothers what we want to test.
-          o.enableCallSiteOptimizationInfoPropagation = false;
+          o.enablePropagationOfDynamicTypesAtCallSites = false;
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
new file mode 100644
index 0000000..a0bafae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
@@ -0,0 +1,91 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeDirectNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeDirectNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverClassInline
+  static class Main {
+    public static void main(String... args) {
+      Main obj = new Main();
+      obj.test("null"); // calls test with "null".
+      obj.test("nul");  // calls test with "nul".
+    }
+
+    @NeverInline
+    private void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
new file mode 100644
index 0000000..e4200b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -0,0 +1,95 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectPositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeDirectPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeDirectPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+    assert abstractValue.isSingleStringValue()
+        && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
+  }
+
+  @NeverClassInline
+  static class Main {
+    public static void main(String... args) {
+      Main obj = new Main();
+      obj.test("nul"); // calls test with "nul".
+    }
+
+    @NeverInline
+    private void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
new file mode 100644
index 0000000..7f9aedb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -0,0 +1,105 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeInterfaceNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeInterfaceNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+          o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(i, isPresent());
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  interface I {
+    void m(String arg);
+  }
+
+  @NeverClassInline
+  static class A implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      I i = new A();
+      i.m("null");  // calls A.m() with "null".
+      i.m("nul");   // calls A.m() with "nul".
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
new file mode 100644
index 0000000..76d8e03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -0,0 +1,125 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfacePositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeInterfacePositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeInterfacePositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .addOptionsModification(o -> {
+          // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+          o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+    assert abstractValue.isSingleStringValue()
+        && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(i, isPresent());
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+  }
+
+  interface I {
+    void m(String arg);
+  }
+
+  @NeverClassInline
+  static class A implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class B implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      I i = System.currentTimeMillis() > 0 ? new A() : new B();
+      i.m("nul");  // calls A.m() with "nul".
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
new file mode 100644
index 0000000..cacb6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
@@ -0,0 +1,87 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeStaticNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeStaticNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+    assert callSiteOptimizationInfo.getAbstractArgumentValue(0).isUnknown();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      test("null"); // calls test with "null".
+      test("nul");  // calls test with "nul".
+    }
+
+    @NeverInline
+    static void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
new file mode 100644
index 0000000..1d92c6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -0,0 +1,91 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticPositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeStaticPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeStaticPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+    AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(0);
+    assert abstractValue.isSingleStringValue()
+        && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      test("nul"); // calls test with "nul".
+    }
+
+    @NeverInline
+    static void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
new file mode 100644
index 0000000..b772bff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -0,0 +1,146 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeVirtualNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null", "null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("m") || methodName.equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    if (methodName.equals("m")) {
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+      assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+    } else {
+      assert methodName.equals("test");
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+      assert callSiteOptimizationInfo.getAbstractArgumentValue(0).isUnknown();
+    }
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class A {
+    @NeverInline
+    void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+    @NeverInline
+    @Override
+    void m(String arg) {
+      // Same as A#m.
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      A a = new A();
+      test(a);    // calls A.m() with "null".
+      a.m("nul"); // calls A.m() with "nul".
+      B b = new B();
+      test(b);    // calls B.m() with "null".
+      b.m("nul"); // calls B.m() with "nul".
+    }
+
+    @NeverInline
+    static void test(A arg) {
+      arg.m("null");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
new file mode 100644
index 0000000..3541836
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -0,0 +1,129 @@
+// 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.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualPositiveTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeVirtualPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null", "null")
+        .inspect(this::inspect);
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+      assert abstractValue.isSingleStringValue()
+          && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+    } else {
+      assert abstractValue.isUnknown();
+    }
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class A {
+    @NeverInline
+    void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+    @NeverInline
+    @Override
+    void m(String arg) {
+      // Same as A#m.
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      A a = System.currentTimeMillis() > 0 ? new A() : new B();
+      a.m("nul");  // calls A.m() with "nul".
+
+      A b = new B();  // with the exact type:
+      b.m("null");    // calls B.m() with "null".
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index fc40fd9..f350e73 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -12,6 +12,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +45,26 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+        .getClassType().toSourceString().endsWith("$Base");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index a1b889c..baea5e4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,34 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("<init>") || methodName.equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType;
+    if (methodName.equals("test")) {
+      upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    } else {
+      // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+      // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+      upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    }
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$Sub1");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index f309eb8..14f9b2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
         })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,17 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$Base");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject i = inspector.clazz(I.class);
     assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 7b8818e..9f1308d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
         })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,23 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    assert upperBoundType.isDefinitelyNotNull();
+    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+      assert upperBoundType.isClassType()
+          && upperBoundType.asClassTypeLatticeElement()
+          .getClassType().toSourceString().endsWith("$Sub1");
+    } else {
+      assert upperBoundType.isClassType()
+          && upperBoundType.asClassTypeLatticeElement()
+              .getClassType().toSourceString().endsWith("$Base");
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject i = inspector.clazz(I.class);
     assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index b63d571..6291f4d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -11,6 +11,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +43,26 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$Base");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index e8b75c5..e3bd783 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -12,6 +12,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +45,30 @@
         .addKeepMainRule(MAIN)
         .enableMergeAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("<init>") || methodName.equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    // `arg` for `test` or the receiver of `Base#<init>`.
+    // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+    // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$Sub1");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index 6902f40..e62f6b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,32 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "A:Sub2", "B:Sub1", "B:Sub2")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("m") || methodName.equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    if (methodName.equals("m")) {
+      TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+      assert upperBoundType.isDefinitelyNotNull();
+      assert upperBoundType.isClassType()
+          && upperBoundType.asClassTypeLatticeElement()
+              .getClassType().toSourceString().endsWith("$Base");
+    } else {
+      assert methodName.equals("test");
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 5f3dda0..b6dae68 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,34 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "B:Sub1")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("<init>") || methodName.equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType;
+    if (methodName.equals("m")) {
+      upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    } else {
+      // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+      // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+      upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    }
+    assert upperBoundType.isDefinitelyNotNull();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$Sub1");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index d84cf8e..def280a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +43,19 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index ef0c90b..e24eadd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +44,22 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index 7481286..80b24a5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
         })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,17 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    assert upperBoundType.isNullable();
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$A");
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject i = inspector.clazz(I.class);
     assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index 613db78..6cbbad3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,6 +47,7 @@
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
         })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
@@ -52,6 +55,13 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject i = inspector.clazz(I.class);
     assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
index 601b19b..8e9e61f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -48,6 +50,7 @@
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
         })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
@@ -55,6 +58,17 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    } else {
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject i = inspector.clazz(I.class);
     assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
index f796efe..1f3d6cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +41,19 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index 401855c..4743089 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +42,22 @@
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 93588d4..cd20498 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,32 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "A", "null", "B")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    String methodName = encodedMethod.method.name.toString();
+    assert methodName.equals("m") || methodName.equals("test")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    if (methodName.equals("m")) {
+      TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+      assert upperBoundType.isNullable();
+      assert upperBoundType.isClassType()
+          && upperBoundType.asClassTypeLatticeElement()
+              .getClassType().equals(encodedMethod.method.holder);
+    } else {
+      assert methodName.equals("test");
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index f8ad42c..6ab99d7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,31 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A", "null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    assert upperBoundType.isClassType()
+        && upperBoundType.asClassTypeLatticeElement()
+            .getClassType().toSourceString().endsWith("$A");
+    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+      assert upperBoundType.isDefinitelyNotNull();
+    } else {
+      assert encodedMethod.method.holder.toSourceString().endsWith("$B");
+      assert upperBoundType.isNullable();
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
index a9a62d9..354ba3b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,12 +47,26 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "C")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert encodedMethod.method.name.toString().equals("m")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+    if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+    } else {
+      assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
+    }
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
index d5b13af..3822a49 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +45,19 @@
         .addKeepClassAndMembersRules(A.class)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "non-null")
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject a = inspector.clazz(A.class);
     assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
index 6e890de..ba8c1fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
@@ -13,6 +13,7 @@
 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.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -54,6 +55,9 @@
         .addClasspathClasses(LibClass.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
         .setMinApi(parameters.getRuntime())
         .compile()
         .addRunClasspathFiles(libraryCompileResult.writeToZip())
@@ -62,6 +66,10 @@
         .inspect(this::inspect);
   }
 
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  }
+
   private void inspect(CodeInspector inspector) {
     ClassSubject customPredicate = inspector.clazz(CustomPredicate.class);
     assertThat(customPredicate, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java
new file mode 100644
index 0000000..3fdb772
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java
@@ -0,0 +1,84 @@
+// 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.classinliner;
+
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BuilderWithNonArgumentFieldValueTest extends TestBase {
+
+  private final boolean enableClassInlining;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, class inlining: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public BuilderWithNonArgumentFieldValueTest(
+      boolean enableClassInlining, TestParameters parameters) {
+    this.enableClassInlining = enableClassInlining;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(BuilderWithNonArgumentFieldValueTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject builderClassSubject = inspector.clazz(Builder.class);
+    assertNotEquals(enableClassInlining, builderClassSubject.isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Builder().world().build());
+    }
+  }
+
+  static class Builder {
+
+    String suffix;
+
+    Builder() {
+      System.out.print("Hello");
+      this.suffix = "!";
+    }
+
+    @NeverInline
+    Builder world() {
+      System.out.print(" world");
+      return this;
+    }
+
+    @NeverInline
+    String build() {
+      return suffix;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java
new file mode 100644
index 0000000..ae2b510
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java
@@ -0,0 +1,103 @@
+// 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 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.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.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 EnumMemberValuePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumMemberValuePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumMemberValuePropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(
+        testClassSubject.uniqueMethodWithName("deadDueToFieldValuePropagation"), not(isPresent()));
+    assertThat(
+        testClassSubject.uniqueMethodWithName("deadDueToReturnValuePropagation"), not(isPresent()));
+
+    // Verify that there are no more conditional instructions.
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+  }
+
+  static class TestClass {
+
+    static MyEnum INSTANCE = MyEnum.A;
+
+    public static void main(String[] args) {
+      if (INSTANCE == MyEnum.A) {
+        System.out.print("Hello");
+      } else {
+        deadDueToFieldValuePropagation();
+      }
+      if (get() == MyEnum.A) {
+        System.out.println(" world!");
+      } else {
+        deadDueToReturnValuePropagation();
+      }
+    }
+
+    @NeverInline
+    static MyEnum get() {
+      return MyEnum.A;
+    }
+
+    @NeverInline
+    static void deadDueToFieldValuePropagation() {
+      throw new RuntimeException();
+    }
+
+    @NeverInline
+    static void deadDueToReturnValuePropagation() {
+      throw new RuntimeException();
+    }
+  }
+
+  enum MyEnum {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
new file mode 100644
index 0000000..a3d644f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -0,0 +1,136 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+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.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import 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 InstanceFieldValuePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InstanceFieldValuePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceFieldValuePropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            StringUtils.times(StringUtils.lines("A", "42", "Hello world!"), 2));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    // Verify that all instance-get instructions in testDefinitelyNotNull() has been removed by
+    // member value propagation.
+    MethodSubject testDefinitelyNotNullMethodSubject =
+        testClassSubject.uniqueMethodWithName("testDefinitelyNotNull");
+    assertThat(testDefinitelyNotNullMethodSubject, isPresent());
+    assertTrue(
+        testDefinitelyNotNullMethodSubject
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isInstanceGet));
+    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+    //  ends up being unused.
+    assertTrue(
+        testDefinitelyNotNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isNewInstance));
+
+    // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
+    // propagation.
+    MethodSubject testMaybeNullMethodSubject =
+        testClassSubject.uniqueMethodWithName("testMaybeNull");
+    assertThat(testMaybeNullMethodSubject, isPresent());
+    // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
+    //  when the receiver is nullable.
+    assertTrue(
+        testMaybeNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isInstanceGet));
+    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+    //  ends up being unused.
+    assertTrue(
+        testMaybeNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isNewInstance));
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
+    //  be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
+    assertEquals(3, aClassSubject.allInstanceFields().size());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testDefinitelyNotNull();
+      testMaybeNull();
+    }
+
+    @NeverInline
+    static void testDefinitelyNotNull() {
+      A a = new A();
+      System.out.println(a.e);
+      System.out.println(a.i);
+      System.out.println(a.s);
+    }
+
+    @NeverInline
+    static void testMaybeNull() {
+      A a = System.currentTimeMillis() >= 0 ? new A() : null;
+      System.out.println(a.e);
+      System.out.println(a.i);
+      System.out.println(a.s);
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    MyEnum e = MyEnum.A;
+    int i = 42;
+    String s = "Hello world!";
+  }
+
+  enum MyEnum {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
new file mode 100644
index 0000000..d2b739e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
@@ -0,0 +1,87 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+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;
+
+@RunWith(Parameterized.class)
+public class InstanceFieldValuePropagationWithMultipleInstanceInitializersTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InstanceFieldValuePropagationWithMultipleInstanceInitializersTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.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 testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    // Verify that the instance-get instruction in main() is still present in main(), since the
+    // value of `a.greeting` depends on the constructor being used.
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isInstanceGet));
+
+    // Verify that the `greeting` field is still present.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueFieldWithName("greeting"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = System.currentTimeMillis() >= 0 ? new A() : new A(new Object());
+      System.out.println(a.greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    String greeting;
+
+    A() {
+      this.greeting = "Hello world!";
+    }
+
+    A(Object unused) {
+      this.greeting = ":-(";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 90aeb7c..aa8e1d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -135,7 +135,7 @@
     // In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
     // code. Then, remaining call with non-null argument made getClass() replaceable.
     // Disable the propagation of call site information to separate the tests.
-    options.enableCallSiteOptimizationInfoPropagation = false;
+    options.enablePropagationOfDynamicTypesAtCallSites = false;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
new file mode 100644
index 0000000..2635741
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -0,0 +1,106 @@
+// 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 com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticWithNullOutvalueTest 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 InvokeStaticWithNullOutvalueTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeStaticWithNullOutvalueTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("Companion#boo", "Companion#foo")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check if the instance is gone.
+    ClassSubject host = inspector.clazz(Host.class);
+    assertThat(host, isPresent());
+    FieldSubject instance = host.uniqueFieldWithName("companion");
+    assertThat(instance, not(isPresent()));
+
+    ClassSubject companion = inspector.clazz(Host.Companion.class);
+    assertThat(companion, not(isPresent()));
+
+    // Check if the candidate methods are staticized (if necessary) and migrated.
+    for (String name : ImmutableList.of("boo", "foo")) {
+      MethodSubject oo = host.uniqueMethodWithName(name);
+      assertThat(oo, isPresent());
+      assertTrue(oo.isStatic());
+      assertTrue(
+          oo.streamInstructions().anyMatch(
+              i -> i.isInvokeVirtual()
+                  && i.getMethod().toSourceString().contains("PrintStream.println")));
+    }
+  }
+
+  @NeverClassInline
+  static class Host {
+    private static final Companion companion = new Companion();
+
+    static class Companion {
+      @NeverInline
+      private static Object boo() {
+        System.out.println("Companion#boo");
+        return null;
+      }
+
+      @NeverInline
+      void foo() {
+        // Return value is not used, hence invoke-static without out-value.
+        boo();
+        System.out.println("Companion#foo");
+      }
+    }
+
+    @NeverInline
+    static void bar() {
+      companion.foo();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Host.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 8869a01..0ac155c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -35,7 +36,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -53,6 +54,13 @@
         .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
+  private void configure(InternalOptions options) {
+    // This test wants to check if compile-time computation is not applied to non-null,
+    // non-constant value. In a simple test setting, call-site optimization knows the argument is
+    // always a non-null, specific constant, but that is beyond the scope of this test.
+    options.enablePropagationOfConstantsAtCallSites = false;
+  }
+
   private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
     CodeInspector codeInspector = result.inspector();
     ClassSubject mainClass = codeInspector.clazz(MAIN);
@@ -62,7 +70,7 @@
 
     MethodSubject wrapper = mainClass.uniqueMethodWithName("wrapper");
     assertThat(wrapper, isPresent());
-    // Because of nullable, non-constant argument, isEmpty() should remain.
+    // Due to nullable, non-constant argument (w/o call-site optimization), isEmpty() should remain.
     assertEquals(1, countCall(wrapper, "String", "isEmpty"));
   }
 
@@ -74,7 +82,8 @@
         testForD8()
             .debug()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 3);
@@ -83,7 +92,8 @@
         testForD8()
             .release()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 1);
@@ -97,7 +107,8 @@
             .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 1);
@@ -107,7 +118,7 @@
 
     @NeverInline
     static boolean wrapper(String arg) {
-      // Cannot be computed at compile time.
+      // Cannot be computed at compile time (unless call-site optimization is enabled).
       return arg.isEmpty();
     }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 85cf999..f9d9d21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
     // Disable the propagation of call site information to test String#valueOf optimization with
     // nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
     // simplify everything in that method.
-    options.enableCallSiteOptimizationInfoPropagation = false;
+    options.enablePropagationOfDynamicTypesAtCallSites = false;
     options.testing.forceNameReflectionOptimization = true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
index 160dc3f..e42b66b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
@@ -14,7 +14,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestBase;
-import com.android.tools.r8.KotlinTestCompileResult;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -110,15 +109,18 @@
     compileResult.writeToZip(r8ProcessedLibZip);
 
     String appFolder = PKG_PREFIX + "/supertype_app";
-    KotlinTestCompileResult kotlinTestCompileResult =
-        testForKotlin()
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf())
             .addClasspathFiles(r8ProcessedLibZip)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            .compile();
+            // TODO(b/143687784): update to just .compile() once fixed.
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+
     // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode());
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(
-        kotlinTestCompileResult.stderr(),
+        kotlinTestCompileResult.stderr,
         containsString("unresolved supertypes: " + pkg + ".supertype_lib.internal.Itf"));
   }
 
@@ -157,16 +159,18 @@
     compileResult.writeToZip(r8ProcessedLibZip);
 
     String appFolder = PKG_PREFIX + "/extension_app";
-    KotlinTestCompileResult kotlinTestCompileResult =
-        testForKotlin()
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf())
             .addClasspathFiles(r8ProcessedLibZip)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            .compile();
+            // TODO(b/143687784): update to just .compile() once fixed.
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
     // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode());
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(
-        kotlinTestCompileResult.stderr(),
+        kotlinTestCompileResult.stderr,
         containsString("unresolved supertypes: " + pkg + ".extension_lib.Super"));
-    assertThat(kotlinTestCompileResult.stderr(), containsString("unresolved reference: doStuff"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java
new file mode 100644
index 0000000..2bef90c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java
@@ -0,0 +1,20 @@
+// 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.regress.b142682636;
+
+class Regress142682636 {
+  static void foo(long v, byte[] a, int s, int b) {
+    for (int i = s; i < s + b; i++) {
+      a[i] = (byte) v;
+      v = v >> 8;
+    }
+  }
+
+  static void bar(int s, long v, byte[] a, int b) {
+    for (int i = s; i < s + b; i++) {
+      a[i] = (byte) v;
+      v = v >> 8;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
new file mode 100644
index 0000000..42a64b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
@@ -0,0 +1,43 @@
+// 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.regress.b142682636;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.MoveWide;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class Regress142682636Runner extends TestBase {
+  private final Class<?> testClass = Regress142682636.class;
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector inspector = testForD8()
+        .addProgramClasses(testClass)
+        .release()
+        .compile()
+        .inspector();
+    ClassSubject clazz = inspector.clazz(testClass);
+    assertThat(clazz, isPresent());
+    MethodSubject foo = clazz.uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+    checkNoMoveWide(foo);
+    MethodSubject bar = clazz.uniqueMethodWithName("bar");
+    assertThat(bar, isPresent());
+    checkNoMoveWide(bar);
+  }
+
+  private void checkNoMoveWide(MethodSubject m) {
+    assertTrue(Arrays.stream(m.getMethod().getCode().asDexCode().instructions)
+        .noneMatch(i -> i instanceof MoveWide));
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
index ea9b756..6c2effe 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
@@ -4,24 +4,21 @@
 package com.android.tools.r8.resolution;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.DescriptorUtils;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualOnInterfaceTest extends TestBase {
@@ -41,7 +38,7 @@
   public void testReference() throws Exception {
     testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
         .addProgramClasses(I.class, C1.class, C2.class)
-        .addProgramClassFileData(DumpMain.dump())
+        .addProgramClassFileData(transformMain())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(false));
   }
@@ -51,7 +48,7 @@
     try {
       testForR8(parameters.getBackend())
           .addProgramClasses(I.class, C1.class, C2.class)
-          .addProgramClassFileData(DumpMain.dump())
+          .addProgramClassFileData(transformMain())
           .addKeepMainRule(Main.class)
           .setMinApi(parameters.getApiLevel())
           .compile()
@@ -115,92 +112,20 @@
     }
   }
 
-  static class DumpMain implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(Main.class).toString()});
-    }
-
-    public static byte[] dump() {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(Main.class.getName()),
-          null,
-          "java/lang/Object",
-          null);
-
-      {
-        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-
-      {
-        methodVisitor =
-            classWriter.visitMethod(
-                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitInsn(ARRAYLENGTH);
-        methodVisitor.visitInsn(ICONST_2);
-        methodVisitor.visitInsn(IREM);
-        Label label0 = new Label();
-        methodVisitor.visitJumpInsn(IFNE, label0);
-        methodVisitor.visitTypeInsn(
-            NEW, "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C1");
-        methodVisitor.visitInsn(DUP);
-        methodVisitor.visitMethodInsn(
-            INVOKESPECIAL,
-            "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C1",
-            "<init>",
-            "()V",
-            false);
-        Label label1 = new Label();
-        methodVisitor.visitJumpInsn(GOTO, label1);
-        methodVisitor.visitLabel(label0);
-        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-        methodVisitor.visitTypeInsn(
-            NEW, "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C2");
-        methodVisitor.visitInsn(DUP);
-        methodVisitor.visitMethodInsn(
-            INVOKESPECIAL,
-            "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C2",
-            "<init>",
-            "()V",
-            false);
-        methodVisitor.visitLabel(label1);
-        methodVisitor.visitFrame(
-            Opcodes.F_SAME1,
-            0,
-            null,
-            1,
-            new Object[] {"com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$I"});
-        methodVisitor.visitVarInsn(ASTORE, 1);
-        methodVisitor.visitVarInsn(ALOAD, 1);
-        // Manually changed from INVOKEINTERFACE & true => INVOKEVIRTUAL & false
-        methodVisitor.visitMethodInsn(
-            INVOKEVIRTUAL,
-            "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$I",
-            "f",
-            "()V",
-            false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(2, 2);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformMain() throws Exception {
+    String binaryNameForI = DescriptorUtils.getBinaryNameFromJavaType(I.class.getTypeName());
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (owner.equals(binaryNameForI) && name.equals("f")) {
+                assertEquals(INVOKEINTERFACE, opcode);
+                assertTrue(isInterface);
+                continuation.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false);
+              } else {
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index cf2be59..ef0e967 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.resolution;
 
 import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderOrEqualThan;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.resolution.singletarget.Main;
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -16,40 +14,94 @@
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne;
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree;
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(VmTestRunner.class)
+@RunWith(Parameterized.class)
 public class SingleTargetExecutionTest extends AsmTestBase {
 
-  public static List<Class> CLASSES = ImmutableList.of(
-      InterfaceWithDefault.class,
-      AbstractTopClass.class,
-      AbstractSubClass.class,
-      SubSubClassOne.class,
-      SubSubClassTwo.class,
-      SubSubClassThree.class,
-      Main.class
-  );
+  public static List<Class<?>> CLASSES =
+      ImmutableList.of(
+          InterfaceWithDefault.class,
+          AbstractTopClass.class,
+          AbstractSubClass.class,
+          SubSubClassOne.class,
+          SubSubClassTwo.class,
+          SubSubClassThree.class,
+          Main.class);
 
   public static List<byte[]> ASM_CLASSES = ImmutableList.of(
       getBytesFromAsmClass(IrrelevantInterfaceWithDefaultDump::dump)
   );
 
+  public static final String EXPECTED =
+      StringUtils.lines(
+          "SubSubClassOne",
+          "SubSubClassOne",
+          "AbstractTopClass",
+          "SubSubClassOne",
+          "AbstractTopClass",
+          "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "ICCE",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "AbstractTopClass",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "AbstractTopClass",
+          "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
+          "InterfaceWithDefault",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "ICCE",
+          "InterfaceWithDefault",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "InterfaceWithDefault",
+          "ICCE");
+
+  public final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingleTargetExecutionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
-  // TODO(b/72208584) The desugared version of this test masks ICCE.
-  @IgnoreIfVmOlderOrEqualThan(Version.V7_0_0)
-  public void runSingleTargetTest() throws Exception {
-    List<byte[]> allBytes = new ArrayList<>();
-    allBytes.addAll(ASM_CLASSES);
-    for (Class clazz : CLASSES) {
-      allBytes.add(ToolHelper.getClassAsBytes(clazz));
-    }
-    ensureSameOutput(Main.class.getCanonicalName(),
-        ToolHelper.getMinApiLevelForDexVm(),
-        allBytes.toArray(new byte[allBytes.size()][]));
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(ASM_CLASSES)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .noMinification()
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(ASM_CLASSES)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/144085169) R8 masks ICCE.
+        .assertSuccessWithOutput(EXPECTED.replace("ICCE", "InterfaceWithDefault"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index f91f1bd..a2df4aa 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
 import com.android.tools.r8.resolution.singletarget.one.InterfaceWithDefault;
-import com.android.tools.r8.resolution.singletarget.one.IrrelevantInterfaceWithDefault;
 import com.android.tools.r8.resolution.singletarget.one.IrrelevantInterfaceWithDefaultDump;
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne;
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree;
@@ -233,13 +232,7 @@
           manyTargets(
               "overriddenInOtherInterface",
               AbstractTopClass.class,
-              InterfaceWithDefault.class,
-              IrrelevantInterfaceWithDefault.class),
-          manyTargets(
-              "overriddenInOtherInterface",
-              SubSubClassOne.class,
-              InterfaceWithDefault.class,
-              IrrelevantInterfaceWithDefault.class),
+              InterfaceWithDefault.class),
           manyTargets(
               "abstractMethod",
               ThirdAbstractTopClass.class,
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 63ef57f..c9a1c5e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -48,11 +48,10 @@
             buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
     // Resolution may return any of the matches, so it is valid if this expectation changes.
-    assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index b1afb50..075e399 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -10,24 +10,17 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class DefaultLeftAbstractRightTest extends TestBase {
@@ -53,21 +46,20 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("L::f");
   }
@@ -76,7 +68,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -109,42 +101,7 @@
     }
   }
 
-  static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() throws Exception {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          "java/lang/Object",
-          new String[] {
-            DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
-            // Manually added 'implements R'.
-            DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(L.class, R.class).transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index e889a05..58d0891 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -10,24 +10,17 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class DefaultRightAbstractLeftTest extends TestBase {
@@ -53,21 +46,20 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("R::f");
   }
@@ -76,7 +68,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -109,42 +101,7 @@
     }
   }
 
-  static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() throws Exception {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          "java/lang/Object",
-          new String[] {
-            // Manually added 'implements L'.
-            DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
-            DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(L.class, R.class).transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 38e564f..f5a99fa 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -19,19 +18,13 @@
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class DefaultTopAbstractLeftTest extends TestBase {
@@ -57,21 +50,20 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
   }
@@ -80,7 +72,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -130,42 +122,7 @@
     }
   }
 
-  static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() throws Exception {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          "java/lang/Object",
-          new String[] {
-            // Manually added 'implements L'.
-            DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
-            DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(L.class, R.class).transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 1601394..bd2d4ac 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -19,19 +18,13 @@
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class DefaultTopAbstractRightTest extends TestBase {
@@ -57,21 +50,20 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
   }
@@ -80,7 +72,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -124,48 +116,13 @@
     // Intentionally empty.
   }
 
+  private static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(L.class, R.class).transform();
+  }
+
   static class Main {
     public static void main(String[] args) {
       new B().f();
     }
   }
-
-  static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          "java/lang/Object",
-          new String[] {
-            DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
-            // Manually added 'implements R'.
-            DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index c827786..af19bf1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -5,34 +5,26 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
-import org.hamcrest.Matcher;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class DefaultTopAndBothTest extends TestBase {
@@ -57,69 +49,38 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(2, resolutionTargets.size());
-    assertTrue(
-        resolutionTargets.stream()
-            .anyMatch(m -> m.method.holder.toSourceString().equals(L.class.getTypeName())));
-    assertTrue(
-        resolutionTargets.stream()
-            .anyMatch(m -> m.method.holder.toSourceString().equals(R.class.getTypeName())));
+    Set<String> holders = new HashSet<>();
+    resolutionResult
+        .asFailedResolution()
+        .forEachFailureDependency(
+            clazz -> fail("Unexpected class dependency"),
+            target -> holders.add(target.method.holder.toSourceString()));
+    assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedError(false));
+        .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedError(true));
-  }
-
-  private boolean isDesugaringDefaultInterfaceMethods() {
-    return parameters.getApiLevel() != null
-        && parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel();
-  }
-
-  private Matcher<String> getExpectedError(boolean isR8) {
-    // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
-    if (isR8
-        && parameters.getRuntime().isCf()
-        && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
-      return containsString("AbstractMethodError");
-    }
-    // TODO(b/72208584): Default interface method desugaring changes error behavior.
-    if (isDesugaringDefaultInterfaceMethods()) {
-      if (isR8) {
-        // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
-        return containsString("AbstractMethodError");
-      }
-      // Dalvik fails with a verify error instead of the runtime failure (unless R8 removed the
-      // methods as indicated by the above.
-      if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-        return containsString("VerifyError");
-      }
-      return containsString("AbstractMethodError");
-    }
-    // Reference result should be an incompatible class change error due to the two non-abstract
-    // methods in the maximally specific set.
-    return containsString("IncompatibleClassChangeError");
+        .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
   }
 
   public interface T {
@@ -154,42 +115,7 @@
     }
   }
 
-  static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          "java/lang/Object",
-          new String[] {
-            DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
-            // Manually added 'implements R'.
-            DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  public static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(L.class, R.class).transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index a860e90..7ca9525 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -48,9 +48,8 @@
             buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 8e9b843..1cbd668 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -48,9 +48,8 @@
             buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(1, resolutionTargets.size());
-    assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index ae678cd..13de4d4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -5,7 +5,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
@@ -13,25 +13,19 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class TwoDefaultMethodsWithoutTopTest extends TestBase {
@@ -57,56 +51,44 @@
     AppInfoWithLiveness appInfo =
         SingleTargetLookupTest.createAppInfoWithLiveness(
             buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(DumpB.dump()))
+                .addClassProgramData(Collections.singletonList(transformB()))
                 .build(),
             Main.class);
     DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
-    assertEquals(2, resolutionTargets.size());
-    assertTrue(
-        resolutionTargets.stream()
-            .anyMatch(m -> m.method.holder.toSourceString().equals(I.class.getTypeName())));
-    assertTrue(
-        resolutionTargets.stream()
-            .anyMatch(m -> m.method.holder.toSourceString().equals(J.class.getTypeName())));
+    Set<String> holders = new HashSet<>();
+    resolutionResult
+        .asFailedResolution()
+        .forEachFailureDependency(
+            clazz -> fail("Unexpected class dependency"),
+            m -> holders.add(m.method.holder.toSourceString()));
+    assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
   }
 
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
-        .apply(r -> checkResult(r, false));
+        .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
-        .addProgramClassFileData(DumpB.dump())
+        .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .apply(r -> checkResult(r, true));
+        .apply(r -> checkResultR8(r));
   }
 
-  private void checkResult(TestRunResult<?> runResult, boolean isR8) {
-    // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
-    if (isR8
-        && parameters.getRuntime().isCf()
-        && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
-      runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
-    } else if (parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
-      if (isR8) {
-        // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
-        runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
-      } else {
-        // TODO(b/72208584): Desugare changes error result.
-        runResult.assertSuccessWithOutputLines("I::f");
-      }
+  private void checkResultR8(TestRunResult<?> runResult) {
+    // TODO(b/144085169): R8/CF produces incorrect result.
+    if (parameters.getRuntime().isCf()) {
+      runResult.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
     } else {
       runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
     }
@@ -136,46 +118,7 @@
     }
   }
 
-  private static class DumpB implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
-    }
-
-    public static byte[] dump() {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_PUBLIC | ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
-          null,
-          DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
-          new String[] {
-            // Manually added 'implements J'.
-            DescriptorUtils.getBinaryNameFromJavaType(J.class.getTypeName()),
-          });
-
-      {
-        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(
-            INVOKESPECIAL,
-            DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
-            "<init>",
-            "()V",
-            false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformB() throws Exception {
+    return transformer(B.class).setImplements(J.class).transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
index 466d57a..f29c3ca 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -4,105 +4,9 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
 public class ChromuimAssertionHookMock {
   public static void assertFailureHandler(AssertionError assertion) {
     System.out.println("Got AssertionError " + assertion);
   }
 }
 
-/* Below is an asmified dump of the above class */
-
-class ChromuimAssertionHookMockDump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8,
-        ACC_PUBLIC | ACC_SUPER,
-        "com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock",
-        null,
-        "java/lang/Object",
-        null);
-
-    classWriter.visitSource("ChromuimAssertionHookMock.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(7, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLocalVariable(
-          "this",
-          "Lcom/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock;",
-          null,
-          label0,
-          label1,
-          0);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC,
-              "assertFailureHandler",
-              "(Ljava/lang/AssertionError;)V",
-              null,
-              null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(9, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
-      methodVisitor.visitLdcInsn("Got AssertionError ");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL,
-          "java/lang/StringBuilder",
-          "append",
-          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
-          false);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL,
-          "java/lang/StringBuilder",
-          "append",
-          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
-          false);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(10, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "assertion", "Ljava/lang/AssertionError;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(3, 1);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index 17139be..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -4,13 +4,6 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
 public class ClassWithAssertions {
   int x = 0;
 
@@ -33,238 +26,3 @@
     new ClassWithAssertions(Integer.parseInt(args[0])).getX();
   }
 }
-
-/* Below is an asmified dump of the above class */
-
-class ClassWithAssertionsDump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    FieldVisitor fieldVisitor;
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8,
-        ACC_PUBLIC | ACC_SUPER,
-        "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-        null,
-        "java/lang/Object",
-        null);
-
-    classWriter.visitSource("ClassWithAssertions.java", null);
-
-    {
-      fieldVisitor = classWriter.visitField(0, "x", "I", null, null);
-      fieldVisitor.visitEnd();
-    }
-    {
-      fieldVisitor =
-          classWriter.visitField(
-              ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
-      fieldVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "(I)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(10, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(8, label1);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitInsn(ICONST_0);
-      methodVisitor.visitFieldInsn(
-          PUTFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(11, label2);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitVarInsn(ILOAD, 1);
-      methodVisitor.visitFieldInsn(
-          PUTFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
-      Label label3 = new Label();
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(12, label3);
-      methodVisitor.visitInsn(RETURN);
-      Label label4 = new Label();
-      methodVisitor.visitLabel(label4);
-      methodVisitor.visitLocalVariable(
-          "this",
-          "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
-          null,
-          label0,
-          label4,
-          0);
-      methodVisitor.visitLocalVariable("x", "I", null, label0, label4, 1);
-      methodVisitor.visitMaxs(2, 2);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(0, "condition", "()Z", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(15, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitFieldInsn(
-          GETFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
-      methodVisitor.visitInsn(ICONST_1);
-      Label label1 = new Label();
-      methodVisitor.visitJumpInsn(IF_ICMPNE, label1);
-      methodVisitor.visitInsn(ICONST_1);
-      Label label2 = new Label();
-      methodVisitor.visitJumpInsn(GOTO, label2);
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitInsn(ICONST_0);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
-      methodVisitor.visitInsn(IRETURN);
-      Label label3 = new Label();
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLocalVariable(
-          "this",
-          "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
-          null,
-          label0,
-          label3,
-          0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(0, "getX", "()I", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(19, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("1");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(20, label1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC,
-          "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-          "$assertionsDisabled",
-          "Z");
-      Label label2 = new Label();
-      methodVisitor.visitJumpInsn(IFNE, label2);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL,
-          "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-          "condition",
-          "()Z",
-          false);
-      methodVisitor.visitJumpInsn(IFNE, label2);
-      methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
-      methodVisitor.visitInsn(ATHROW);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(21, label2);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("2");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label3 = new Label();
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(22, label3);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitFieldInsn(
-          GETFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
-      methodVisitor.visitInsn(IRETURN);
-      Label label4 = new Label();
-      methodVisitor.visitLabel(label4);
-      methodVisitor.visitLocalVariable(
-          "this",
-          "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
-          null,
-          label0,
-          label4,
-          0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(26, label0);
-      methodVisitor.visitTypeInsn(
-          NEW, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitInsn(ICONST_0);
-      methodVisitor.visitInsn(AALOAD);
-      methodVisitor.visitMethodInsn(
-          INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL,
-          "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-          "<init>",
-          "(I)V",
-          false);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL,
-          "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-          "getX",
-          "()I",
-          false);
-      methodVisitor.visitInsn(POP);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(27, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(4, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(7, label0);
-      methodVisitor.visitLdcInsn(
-          Type.getType("Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;"));
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
-      Label label1 = new Label();
-      methodVisitor.visitJumpInsn(IFNE, label1);
-      methodVisitor.visitInsn(ICONST_1);
-      Label label2 = new Label();
-      methodVisitor.visitJumpInsn(GOTO, label2);
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitInsn(ICONST_0);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
-      methodVisitor.visitFieldInsn(
-          PUTSTATIC,
-          "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
-          "$assertionsDisabled",
-          "Z");
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 0);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 8dad81d..ab80fe5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.function.Function;
@@ -189,7 +190,7 @@
   private static R8TestCompileResult compileWithAccessModification(Backend backend)
       throws CompilationFailedException {
     return testForR8(staticTemp, backend)
-        .addProgramClassFileData(ClassWithAssertionsDump.dump())
+        .addProgramClasses(ClassWithAssertions.class)
         .addKeepMainRule(ClassWithAssertions.class)
         .addOptionsModification(o -> o.enableInlining = false)
         .allowAccessModification()
@@ -200,7 +201,7 @@
   private static R8TestCompileResult compileCf(InternalOptions.AssertionProcessing assertionsState)
       throws CompilationFailedException {
     return testForR8(staticTemp, Backend.CF)
-        .addProgramClassFileData(ClassWithAssertionsDump.dump())
+        .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .noTreeShaking()
         .noMinification()
@@ -219,10 +220,11 @@
   }
 
   private static R8TestCompileResult compileRegress110887293(Function<byte[], byte[]> rewriter)
-      throws CompilationFailedException {
+      throws CompilationFailedException, IOException {
     return testForR8(staticTemp, Backend.DEX)
         .addProgramClassFileData(
-            rewriter.apply(ClassWithAssertionsDump.dump()), ChromuimAssertionHookMockDump.dump())
+            rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)))
+        .addProgramClasses(ChromuimAssertionHookMock.class)
         .setMinApi(AndroidApiLevel.B)
         .debug()
         .noTreeShaking()
@@ -230,7 +232,8 @@
         .compile();
   }
 
-  private static CompilationResults compileAll(Backend backend) throws CompilationFailedException {
+  private static CompilationResults compileAll(Backend backend)
+      throws CompilationFailedException, IOException {
     R8TestCompileResult withAccess = compileWithAccessModification(backend);
     if (backend == Backend.CF) {
       return new CompilationResults(
@@ -337,7 +340,7 @@
   private D8TestCompileResult compileD8(InternalOptions.AssertionProcessing assertionsState)
       throws CompilationFailedException {
     return testForD8()
-        .addProgramClassFileData(ClassWithAssertionsDump.dump())
+        .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .setMinApi(AndroidApiLevel.B)
         .addOptionsModification(o -> o.assertionProcessing = assertionsState)
@@ -348,7 +351,7 @@
       InternalOptions.AssertionProcessing assertionsState) throws Exception {
     Path program =
         testForR8(Backend.CF)
-            .addProgramClassFileData(ClassWithAssertionsDump.dump())
+            .addProgramClasses(ClassWithAssertions.class)
             .debug()
             .setMinApi(AndroidApiLevel.B)
             .noTreeShaking()
@@ -373,11 +376,11 @@
   }
 
   private D8TestCompileResult compileD8Regress110887293(Function<byte[], byte[]> rewriter)
-      throws CompilationFailedException {
+      throws CompilationFailedException, IOException {
     return testForD8()
         .addProgramClassFileData(
-            rewriter.apply(ClassWithAssertionsDump.dump()),
-            rewriter.apply(ChromuimAssertionHookMockDump.dump()))
+            rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+            rewriter.apply(ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class)))
         .debug()
         .setMinApi(AndroidApiLevel.B)
         .compile();
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 63f69d7..9da1f4a 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
@@ -81,19 +81,21 @@
       }
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
     } else {
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inSwitch"));
+      assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
-    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -123,20 +125,22 @@
       String expectedConst = parameters.isDexRuntime() ? "1TWO" : "TWO";
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
+      assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+      assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
+      assertNameWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     // TODO(jakew) this should be allowed!
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
 
-    assertNameWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -173,18 +177,22 @@
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
+      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
+      assertToStringReplacedWithConst(
+          clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("noToString"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("local"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
+      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
+      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
-    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
-    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   private static void assertOrdinalReplacedWithConst(MethodSubject method, int expectedConst) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index e9e4c9f..8f09a80 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -57,8 +57,9 @@
     return TimeUnit.SECONDS.name();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
-  private static String wrongTypeStaticField() {
+  private static String differentTypeStaticField() {
     return Number.DOWN.name();
   }
 
@@ -88,7 +89,7 @@
     System.out.println(multipleUsages());
     System.out.println(inlined());
     System.out.println(libraryType());
-    System.out.println(wrongTypeStaticField());
+    System.out.println(differentTypeStaticField());
     System.out.println(nonValueStaticField());
     System.out.println(phi(true));
     System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index b439e67..534b16c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -69,7 +69,7 @@
   }
 
   @NeverInline
-  private static long wrongTypeStaticField() {
+  private static long differentTypeStaticField() {
     return Number.DOWN.ordinal();
   }
 
@@ -100,7 +100,7 @@
     System.out.println(inlined());
     System.out.println(inSwitch());
     System.out.println(libraryType());
-    System.out.println(wrongTypeStaticField());
+    System.out.println(differentTypeStaticField());
     System.out.println(nonValueStaticField());
     System.out.println(phi(true));
     System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 44d7309..3617c8b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -94,11 +94,13 @@
     return TimeUnit.SECONDS.toString();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
-  private static String wrongTypeStaticField() {
+  private static String differentTypeStaticField() {
     return NoToString.DOWN.toString();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static String nonValueStaticField() {
     return NoToString.DEFAULT.toString();
@@ -128,7 +130,7 @@
     System.out.println(multipleUsages());
     System.out.println(inlined());
     System.out.println(libraryType());
-    System.out.println(wrongTypeStaticField());
+    System.out.println(differentTypeStaticField());
     System.out.println(nonValueStaticField());
     System.out.println(phi(true));
     System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index ea96ead..92d16ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -56,43 +56,50 @@
         .enableMergeAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
-        .inspect(codeInspector -> {
-          ClassSubject main = codeInspector.clazz(MAIN);
-          assertThat(main, isPresent());
+        .inspect(
+            codeInspector -> {
+              ClassSubject main = codeInspector.clazz(MAIN);
+              assertThat(main, isPresent());
 
-          MethodSubject mainMethod = main.mainMethod();
-          assertThat(mainMethod, isPresent());
+              MethodSubject mainMethod = main.mainMethod();
+              assertThat(mainMethod, isPresent());
 
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): not trivial; assigned only once in <init>
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): not trivial; assigned multiple times, but within a certain range.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
-        })
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
+              // TODO(b/138913138): not trivial; assigned only once in <init>
+              assertFalse(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
+              // TODO(b/138913138): not trivial; assigned multiple times, but within a certain
+              // range.
+              assertFalse(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
+            })
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("The end");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index ecd6635..cc02426 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -83,7 +83,7 @@
           // Call site optimization propagation will conclude that the input of B...Caller#call is
           // always null, and replace the last call with null-throwing instruction.
           // However, we want to test return type and parameter type are kept in this scenario.
-          o.enableCallSiteOptimizationInfoPropagation = false;
+          o.enablePropagationOfDynamicTypesAtCallSites = false;
           o.enableInlining = false;
         })
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java
new file mode 100644
index 0000000..169bfa5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java
@@ -0,0 +1,66 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByClassForNameTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInitializedByClassForNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInitializedByClassForNameTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is not removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws ClassNotFoundException {
+      Class.forName("com.android.tools.r8.shaking.clinit.ClassInitializedByClassForNameTest$A");
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java
new file mode 100644
index 0000000..8036f2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java
@@ -0,0 +1,70 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByInvokeStaticTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInitializedByInvokeStaticTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInitializedByInvokeStaticTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is not removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(A.getExclamationMark());
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.print("Hello world");
+    }
+
+    static String getExclamationMark() {
+      return "!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java
new file mode 100644
index 0000000..2e31c07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java
@@ -0,0 +1,57 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByKeepRuleTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInitializedByKeepRuleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInitializedByKeepRuleTest.class)
+        .addKeepClassRules(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is not removed.
+    ClassSubject aClassSubject = inspector.clazz(TestClass.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    static {
+      System.out.print("Hello world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java
new file mode 100644
index 0000000..2349839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java
@@ -0,0 +1,66 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByNewInstanceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInitializedByNewInstanceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInitializedByNewInstanceTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is not removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java
new file mode 100644
index 0000000..9501a21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java
@@ -0,0 +1,68 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByStaticGetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInitializedByStaticGetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInitializedByStaticGetTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is not removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(A.EXCLAMATION_MARK);
+    }
+  }
+
+  static class A {
+
+    static String EXCLAMATION_MARK = "!";
+
+    static {
+      System.out.print("Hello world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
new file mode 100644
index 0000000..0a1914f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -0,0 +1,69 @@
+// 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.clinit;
+
+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 ClassNotInitializedByCheckCastTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassNotInitializedByCheckCastTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassNotInitializedByCheckCastTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("null");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    static Object object = System.currentTimeMillis() >= 0 ? null : new Object();
+
+    public static void main(String[] args) {
+      System.out.println((A) object);
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java
new file mode 100644
index 0000000..f40cc94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java
@@ -0,0 +1,67 @@
+// 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.clinit;
+
+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.R8TestRunResult;
+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 ClassNotInitializedByConstClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassNotInitializedByConstClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ClassNotInitializedByConstClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class);
+
+    // Check that A.<clinit>() is removed.
+    CodeInspector inspector = result.inspector();
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), not(isPresent()));
+
+    result.assertSuccessWithOutputLines(aClassSubject.getFinalName());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(A.class.getName());
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java
new file mode 100644
index 0000000..cc64abf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java
@@ -0,0 +1,71 @@
+// 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.clinit;
+
+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 ClassNotInitializedByInstanceOfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassNotInitializedByInstanceOfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassNotInitializedByInstanceOfTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.testing.enableCheckCastAndInstanceOfRemoval = false)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A.<clinit>() is removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.clinit(), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    static Object object = System.currentTimeMillis() >= 0 ? new Object() : null;
+
+    public static void main(String[] args) {
+      System.out.println(object instanceof A);
+    }
+  }
+
+  static class A {
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
new file mode 100644
index 0000000..3f68961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
@@ -0,0 +1,97 @@
+// 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.AndroidApiLevel;
+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 InterfaceInitializedByImplementationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        // Ensure default interface methods are supported.
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  public InterfaceInitializedByImplementationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InterfaceInitializedByImplementationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check that A is not removed.
+    ClassSubject aClasssubject = inspector.clazz(A.class);
+    assertThat(aClasssubject, isPresent());
+
+    // Check that I.<clinit>() is not removed.
+    ClassSubject iClassSubject = inspector.clazz(I.class);
+    assertThat(iClassSubject, isPresent());
+    assertThat(iClassSubject.clinit(), isPresent());
+
+    // Check that B.<clinit>() is not removed.
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertThat(bClassSubject.clinit(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().m();
+    }
+  }
+
+  interface I {
+
+    B B_INSTANCE = new B();
+
+    // TODO(b/144266257): If tree shaking removes this method, then I.<clinit>() won't be run when
+    //  A is being class initialized.
+    @NeverInline
+    default void m() {
+      System.out.println(" world!");
+    }
+  }
+
+  static class A implements I {}
+
+  static class B {
+
+    static {
+      System.out.print("Hello");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index e094a13..ecfc895 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -92,12 +92,14 @@
     QueryNode mainMethod = inspector.method(mainMethodRef).assertNotRenamed().assertKeptBy(root);
     // TestClass.<init> is kept by TestClass.main.
     QueryNode testInit = inspector.method(testInitRef).assertPresent().assertKeptBy(mainMethod);
-    // Foo.<clinit> is kept by TestClass.<init>
-    QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(testInit);
+    // The type Foo is kept by TestClass.<init>
+    QueryNode fooClassNode = inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(testInit);
+    // Foo.<clinit> is kept by Foo
+    QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(fooClassNode);
+    // The type Foo is also kept by Foo.<clinit>
+    fooClassNode.assertKeptBy(fooClInit);
     // Foo.<init> is kept by Foo.<clinit>
     QueryNode fooInit = inspector.method(fooInitRef).assertPresent().assertKeptBy(fooClInit);
-    // The type Foo is kept by the class constructor of Foo and the instance initializer.
-    inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(fooClInit).assertKeptBy(fooInit);
   }
 
   public static final class FooStaticMethod {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 3c5c230..707a415 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.origin.Origin;
@@ -70,25 +72,31 @@
   private static final String CLASS_NAME = KeptViaClassInitializerTestRunner.class.getTypeName();
   private static final String EXPECTED = StringUtils.lines("I'm an A");
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withAllApiLevels()
+        .build();
   }
 
-  public KeptViaClassInitializerTestRunner(Backend backend) {
-    this.backend = backend;
+  public KeptViaClassInitializerTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
-  public static String KEPT_REASON_SUFFIX = StringUtils.lines(
-      // The full reason is not shared between CF and DEX due to desugaring.
-      "|  void " + CLASS_NAME + "$T.<clinit>()",
-      "|- is referenced from:",
-      "|  void " + CLASS_NAME + "$Main.main(java.lang.String[])",
-      "|- is referenced in keep rule:",
-      "|  -keep class " + CLASS_NAME + "$Main { void main(java.lang.String[]); }"
-  );
+  public static String KEPT_REASON_SUFFIX =
+      StringUtils.lines(
+          // The full reason is not shared between CF and DEX due to desugaring.
+          "|  void " + CLASS_NAME + "$T.<clinit>()",
+          "|- is reachable from:",
+          "|  com.android.tools.r8.shaking.keptgraph.KeptViaClassInitializerTestRunner$T",
+          "|- is referenced from:",
+          "|  void " + CLASS_NAME + "$Main.main(java.lang.String[])",
+          "|- is referenced in keep rule:",
+          "|  -keep class " + CLASS_NAME + "$Main { void main(java.lang.String[]); }");
 
   @Test
   public void testKeptMethod() throws Exception {
@@ -99,18 +107,18 @@
 
     WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
     GraphInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .enableGraphInspector(consumer)
             .addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
             .addKeepMethodRules(mainMethod)
             .setMinApi(AndroidApiLevel.N)
             .apply(
                 b -> {
-                  if (backend == Backend.DEX) {
+                  if (parameters.isDexRuntime()) {
                     b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N));
                   }
                 })
-            .run(Main.class)
+            .run(parameters.getRuntime(), Main.class)
             .assertSuccessWithOutput(EXPECTED)
             .graphInspector();
 
@@ -124,7 +132,7 @@
 
     // TODO(b/124499108): Currently synthetic lambda classes are referenced,
     //  should be their originating context.
-    if (backend == Backend.DEX) {
+    if (parameters.isDexRuntime()) {
       assertThat(baos.toString(), containsString("-$$Lambda$"));
     } else {
       assertThat(baos.toString(), not(containsString("-$$Lambda$")));
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
new file mode 100644
index 0000000..8157f47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -0,0 +1,199 @@
+// 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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+
+public class ClassFileTransformer {
+
+  /**
+   * Basic algorithm for transforming the content of a class file.
+   *
+   * <p>The provided transformers are nested in the order given: the first in the list will receive
+   * is call back first, if it forwards to 'super' then the seconds call back will be called, etc,
+   * until finally the writer will be called. If the writer is not called the effect is as if the
+   * callback was never called and its content will not be in the result.
+   */
+  public static byte[] transform(
+      byte[] bytes,
+      List<ClassTransformer> classTransformers,
+      List<MethodTransformer> methodTransformers) {
+    ClassReader reader = new ClassReader(bytes);
+    ClassWriter writer = new ClassWriter(reader, 0);
+    ClassVisitor subvisitor = new InnerMostClassTransformer(writer, methodTransformers);
+    for (int i = classTransformers.size() - 1; i >= 0; i--) {
+      classTransformers.get(i).setSubVisitor(subvisitor);
+      subvisitor = classTransformers.get(i);
+    }
+    reader.accept(subvisitor, 0);
+    return writer.toByteArray();
+  }
+
+  // Inner-most bride from the class transformation to the method transformers.
+  private static class InnerMostClassTransformer extends ClassVisitor {
+    ClassReference classReference;
+    final List<MethodTransformer> methodTransformers;
+
+    InnerMostClassTransformer(ClassWriter writer, List<MethodTransformer> methodTransformers) {
+      super(ASM7, writer);
+      this.methodTransformers = methodTransformers;
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      super.visit(version, access, name, signature, superName, interfaces);
+      classReference = Reference.classFromBinaryName(name);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      MethodContext context = createMethodContext(access, name, descriptor);
+      MethodVisitor subvisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+      for (int i = methodTransformers.size() - 1; i >= 0; i--) {
+        MethodTransformer transformer = methodTransformers.get(i);
+        transformer.setSubVisitor(subvisitor);
+        transformer.setContext(context);
+        subvisitor = transformer;
+      }
+      return subvisitor;
+    }
+
+    private MethodContext createMethodContext(int access, String name, String descriptor) {
+      // Maybe clean up this parsing of info as it is not very nice.
+      MethodSignature methodSignature = MethodSignature.fromSignature(name, descriptor);
+      MethodReference methodReference =
+          Reference.method(
+              classReference,
+              name,
+              Arrays.stream(methodSignature.parameters)
+                  .map(DescriptorUtils::javaTypeToDescriptor)
+                  .map(Reference::typeFromDescriptor)
+                  .collect(Collectors.toList()),
+              methodSignature.type.equals("void")
+                  ? null
+                  : Reference.typeFromDescriptor(
+                      DescriptorUtils.javaTypeToDescriptor(methodSignature.type)));
+      return new MethodContext(methodReference, access);
+    }
+  }
+
+  // Transformer utilities.
+
+  private final byte[] bytes;
+  private final List<ClassTransformer> classTransformers = new ArrayList<>();
+  private final List<MethodTransformer> methodTransformers = new ArrayList<>();
+
+  private ClassFileTransformer(byte[] bytes) {
+    this.bytes = bytes;
+  }
+
+  public static ClassFileTransformer create(byte[] bytes) {
+    return new ClassFileTransformer(bytes);
+  }
+
+  public static ClassFileTransformer create(Class<?> clazz) throws IOException {
+    return create(ToolHelper.getClassAsBytes(clazz));
+  }
+
+  public byte[] transform() {
+    return ClassFileTransformer.transform(bytes, classTransformers, methodTransformers);
+  }
+
+  /** Base addition of a transformer on the class. */
+  public ClassFileTransformer addClassTransformer(ClassTransformer transformer) {
+    classTransformers.add(transformer);
+    return this;
+  }
+
+  /** Base addtion of a transformer on methods. */
+  public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
+    methodTransformers.add(transformer);
+    return this;
+  }
+
+  /** Unconditionally replace the implements clause of a class. */
+  public ClassFileTransformer setImplements(Class<?>... interfaces) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] ignoredInterfaces) {
+            super.visit(
+                version,
+                access,
+                name,
+                signature,
+                superName,
+                Arrays.stream(interfaces)
+                    .map(clazz -> DescriptorUtils.getBinaryNameFromJavaType(clazz.getTypeName()))
+                    .toArray(String[]::new));
+          }
+        });
+  }
+
+  /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
+  @FunctionalInterface
+  public interface MethodInsnTransform {
+    void visitMethodInsn(
+        int opcode,
+        String owner,
+        String name,
+        String descriptor,
+        boolean isInterface,
+        MethodInsnTransformContinuation continuation);
+  }
+
+  /** Continuation for transforming a method. Will continue with the super visitor if called. */
+  @FunctionalInterface
+  public interface MethodInsnTransformContinuation {
+    void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface);
+  }
+
+  public ClassFileTransformer transformMethodInsnInMethod(
+      String methodName, MethodInsnTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitMethodInsn(
+              int opcode, String owner, String name, String descriptor, boolean isInterface) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitMethodInsn(
+                  opcode, owner, name, descriptor, isInterface, super::visitMethodInsn);
+            } else {
+              super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+            }
+          }
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
new file mode 100644
index 0000000..885257e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import org.objectweb.asm.ClassVisitor;
+
+/**
+ * Class for transforming the content of a class.
+ *
+ * <p>This is just a simple wrapper on the ASM ClassVisitor interface.
+ */
+public class ClassTransformer extends ClassVisitor {
+  public ClassTransformer() {
+    super(ASM7, null);
+  }
+
+  // Package internals.
+
+  void setSubVisitor(ClassVisitor visitor) {
+    this.cv = visitor;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
new file mode 100644
index 0000000..a5ffa82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
@@ -0,0 +1,57 @@
+// 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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Class for transforming the content of a method.
+ *
+ * <p>This is just a simple wrapper on the ASM MethodVisitor interface with some added methods for
+ * obtaining context information.
+ */
+public class MethodTransformer extends MethodVisitor {
+
+  static class MethodContext {
+    public final MethodReference method;
+    public final int accessFlags;
+
+    public MethodContext(MethodReference method, int accessFlags) {
+      this.method = method;
+      this.accessFlags = accessFlags;
+    }
+  }
+
+  private MethodContext context;
+
+  public MethodTransformer() {
+    super(ASM7, null);
+  }
+
+  public ClassReference getHolder() {
+    return getContext().method.getHolderClass();
+  }
+
+  public MethodReference getMethod() {
+    return getContext().method;
+  }
+
+  // Package internals.
+
+  MethodContext getContext() {
+    return context;
+  }
+
+  void setSubVisitor(MethodVisitor visitor) {
+    this.mv = visitor;
+  }
+
+  void setContext(MethodContext context) {
+    this.context = context;
+  }
+}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f312919..a848beb 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -245,7 +245,9 @@
 
 def g4_change(version, r8version):
   return subprocess.check_output(
-      'g4 change --desc "Update R8 to version %s %s"' % (version, r8version),
+      'g4 change --desc "Update R8 to version %s %s\n\n'
+      'IGNORE_COMPLIANCELINT=D8 and R8 are built externally to produce a fully '
+      'tested R8lib"' % (version, r8version),
       shell=True)