Merge commit '52275f13b7965a7380c28c2449b662e3def10df4' into dev-release
diff --git a/.gitignore b/.gitignore
index ec03160..0629bac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -98,6 +98,8 @@
 third_party/kotlin/kotlin-compiler-1.3.41
 third_party/kotlin/kotlin-compiler-1.3.72.tar.gz
 third_party/kotlin/kotlin-compiler-1.3.72
+third_party/kotlinx-coroutines-1.3.6.tar.gz
+third_party/kotlinx-coroutines-1.3.6
 third_party/nest/*
 third_party/openjdk/desugar_jdk_libs
 third_party/openjdk/desugar_jdk_libs.tar.gz
diff --git a/build.gradle b/build.gradle
index 90ad4ab..338df88 100644
--- a/build.gradle
+++ b/build.gradle
@@ -327,6 +327,7 @@
                 "kotlin/kotlin-compiler-1.3.11",
                 "kotlin/kotlin-compiler-1.3.41",
                 "kotlin/kotlin-compiler-1.3.72",
+                "kotlinx-coroutines-1.3.6",
                 "openjdk/openjdk-rt-1.8",
                 "openjdk/desugar_jdk_libs",
                 "openjdk/jdk-11-test",
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 50e5be1..9bfdfcd 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -8,7 +8,9 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -134,14 +136,15 @@
      * associated diagnostics handler and a {@link CompilationFailedException} exception is thrown.
      */
     public final C build() throws CompilationFailedException {
-      try {
-        validate();
-        C c = makeCommand();
-        reporter.failIfPendingErrors();
-        return c;
-      } catch (AbortException e) {
-        throw new CompilationFailedException(e);
-      }
+      Box<C> box = new Box<>(null);
+      ExceptionUtils.withCompilationHandler(
+          reporter,
+          () -> {
+            validate();
+            box.set(makeCommand());
+            reporter.failIfPendingErrors();
+          });
+      return box.get();
     }
 
     // Helper to construct the actual command. Called as part of {@link build()}.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d3a291f..8406706 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -684,7 +684,8 @@
                   appView,
                   new SubtypingInfo(application.allClasses(), application),
                   keptGraphConsumer,
-                  missingClasses);
+                  missingClasses,
+                  prunedTypes);
           appView.setAppInfo(
               enqueuer
                   .traceApplication(
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index d31606d..6220eb4 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -11,7 +11,11 @@
 import com.google.common.collect.Iterables;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
@@ -24,12 +28,15 @@
           MIN_API_FLAG,
           "--main-dex-rules",
           "--main-dex-list",
+          "--feature",
           "--main-dex-list-output",
           "--pg-conf",
           "--pg-map-output",
           "--desugared-lib",
           THREAD_COUNT_FLAG);
 
+  private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
+
   public static void main(String[] args) throws CompilationFailedException {
     R8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
@@ -83,6 +90,9 @@
                   "  --main-dex-rules <file> # Proguard keep rules for classes to place in the",
                   "                          # primary dex file.",
                   "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
+                  "  --feature <input> <output> ",
+                  "                          # Add feature <input> file to <output> file. Several ",
+                  "                          # occurrences can map to the same output.",
                   "  --main-dex-list-output <file>  ",
                   "                          # Output the full main-dex list in <file>."),
               ASSERTIONS_USAGE_MESSAGE,
@@ -131,9 +141,11 @@
   private void parse(
       String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+    Map<Path, List<Path>> featureSplitJars = new HashMap<>();
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
       String nextArg = null;
+      String nextNextArg = null;
       if (OPTIONS_WITH_PARAMETER.contains(arg)) {
         if (++i < expandedArgs.length) {
           nextArg = expandedArgs[i];
@@ -143,6 +155,16 @@
                   "Missing parameter for " + expandedArgs[i - 1] + ".", argsOrigin));
           break;
         }
+        if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
+          if (++i < expandedArgs.length) {
+            nextNextArg = expandedArgs[i];
+          } else {
+            builder.error(
+                new StringDiagnostic(
+                    "Missing parameter for " + expandedArgs[i - 2] + ".", argsOrigin));
+            break;
+          }
+        }
       }
       if (arg.length() == 0) {
         continue;
@@ -214,6 +236,10 @@
         builder.setDisableDesugaring(true);
       } else if (arg.equals("--main-dex-rules")) {
         builder.addMainDexRulesFiles(Paths.get(nextArg));
+      } else if (arg.equals("--feature")) {
+        featureSplitJars
+            .computeIfAbsent(Paths.get(nextNextArg), k -> new ArrayList<>())
+            .add(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list")) {
         builder.addMainDexListFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list-output")) {
@@ -239,5 +265,20 @@
         builder.addProgramFiles(Paths.get(arg));
       }
     }
+    featureSplitJars.forEach(
+        (outputPath, inputJars) -> addFeatureJar(builder, outputPath, inputJars));
+  }
+
+  public void addFeatureJar(R8Command.Builder builder, Path outputPath, List<Path> inputJarPaths) {
+    builder.addFeatureSplit(
+        featureSplitGenerator -> {
+          featureSplitGenerator.setProgramConsumer(
+              builder.createProgramOutputConsumer(outputPath, OutputMode.DexIndexed, true));
+          for (Path inputPath : inputJarPaths) {
+            featureSplitGenerator.addProgramResourceProvider(
+                ArchiveProgramResourceProvider.fromArchive(inputPath));
+          }
+          return featureSplitGenerator.build();
+        });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 854c5e7..788b358 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -38,6 +38,14 @@
   public static OptionalBool isMethodAccessible(
       DexEncodedMethod method,
       DexClass holder,
+      ProgramMethod context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isMethodAccessible(method, holder, context.getHolder(), appView.appInfo());
+  }
+
+  public static OptionalBool isMethodAccessible(
+      DexEncodedMethod method,
+      DexClass holder,
       DexProgramClass context,
       AppInfoWithClassHierarchy appInfo) {
     return isMemberAccessible(method.accessFlags, holder, context, appInfo);
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 73395b2..7a85dc8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -42,6 +42,10 @@
     this.staticValue = staticValue;
   }
 
+  public DexType type() {
+    return field.type;
+  }
+
   public boolean isProgramField(DexDefinitionSupplier definitions) {
     if (field.holder.isClassType()) {
       DexClass clazz = definitions.definitionFor(field.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 3b635e6..1a318c0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1268,6 +1268,7 @@
     public final DexMethod name;
     public final DexMethod toString;
     public final DexMethod compareTo;
+    public final DexMethod equals;
 
     public final DexMethod constructor =
         createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -1301,10 +1302,13 @@
               DexString.EMPTY_ARRAY);
       compareTo =
           createMethod(
+              enumDescriptor, compareToMethodName, intDescriptor, new DexString[] {enumDescriptor});
+      equals =
+          createMethod(
               enumDescriptor,
-              compareToMethodName,
-              stringDescriptor,
-              new DexString[] {enumDescriptor});
+              equalsMethodName,
+              booleanDescriptor,
+              new DexString[] {objectDescriptor});
     }
 
     public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 978646c..41f5dc8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -388,7 +388,7 @@
         return false;
       }
       ResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOn(superType, method, instruction.itf);
+          appView.appInfo().resolveMethodOn(superType, method, instruction.isInterface);
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 0e3f404..8b63913 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -162,10 +162,16 @@
 
   public static DexType getRefinedReceiverType(
       AppView<? extends AppInfoWithClassHierarchy> appView, DexMethod method, Value receiver) {
-    TypeElement lattice = receiver.getDynamicUpperBoundType(appView);
+    return toRefinedReceiverType(receiver.getDynamicUpperBoundType(appView), method, appView);
+  }
+
+  public static DexType toRefinedReceiverType(
+      TypeElement receiverUpperBoundType,
+      DexMethod method,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     DexType staticReceiverType = method.holder;
-    if (lattice.isClassType()) {
-      ClassTypeElement classType = lattice.asClassType();
+    if (receiverUpperBoundType.isClassType()) {
+      ClassTypeElement classType = receiverUpperBoundType.asClassType();
       DexType refinedType = classType.getClassType();
       if (refinedType == appView.dexItemFactory().objectType) {
         Set<DexType> interfaces = classType.getInterfaces();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index c6012bd..117af7b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -12,50 +12,60 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Assume.Assumption;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Objects;
 import java.util.Set;
 
-public class Assume<An extends Assumption> extends Instruction {
+public class Assume extends Instruction {
 
   private static final String ERROR_MESSAGE =
       "Expected Assume instructions to be removed after IR processing.";
 
-  private final An assumption;
+  private final DynamicTypeAssumption dynamicTypeAssumption;
+  private final NonNullAssumption nonNullAssumption;
   private final Instruction origin;
 
-  private Assume(An assumption, Value dest, Value src, Instruction origin, AppView<?> appView) {
+  private Assume(
+      DynamicTypeAssumption dynamicTypeAssumption,
+      NonNullAssumption nonNullAssumption,
+      Value dest,
+      Value src,
+      Instruction origin,
+      AppView<?> appView) {
     super(dest, src);
-    assert assumption != null;
-    assert assumption.verifyCorrectnessOfValues(dest, src, appView);
+    assert dynamicTypeAssumption != null || nonNullAssumption != null;
+    assert BooleanUtils.intValue(dynamicTypeAssumption != null)
+            + BooleanUtils.intValue(nonNullAssumption != null)
+        == 1;
+    assert dynamicTypeAssumption == null
+        || dynamicTypeAssumption.verifyCorrectnessOfValues(dest, src, appView);
+    assert nonNullAssumption == null
+        || nonNullAssumption.verifyCorrectnessOfValues(dest, src, appView);
     assert dest != null;
-    this.assumption = assumption;
+    this.dynamicTypeAssumption = dynamicTypeAssumption;
+    this.nonNullAssumption = nonNullAssumption;
     this.origin = origin;
   }
 
-  public static Assume<NoAssumption> createAssumeNoneInstruction(
+  public static Assume createAssumeNonNullInstruction(
       Value dest, Value src, Instruction origin, AppView<?> appView) {
-    return new Assume<>(NoAssumption.get(), dest, src, origin, appView);
+    return new Assume(null, NonNullAssumption.get(), dest, src, origin, appView);
   }
 
-  public static Assume<NonNullAssumption> createAssumeNonNullInstruction(
-      Value dest, Value src, Instruction origin, AppView<?> appView) {
-    return new Assume<>(NonNullAssumption.get(), dest, src, origin, appView);
-  }
-
-  public static Assume<DynamicTypeAssumption> createAssumeDynamicTypeInstruction(
+  public static Assume createAssumeDynamicTypeInstruction(
       TypeElement dynamicUpperBoundType,
       ClassTypeElement dynamicLowerBoundType,
       Value dest,
       Value src,
       Instruction origin,
       AppView<?> appView) {
-    return new Assume<>(
+    return new Assume(
         new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType),
+        null,
         dest,
         src,
         origin,
@@ -69,7 +79,7 @@
 
   public boolean verifyInstructionIsNeeded(AppView<?> appView) {
     if (isAssumeDynamicType()) {
-      assert assumption.verifyCorrectnessOfValues(outValue(), src(), appView);
+      assert dynamicTypeAssumption.verifyCorrectnessOfValues(outValue(), src(), appView);
     }
     return true;
   }
@@ -79,8 +89,12 @@
     return visitor.visit(this);
   }
 
-  public An getAssumption() {
-    return assumption;
+  public DynamicTypeAssumption getDynamicTypeAssumption() {
+    return dynamicTypeAssumption;
+  }
+
+  public NonNullAssumption getNonNullAssumption() {
+    return nonNullAssumption;
   }
 
   public Value src() {
@@ -98,9 +112,6 @@
 
   @Override
   public String getInstructionName() {
-    if (isAssumeNone()) {
-      return "AssumeNone";
-    }
     if (isAssumeDynamicType()) {
       return "AssumeDynamicType";
     }
@@ -116,51 +127,18 @@
   }
 
   @Override
-  public Assume<An> asAssume() {
+  public Assume asAssume() {
     return this;
   }
 
   @Override
-  public boolean isAssumeNone() {
-    return assumption.isAssumeNone();
-  }
-
-  @Override
-  public Assume<NoAssumption> asAssumeNone() {
-    assert isAssumeNone();
-    @SuppressWarnings("unchecked")
-    Assume<NoAssumption> self = (Assume<NoAssumption>) this;
-    return self;
-  }
-
-  @Override
   public boolean isAssumeDynamicType() {
-    return assumption.isAssumeDynamicType();
-  }
-
-  @Override
-  public Assume<DynamicTypeAssumption> asAssumeDynamicType() {
-    assert isAssumeDynamicType();
-    @SuppressWarnings("unchecked")
-    Assume<DynamicTypeAssumption> self = (Assume<DynamicTypeAssumption>) this;
-    return self;
+    return dynamicTypeAssumption != null;
   }
 
   @Override
   public boolean isAssumeNonNull() {
-    return assumption.isAssumeNonNull();
-  }
-
-  @Override
-  public Assume<NonNullAssumption> asAssumeNonNull() {
-    assert isAssumeNonNull();
-    @SuppressWarnings("unchecked")
-    Assume<NonNullAssumption> self = (Assume<NonNullAssumption>) this;
-    return self;
-  }
-
-  public boolean mayAffectStaticType() {
-    return isAssumeNonNull();
+    return nonNullAssumption != null;
   }
 
   @Override
@@ -171,12 +149,8 @@
     if (outType.isPrimitiveType()) {
       return false;
     }
-    if (assumption.isAssumeNone()) {
-      // The main purpose of AssumeNone is to test local alias tracking.
-      return true;
-    }
-    if (assumption.isAssumeDynamicType()) {
-      outType = asAssumeDynamicType().assumption.getDynamicUpperBoundType();
+    if (isAssumeDynamicType()) {
+      outType = dynamicTypeAssumption.getDynamicUpperBoundType();
     }
     if (appView.appInfo().hasLiveness()) {
       if (outType.isClassType()
@@ -233,8 +207,9 @@
     if (!other.isAssume()) {
       return false;
     }
-    Assume<?> assumeInstruction = other.asAssume();
-    return assumption.equals(assumeInstruction.assumption);
+    Assume assumeInstruction = other.asAssume();
+    return Objects.equals(dynamicTypeAssumption, assumeInstruction.dynamicTypeAssumption)
+        && Objects.equals(nonNullAssumption, assumeInstruction.nonNullAssumption);
   }
 
   @Override
@@ -245,10 +220,10 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    if (assumption.isAssumeNone() || assumption.isAssumeDynamicType()) {
+    if (isAssumeDynamicType()) {
       return src().getType();
     }
-    if (assumption.isAssumeNonNull()) {
+    if (isAssumeNonNull()) {
       assert src().getType().isReferenceType();
       return src().getType().asReferenceType().asMeetWithNotNull();
     }
@@ -281,7 +256,7 @@
 
     TypeElement inType = src().getType();
     TypeElement outType = getOutType();
-    if (isAssumeNone() || isAssumeDynamicType()) {
+    if (isAssumeDynamicType()) {
       assert inType.isReferenceType() : inType;
       assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
@@ -303,63 +278,22 @@
     //     are still valid.
     String originString =
         origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)";
-    if (isAssumeNone() || isAssumeNonNull()) {
+    if (isAssumeNonNull()) {
       return super.toString() + originString;
     }
     if (isAssumeDynamicType()) {
-      DynamicTypeAssumption assumption = asAssumeDynamicType().getAssumption();
       return super.toString()
           + "; upper bound: "
-          + assumption.dynamicUpperBoundType
-          + (assumption.dynamicLowerBoundType != null
-              ? "; lower bound: " + assumption.dynamicLowerBoundType
+          + dynamicTypeAssumption.dynamicUpperBoundType
+          + (dynamicTypeAssumption.dynamicLowerBoundType != null
+              ? "; lower bound: " + dynamicTypeAssumption.dynamicLowerBoundType
               : "")
           + originString;
     }
     return super.toString();
   }
 
-  abstract static class Assumption {
-
-    public boolean isAssumeNone() {
-      return false;
-    }
-
-    public boolean isAssumeDynamicType() {
-      return false;
-    }
-
-    public boolean isAssumeNonNull() {
-      return false;
-    }
-
-    public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      return true;
-    }
-  }
-
-  public static class NoAssumption extends Assumption {
-    private static final NoAssumption instance = new NoAssumption();
-
-    private NoAssumption() {}
-
-    static NoAssumption get() {
-      return instance;
-    }
-
-    @Override
-    public boolean isAssumeNone() {
-      return true;
-    }
-
-    @Override
-    public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      assert dest.getType() == src.getType();
-      return true;
-    }
-  }
-
-  public static class DynamicTypeAssumption extends Assumption {
+  public static class DynamicTypeAssumption {
 
     private final TypeElement dynamicUpperBoundType;
     private final ClassTypeElement dynamicLowerBoundType;
@@ -378,12 +312,6 @@
       return dynamicLowerBoundType;
     }
 
-    @Override
-    public boolean isAssumeDynamicType() {
-      return true;
-    }
-
-    @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
       assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getType(), appView);
       return true;
@@ -408,7 +336,7 @@
     }
   }
 
-  public static class NonNullAssumption extends Assumption {
+  public static class NonNullAssumption {
 
     private static final NonNullAssumption instance = new NonNullAssumption();
 
@@ -418,12 +346,6 @@
       return instance;
     }
 
-    @Override
-    public boolean isAssumeNonNull() {
-      return true;
-    }
-
-    @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
       assert !src.isNeverNull();
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index fdfbe61..f8793ab 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -35,7 +35,7 @@
   }
 
   @Override
-  public T visit(Assume<?> instruction) {
+  public T visit(Assume instruction) {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 4a6a082..a0af5e2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -620,7 +620,7 @@
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
         if (instruction.isAssumeDynamicType()) {
-          assert instruction.asAssumeDynamicType().verifyInstructionIsNeeded(appView);
+          assert instruction.asAssume().verifyInstructionIsNeeded(appView);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 115afec..b8a5787 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -128,6 +128,11 @@
       DexEncodedField encodedField =
           appInfoWithLiveness.resolveField(getField()).getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
+
+      if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
+        return false;
+      }
+
       return appInfoWithLiveness.isFieldRead(encodedField)
           || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
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 6d298df..1400455 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
@@ -22,9 +22,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 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;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -716,15 +713,7 @@
     return false;
   }
 
-  public Assume<?> asAssume() {
-    return null;
-  }
-
-  public boolean isAssumeNone() {
-    return false;
-  }
-
-  public Assume<NoAssumption> asAssumeNone() {
+  public Assume asAssume() {
     return null;
   }
 
@@ -732,18 +721,10 @@
     return false;
   }
 
-  public Assume<DynamicTypeAssumption> asAssumeDynamicType() {
-    return null;
-  }
-
   public boolean isAssumeNonNull() {
     return false;
   }
 
-  public Assume<NonNullAssumption> asAssumeNonNull() {
-    return null;
-  }
-
   public boolean isBinop() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index 4bb29bb..a374439 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -24,7 +24,7 @@
 
   T visit(ArrayPut instruction);
 
-  T visit(Assume<?> instruction);
+  T visit(Assume instruction);
 
   T visit(CheckCast instruction);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 8fad9af..1c19488 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -61,6 +61,7 @@
     super(result, arguments);
   }
 
+  @Deprecated
   public static Invoke create(
       Type type, DexItem target, DexProto proto, Value result, List<Value> arguments) {
     return create(type, target, proto, result, arguments, false);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 1bc6ba6..0f736a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -16,6 +16,8 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 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.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -27,15 +29,15 @@
 
 public class InvokeDirect extends InvokeMethodWithReceiver {
 
-  private final boolean itf;
+  private final boolean isInterface;
 
   public InvokeDirect(DexMethod target, Value result, List<Value> arguments) {
     this(target, result, arguments, false);
   }
 
-  public InvokeDirect(DexMethod target, Value result, List<Value> arguments, boolean itf) {
+  public InvokeDirect(DexMethod target, Value result, List<Value> arguments, boolean isInterface) {
     super(target, result, arguments);
-    this.itf = itf;
+    this.isInterface = isInterface;
     // invoke-direct <init> should have no out value.
     assert !target.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)
         || result == null;
@@ -46,8 +48,9 @@
     return Opcodes.INVOKE_DIRECT;
   }
 
-  public boolean isInterface() {
-    return itf;
+  @Override
+  public boolean getInterfaceBit() {
+    return isInterface;
   }
 
   @Override
@@ -118,7 +121,10 @@
 
   @Override
   public DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver) {
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType) {
     DexMethod invokedMethod = getInvokedMethod();
     if (appView.appInfo().hasLiveness()) {
       AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
@@ -140,7 +146,8 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(org.objectweb.asm.Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
+    builder.add(
+        new CfInvoke(org.objectweb.asm.Opcodes.INVOKESPECIAL, getInvokedMethod(), isInterface));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index f998009..e95be77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType;
+
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeInterfaceRange;
 import com.android.tools.r8.graph.AppView;
@@ -13,7 +15,8 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -28,6 +31,11 @@
   }
 
   @Override
+  public boolean getInterfaceBit() {
+    return true;
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INVOKE_INTERFACE;
   }
@@ -88,7 +96,10 @@
 
   @Override
   public DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver) {
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       return appViewWithLiveness
@@ -98,9 +109,9 @@
               context,
               true,
               appView,
-              TypeAnalysis.getRefinedReceiverType(
-                  appViewWithLiveness, getInvokedMethod(), receiver),
-              receiver.getDynamicLowerBoundType(appViewWithLiveness));
+              toRefinedReceiverType(
+                  receiverUpperBoundType, getInvokedMethod(), appViewWithLiveness),
+              receiverLowerBoundType);
     }
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 8c059d9..c8dd6ec 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
@@ -41,6 +41,8 @@
     this.method = target;
   }
 
+  public abstract boolean getInterfaceBit();
+
   @Override
   public DexType getReturnType() {
     return method.proto.returnType;
@@ -109,8 +111,7 @@
         refinedReceiverLowerBound = null;
       }
     }
-    ResolutionResult resolutionResult =
-        appView.appInfo().resolveMethod(method, isInvokeInterface());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, getInterfaceBit());
     LookupResult lookupResult;
     if (refinedReceiverUpperBound != null) {
       lookupResult =
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 496f869..a2aa502 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -73,15 +73,30 @@
 
   @Override
   public final DexEncodedMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) {
-    return lookupSingleTarget(appView, context, getReceiver());
+    TypeElement receiverUpperBoundType = null;
+    ClassTypeElement receiverLowerBoundType = null;
+    if (appView.enableWholeProgramOptimizations()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      receiverUpperBoundType = getReceiver().getDynamicUpperBoundType(appViewWithLiveness);
+      receiverLowerBoundType = getReceiver().getDynamicLowerBoundType(appViewWithLiveness);
+    }
+    return lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType);
   }
 
   public abstract DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver);
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType);
 
   public final ProgramMethod lookupSingleProgramTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver) {
-    return asProgramMethodOrNull(lookupSingleTarget(appView, context, receiver), appView);
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType) {
+    return asProgramMethodOrNull(
+        lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType),
+        appView);
   }
 
   @Override
@@ -194,7 +209,7 @@
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
     ResolutionResult resolutionResult =
-        appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), isInvokeInterface());
+        appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), getInterfaceBit());
     if (resolutionResult.isFailedResolution()) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 6a1e8a2..824a283 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  public boolean getInterfaceBit() {
+    return false;
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INVOKE_POLYMORPHIC;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 7cd9b1e..7f5836a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public boolean getInterfaceBit() {
+    return isInterface;
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INVOKE_STATIC;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index e2b2e3a..4815431 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -22,11 +24,16 @@
 
 public class InvokeSuper extends InvokeMethodWithReceiver {
 
-  public final boolean itf;
+  public final boolean isInterface;
 
-  public InvokeSuper(DexMethod target, Value result, List<Value> arguments, boolean itf) {
+  public InvokeSuper(DexMethod target, Value result, List<Value> arguments, boolean isInterface) {
     super(target, result, arguments);
-    this.itf = itf;
+    this.isInterface = isInterface;
+  }
+
+  @Override
+  public boolean getInterfaceBit() {
+    return isInterface;
   }
 
   @Override
@@ -75,7 +82,8 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(org.objectweb.asm.Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
+    builder.add(
+        new CfInvoke(org.objectweb.asm.Opcodes.INVOKESPECIAL, getInvokedMethod(), isInterface));
   }
 
   @Override
@@ -95,7 +103,10 @@
 
   @Override
   public DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver) {
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType) {
     if (appView.appInfo().hasLiveness() && context != null) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 4739d83..2d95115 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType;
+
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.graph.AppView;
@@ -14,7 +16,8 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -29,6 +32,11 @@
   }
 
   @Override
+  public boolean getInterfaceBit() {
+    return false;
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INVOKE_VIRTUAL;
   }
@@ -89,12 +97,20 @@
 
   @Override
   public DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver) {
-    return lookupSingleTarget(appView, context, receiver, getInvokedMethod());
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType) {
+    return lookupSingleTarget(
+        appView, context, receiverUpperBoundType, receiverLowerBoundType, getInvokedMethod());
   }
 
   public static DexEncodedMethod lookupSingleTarget(
-      AppView<?> appView, ProgramMethod context, Value receiver, DexMethod method) {
+      AppView<?> appView,
+      ProgramMethod context,
+      TypeElement receiverUpperBoundType,
+      ClassTypeElement receiverLowerBoundType,
+      DexMethod method) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       return appViewWithLiveness
@@ -104,8 +120,8 @@
               context,
               false,
               appView,
-              TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, method, receiver),
-              receiver.getDynamicLowerBoundType(appViewWithLiveness));
+              toRefinedReceiverType(receiverUpperBoundType, method, appViewWithLiveness),
+              receiverLowerBoundType);
     }
     // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used
     // for library modeling.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 678a569..36b6520 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -125,6 +125,10 @@
         return false;
       }
 
+      if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
+        return false;
+      }
+
       return appInfoWithLiveness.isFieldRead(encodedField)
           || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index b79a063..53af0fc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1173,16 +1173,15 @@
       return root.getDynamicUpperBoundType(appView);
     }
 
-    // Try to find an alias of the receiver, which is defined by an instruction of the type
-    // Assume<DynamicTypeAssumption>.
+    // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue =
         getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType());
     TypeElement lattice;
     if (aliasedValue != null) {
-      // If there is an alias of the receiver, which is defined by an Assume<DynamicTypeAssumption>
-      // instruction, then use the dynamic type as the refined receiver type.
+      // If there is an alias of the receiver, which is defined by an Assume instruction that
+      // carries a dynamic type, then use the dynamic type as the refined receiver type.
       lattice =
-          aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicUpperBoundType();
+          aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicUpperBoundType();
 
       // For precision, verify that the dynamic type is at least as precise as the static type.
       assert lattice.lessThanOrEqualUpToNullability(type, appView) : type + " < " + lattice;
@@ -1228,12 +1227,11 @@
       return null;
     }
 
-    // Try to find an alias of the receiver, which is defined by an instruction of the type
-    // Assume<DynamicTypeAssumption>.
+    // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
     if (aliasedValue != null) {
       ClassTypeElement lattice =
-          aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
+          aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
       return lattice != null && type.isDefinitelyNotNull() && lattice.isNullable()
           ? lattice.asMeetWithNotNull()
           : lattice;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index dba65f7..844ccce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -52,6 +52,7 @@
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
+import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -64,7 +65,6 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
-import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
@@ -292,7 +292,7 @@
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       if (options.enableNonNullTracking) {
-        assumers.add(new NonNullTracker(appViewWithLiveness));
+        assumers.add(new AssumeInserter(appViewWithLiveness));
       }
       this.classInliner =
           options.enableClassInlining && options.enableInlining ? new ClassInliner() : null;
@@ -797,8 +797,10 @@
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 3");
-      if (outliner.selectMethodsForOutlining()) {
+      ProgramMethodSet methodsSelectedForOutlining = outliner.selectMethodsForOutlining();
+      if (!methodsSelectedForOutlining.isEmpty()) {
         forEachSelectedOutliningMethod(
+            methodsSelectedForOutlining,
             code -> {
               printMethod(code, "IR before outlining (SSA)", null);
               outliner.identifyOutlineSites(code);
@@ -808,6 +810,7 @@
         appView.appInfo().addSynthesizedClass(outlineClass);
         optimizeSynthesizedClass(outlineClass, executorService);
         forEachSelectedOutliningMethod(
+            methodsSelectedForOutlining,
             code -> {
               outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
@@ -839,9 +842,6 @@
       if (libraryMethodOverrideAnalysis != null) {
         libraryMethodOverrideAnalysis.logResults();
       }
-      if (uninstantiatedTypeOptimization != null) {
-        uninstantiatedTypeOptimization.logResults();
-      }
       if (stringOptimizer != null) {
         stringOptimizer.logResult();
       }
@@ -898,11 +898,13 @@
   }
 
   private void forEachSelectedOutliningMethod(
-      Consumer<IRCode> consumer, ExecutorService executorService)
+      ProgramMethodSet methodsSelectedForOutlining,
+      Consumer<IRCode> consumer,
+      ExecutorService executorService)
       throws ExecutionException {
     assert !options.skipIR;
     ThreadUtils.processItems(
-        outliner.buildMethodsSelectedForOutlining(),
+        methodsSelectedForOutlining,
         method -> {
           IRCode code = method.buildIR(appView);
           assert code != null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index e94d07e..f83952c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -29,6 +29,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -293,8 +294,18 @@
                 assert newInValues.size()
                     == actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
 
+                // TODO(b/157111832): This bit should be part of the graph lens lookup result.
+                boolean isInterface =
+                    getBooleanOrElse(
+                        appView.definitionFor(actualTarget.holder), DexClass::isInterface, false);
                 Invoke newInvoke =
-                    Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
+                    Invoke.create(
+                        actualInvokeType,
+                        actualTarget,
+                        null,
+                        newOutValue,
+                        newInValues,
+                        isInterface);
                 iterator.replaceCurrentInstruction(newInvoke);
                 if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
                   affectedPhis.addAll(newOutValue.uniquePhiUsers());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 250737f..53ff67e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -114,7 +114,7 @@
         ResolutionResult resolutionResult =
             appView
                 .appInfoForDesugaring()
-                .resolveMethod(invoke.getInvokedMethod(), invoke.isInvokeInterface());
+                .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit());
         if (resolutionResult.isFailedResolution()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index f41bdce..d203a12 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -540,16 +540,6 @@
 
     boolean holderIsInterface() {
       InternalOptions options = appView.options();
-      if (!options.isGeneratingClassFiles()) {
-        // When generating dex the value of this flag on invokes does not matter (unused).
-        // We cannot know if definitionFor(implMethod.holder) is null or not in that case,
-        // so we cannot set the flag and just return false.
-        return false;
-      }
-      // The only case where we do Lambda desugaring with Cf to Cf is in L8.
-      // If the compilation is not coreLibraryCompilation, then the assertion
-      // implMethodHolder != null may fail, hence the assertion.
-      assert options.cfToCfDesugar;
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = appView.definitionFor(implMethod.holder);
       if (implMethodHolder == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 9102f53..7f45db3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -34,8 +33,7 @@
   private final IRCode code;
 
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
-  private final Set<Assume<DynamicTypeAssumption>> assumeDynamicTypeInstructionsToRemove =
-      Sets.newIdentityHashSet();
+  private final Set<Assume> assumeDynamicTypeInstructionsToRemove = Sets.newIdentityHashSet();
 
   private boolean mayHaveIntroducedTrivialPhi = false;
 
@@ -48,21 +46,20 @@
     return mayHaveIntroducedTrivialPhi;
   }
 
-  public void markForRemoval(Assume<DynamicTypeAssumption> assumeDynamicTypeInstruction) {
+  public void markForRemoval(Assume assumeDynamicTypeInstruction) {
     assumeDynamicTypeInstructionsToRemove.add(assumeDynamicTypeInstruction);
   }
 
   public void markUsersForRemoval(Value value) {
     for (Instruction user : value.aliasedUsers()) {
       if (user.isAssumeDynamicType()) {
-        markForRemoval(user.asAssumeDynamicType());
+        markForRemoval(user.asAssume());
       }
     }
   }
 
   public void removeIfMarked(
-      Assume<DynamicTypeAssumption> assumeDynamicTypeInstruction,
-      InstructionListIterator instructionIterator) {
+      Assume assumeDynamicTypeInstruction, InstructionListIterator instructionIterator) {
     if (assumeDynamicTypeInstructionsToRemove.remove(assumeDynamicTypeInstruction)) {
       Value inValue = assumeDynamicTypeInstruction.src();
       Value outValue = assumeDynamicTypeInstruction.outValue();
@@ -91,7 +88,7 @@
         while (instructionIterator.hasNext()) {
           Instruction instruction = instructionIterator.next();
           if (instruction.isAssumeDynamicType()) {
-            removeIfMarked(instruction.asAssumeDynamicType(), instructionIterator);
+            removeIfMarked(instruction.asAssume(), instructionIterator);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
similarity index 66%
rename from src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
rename to src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index e08aeae..611a0a1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -48,11 +48,11 @@
 import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 
-public class NonNullTracker implements Assumer {
+public class AssumeInserter implements Assumer {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-  public NonNullTracker(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
@@ -67,7 +67,7 @@
       BasicBlockIterator blockIterator,
       Predicate<BasicBlock> blockTester,
       Timing timing) {
-    timing.begin("Insert assume not null instructions");
+    timing.begin("Insert assume instructions");
     internalInsertAssumeInstructionsInBlocks(code, blockIterator, blockTester, timing);
     timing.end();
   }
@@ -77,54 +77,54 @@
       BasicBlockIterator blockIterator,
       Predicate<BasicBlock> blockTester,
       Timing timing) {
-    timing.begin("Part 1: Compute non null values");
-    NonNullValues nonNullValues = computeNonNullValues(code, blockIterator, blockTester);
+    timing.begin("Part 1: Compute assumed values");
+    AssumedValues assumedValues = computeAssumedValues(code, blockIterator, blockTester);
     timing.end();
-    if (nonNullValues.isEmpty()) {
+    if (assumedValues.isEmpty()) {
       return;
     }
 
     timing.begin("Part 2: Remove redundant assume instructions");
-    removeRedundantAssumeInstructions(nonNullValues);
+    removeRedundantAssumeInstructions(assumedValues);
     timing.end();
 
     timing.begin("Part 3: Compute dominated users");
     Map<Instruction, Set<Value>> redundantKeys =
-        computeDominanceForNonNullValues(code, nonNullValues);
+        computeDominanceForAssumedValues(code, assumedValues);
     timing.end();
-    if (nonNullValues.isEmpty()) {
+    if (assumedValues.isEmpty()) {
       return;
     }
 
     timing.begin("Part 4: Remove redundant dominated assume instructions");
-    removeRedundantDominatedAssumeInstructions(nonNullValues, redundantKeys);
+    removeRedundantDominatedAssumeInstructions(assumedValues, redundantKeys);
     timing.end();
-    if (nonNullValues.isEmpty()) {
+    if (assumedValues.isEmpty()) {
       return;
     }
 
     timing.begin("Part 5: Materialize assume instructions");
-    materializeAssumeInstructions(code, nonNullValues);
+    materializeAssumeInstructions(code, assumedValues);
     timing.end();
   }
 
-  private NonNullValues computeNonNullValues(
+  private AssumedValues computeAssumedValues(
       IRCode code, BasicBlockIterator blockIterator, Predicate<BasicBlock> blockTester) {
-    NonNullValues.Builder nonNullValuesBuilder = new NonNullValues.Builder();
+    AssumedValues.Builder assumedValuesBuilder = new AssumedValues.Builder();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (blockTester.test(block)) {
-        computeNonNullValuesInBlock(code, blockIterator, block, nonNullValuesBuilder);
+        computeAssumedValuesInBlock(code, blockIterator, block, assumedValuesBuilder);
       }
     }
-    return nonNullValuesBuilder.build();
+    return assumedValuesBuilder.build();
   }
 
-  private void computeNonNullValuesInBlock(
+  private void computeAssumedValuesInBlock(
       IRCode code,
       BasicBlockIterator blockIterator,
       BasicBlock block,
-      NonNullValues.Builder nonNullValuesBuilder) {
+      AssumedValues.Builder assumedValuesBuilder) {
     // Add non-null after
     // 1) instructions that implicitly indicate receiver/array is not null.
     // 2) invocations that are guaranteed to return a non-null value.
@@ -138,9 +138,9 @@
       // Case (1), instructions that implicitly indicate receiver/array is not null.
       if (current.throwsOnNullInput()) {
         Value inValue = current.getNonNullInput();
-        if (nonNullValuesBuilder.isMaybeNull(inValue)
+        if (assumedValuesBuilder.isMaybeNull(inValue)
             && isNullableReferenceTypeWithOtherNonDebugUsers(inValue, current)) {
-          nonNullValuesBuilder.addNonNullValueWithUnknownDominance(current, inValue);
+          assumedValuesBuilder.addNonNullValueWithUnknownDominance(current, inValue);
           needsAssumeInstruction = true;
         }
       }
@@ -151,7 +151,7 @@
         if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
           // Case (2) and (3).
           needsAssumeInstruction |=
-              computeNonNullValuesFromSingleTarget(code, invoke, nonNullValuesBuilder);
+              computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
         }
       } else if (current.isFieldGet()) {
         // Case (4), field-get instructions that are guaranteed to read a non-null value.
@@ -163,7 +163,7 @@
             FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
             if (optimizationInfo.getDynamicUpperBoundType() != null
                 && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
-              nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
+              assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
               needsAssumeInstruction = true;
             }
           }
@@ -182,7 +182,7 @@
           assert !instructionIterator.hasNext();
           assert instructionIterator.peekPrevious().isGoto();
           assert blockIterator.peekPrevious() == insertionBlock;
-          computeNonNullValuesInBlock(code, blockIterator, insertionBlock, nonNullValuesBuilder);
+          computeAssumedValuesInBlock(code, blockIterator, insertionBlock, assumedValuesBuilder);
           return;
         }
         if (current.instructionTypeCanThrow()) {
@@ -194,16 +194,16 @@
     If ifInstruction = block.exit().asIf();
     if (ifInstruction != null && ifInstruction.isNonTrivialNullTest()) {
       Value lhs = ifInstruction.lhs();
-      if (nonNullValuesBuilder.isMaybeNull(lhs)
+      if (assumedValuesBuilder.isMaybeNull(lhs)
           && isNullableReferenceTypeWithOtherNonDebugUsers(lhs, ifInstruction)
           && ifInstruction.targetFromNonNullObject().getPredecessors().size() == 1) {
-        nonNullValuesBuilder.addNonNullValueWithUnknownDominance(ifInstruction, lhs);
+        assumedValuesBuilder.addNonNullValueWithUnknownDominance(ifInstruction, lhs);
       }
     }
   }
 
-  private boolean computeNonNullValuesFromSingleTarget(
-      IRCode code, InvokeMethod invoke, NonNullValues.Builder nonNullValuesBuilder) {
+  private boolean computeAssumedValuesFromSingleTarget(
+      IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
     if (singleTarget == null) {
       return false;
@@ -217,7 +217,7 @@
     if (outValue != null
         && optimizationInfo.neverReturnsNull()
         && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
-      nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
+      assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
       needsAssumeInstruction = true;
     }
 
@@ -228,9 +228,9 @@
       for (int i = start; i < invoke.arguments().size(); i++) {
         if (nonNullParamOnNormalExits.get(i)) {
           Value argument = invoke.getArgument(i);
-          if (nonNullValuesBuilder.isMaybeNull(argument)
+          if (assumedValuesBuilder.isMaybeNull(argument)
               && isNullableReferenceTypeWithOtherNonDebugUsers(argument, invoke)) {
-            nonNullValuesBuilder.addNonNullValueWithUnknownDominance(invoke, argument);
+            assumedValuesBuilder.addNonNullValueWithUnknownDominance(invoke, argument);
             needsAssumeInstruction = true;
           }
         }
@@ -239,33 +239,33 @@
     return needsAssumeInstruction;
   }
 
-  private void removeRedundantAssumeInstructions(NonNullValues nonNullValues) {
-    nonNullValues.removeIf(
-        (instruction, nonNullValue) -> {
-          if (nonNullValue.isPhi()) {
+  private void removeRedundantAssumeInstructions(AssumedValues assumedValues) {
+    assumedValues.removeIf(
+        (instruction, assumedValue) -> {
+          if (assumedValue.isPhi()) {
             return false;
           }
-          Instruction definition = nonNullValue.definition;
-          return definition != instruction && nonNullValues.contains(definition, nonNullValue);
+          Instruction definition = assumedValue.definition;
+          return definition != instruction && assumedValues.contains(definition, assumedValue);
         });
   }
 
-  private Map<Instruction, Set<Value>> computeDominanceForNonNullValues(
-      IRCode code, NonNullValues nonNullValues) {
+  private Map<Instruction, Set<Value>> computeDominanceForAssumedValues(
+      IRCode code, AssumedValues assumedValues) {
     Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
     LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
     Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
-    nonNullValues.computeDominance(
-        (instruction, nonNullValue) -> {
-          Set<Value> alreadyNonNullValues = redundantKeys.get(instruction);
-          if (alreadyNonNullValues != null && alreadyNonNullValues.contains(nonNullValue)) {
-            // Returning redundant() will cause the entry (instruction, nonNullValue) to be removed.
-            return NonNullDominance.redundant();
+    assumedValues.computeDominance(
+        (instruction, assumedValue) -> {
+          Set<Value> alreadyAssumedValues = redundantKeys.get(instruction);
+          if (alreadyAssumedValues != null && alreadyAssumedValues.contains(assumedValue)) {
+            // Returning redundant() will cause the entry (instruction, assumedValue) to be removed.
+            return AssumedDominance.redundant();
           }
 
           // If this value is non-null since its definition, then it is known to dominate all users.
-          if (nonNullValue == instruction.outValue()) {
-            return NonNullDominance.everything();
+          if (assumedValue == instruction.outValue()) {
+            return AssumedDominance.everything();
           }
 
           // If we learn that this value is known to be non-null in the same block as it is defined,
@@ -273,12 +273,12 @@
           // check, then the non-null-value is known to dominate all other users than the null check
           // itself.
           BasicBlock block = instruction.getBlock();
-          if (nonNullValue.getBlock() == block
+          if (assumedValue.getBlock() == block
               && block.exit().isGoto()
               && !instruction.getBlock().hasCatchHandlers()) {
             InstructionIterator iterator = instruction.getBlock().iterator();
-            if (!nonNullValue.isPhi()) {
-              iterator.nextUntil(x -> x != nonNullValue.definition);
+            if (!assumedValue.isPhi()) {
+              iterator.nextUntil(x -> x != assumedValue.definition);
               iterator.previous();
             }
             boolean isUsedBeforeInstruction = false;
@@ -287,23 +287,23 @@
               if (current == instruction) {
                 break;
               }
-              if (current.inValues().contains(nonNullValue)
-                  || current.getDebugValues().contains(nonNullValue)) {
+              if (current.inValues().contains(assumedValue)
+                  || current.getDebugValues().contains(assumedValue)) {
                 isUsedBeforeInstruction = true;
                 break;
               }
             }
             if (!isUsedBeforeInstruction) {
-              return NonNullDominance.everythingElse();
+              return AssumedDominance.everythingElse();
             }
           }
 
           // Otherwise, we need a dominator tree to determine which users are dominated.
           BasicBlock insertionBlock = getInsertionBlock(instruction);
 
-          assert nonNullValue.hasPhiUsers()
-              || nonNullValue.uniqueUsers().stream().anyMatch(user -> user != instruction)
-              || nonNullValue.isArgument();
+          assert assumedValue.hasPhiUsers()
+              || assumedValue.uniqueUsers().stream().anyMatch(user -> user != instruction)
+              || assumedValue.isArgument();
 
           // Find all users of the original value that are dominated by either the current block
           // or the new split-off block. Since NPE can be explicitly caught, nullness should be
@@ -313,8 +313,8 @@
               dominatedBlocksCache.computeIfAbsent(
                   insertionBlock, x -> dominatorTree.dominatedBlocks(x, Sets.newIdentityHashSet()));
 
-          NonNullDominance.Builder dominance = NonNullDominance.builder(nonNullValue);
-          for (Instruction user : nonNullValue.uniqueUsers()) {
+          AssumedDominance.Builder dominance = AssumedDominance.builder(assumedValue);
+          for (Instruction user : assumedValue.uniqueUsers()) {
             if (user != instruction && dominatedBlocks.contains(user.getBlock())) {
               if (user.getBlock() == insertionBlock && insertionBlock == block) {
                 Instruction first = block.iterator().nextUntil(x -> x == instruction || x == user);
@@ -329,12 +329,12 @@
               // after the given user in case the user is also a null check for the non-null-value.
               redundantKeys
                   .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
-                  .add(nonNullValue);
+                  .add(assumedValue);
             }
           }
-          for (Phi user : nonNullValue.uniquePhiUsers()) {
+          for (Phi user : assumedValue.uniquePhiUsers()) {
             IntList dominatedPredecessorIndices =
-                findDominatedPredecessorIndexesInPhi(user, nonNullValue, dominatedBlocks);
+                findDominatedPredecessorIndexesInPhi(user, assumedValue, dominatedBlocks);
             if (!dominatedPredecessorIndices.isEmpty()) {
               dominance.addDominatedPhiUser(user, dominatedPredecessorIndices);
             }
@@ -345,30 +345,31 @@
   }
 
   private void removeRedundantDominatedAssumeInstructions(
-      NonNullValues nonNullValues, Map<Instruction, Set<Value>> redundantKeys) {
-    nonNullValues.removeAll(redundantKeys);
+      AssumedValues assumedValues, Map<Instruction, Set<Value>> redundantKeys) {
+    assumedValues.removeAll(redundantKeys);
   }
 
-  private void materializeAssumeInstructions(IRCode code, NonNullValues nonNullValues) {
+  private void materializeAssumeInstructions(IRCode code, AssumedValues assumedValues) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
         new IdentityHashMap<>();
-    nonNullValues.forEach(
-        (instruction, nonNullValue, dominance) -> {
+    assumedValues.forEach(
+        (instruction, assumedValue, assumedValueInfo) -> {
           BasicBlock block = instruction.getBlock();
           BasicBlock insertionBlock = getInsertionBlock(instruction);
 
+          AssumedDominance dominance = assumedValueInfo.getDominance();
           Value newValue =
               code.createValue(
-                  nonNullValue.getType().asReferenceType().asMeetWithNotNull(),
-                  nonNullValue.getLocalInfo());
+                  assumedValue.getType().asReferenceType().asMeetWithNotNull(),
+                  assumedValue.getLocalInfo());
           if (dominance.isEverything()) {
-            nonNullValue.replaceUsers(newValue);
+            assumedValue.replaceUsers(newValue);
           } else if (dominance.isEverythingElse()) {
-            nonNullValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
-            nonNullValue.replacePhiUsers(newValue);
+            assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
+            assumedValue.replacePhiUsers(newValue);
           } else if (dominance.isSomething()) {
-            SomethingNonNullDominance somethingDominance = dominance.asSomething();
+            SomethingAssumedDominance somethingDominance = dominance.asSomething();
             somethingDominance
                 .getDominatedPhiUsers()
                 .forEach(
@@ -376,22 +377,22 @@
                       IntListIterator iterator = indices.iterator();
                       while (iterator.hasNext()) {
                         Value operand = user.getOperand(iterator.nextInt());
-                        if (operand != nonNullValue) {
+                        if (operand != assumedValue) {
                           assert operand.isDefinedByInstructionSatisfying(
                               Instruction::isAssumeNonNull);
                           iterator.remove();
                         }
                       }
                     });
-            nonNullValue.replaceSelectiveUsers(
+            assumedValue.replaceSelectiveUsers(
                 newValue,
                 somethingDominance.getDominatedUsers(),
                 somethingDominance.getDominatedPhiUsers());
           }
           affectedValues.addAll(newValue.affectedValues());
 
-          Assume<NonNullAssumption> assumeInstruction =
-              Assume.createAssumeNonNullInstruction(newValue, nonNullValue, instruction, appView);
+          Assume assumeInstruction =
+              Assume.createAssumeNonNullInstruction(newValue, assumedValue, instruction, appView);
           assumeInstruction.setPosition(instruction.getPosition());
           if (insertionBlock != block) {
             insertionBlock.listIterator(code).add(assumeInstruction);
@@ -431,8 +432,8 @@
   }
 
   private IntList findDominatedPredecessorIndexesInPhi(
-      Phi user, Value knownToBeNonNullValue, Set<BasicBlock> dominatedBlocks) {
-    assert user.getOperands().contains(knownToBeNonNullValue);
+      Phi user, Value assumedValue, Set<BasicBlock> dominatedBlocks) {
+    assert user.getOperands().contains(assumedValue);
     List<Value> operands = user.getOperands();
     List<BasicBlock> predecessors = user.getBlock().getPredecessors();
     assert operands.size() == predecessors.size();
@@ -446,7 +447,7 @@
       BasicBlock predecessor = predecessorIterator.next();
       // When this phi is chosen to be known-to-be-non-null value,
       // check if the corresponding predecessor is dominated by the block where non-null is added.
-      if (operand == knownToBeNonNullValue && dominatedBlocks.contains(predecessor)) {
+      if (operand == assumedValue && dominatedBlocks.contains(predecessor)) {
         predecessorIndexes.add(index);
       }
 
@@ -479,47 +480,70 @@
     return false;
   }
 
-  static class NonNullValues {
+  static class AssumedValueInfo {
+
+    AssumedDominance dominance;
+    NonNullAssumption nonNullAssumption;
+
+    AssumedValueInfo(AssumedDominance dominance) {
+      this.dominance = dominance;
+    }
+
+    AssumedDominance getDominance() {
+      return dominance;
+    }
+
+    void setDominance(AssumedDominance dominance) {
+      this.dominance = dominance;
+    }
+
+    void setNotNull() {
+      nonNullAssumption = NonNullAssumption.get();
+    }
+  }
+
+  static class AssumedValues {
 
     /**
      * A mapping from each instruction to the (in and out) values that are guaranteed to be non-null
      * by the instruction. Each non-null value is subsequently mapped to the set of users that it
      * dominates.
      */
-    Map<Instruction, Map<Value, NonNullDominance>> nonNullValues;
+    Map<Instruction, Map<Value, AssumedValueInfo>> assumedValues;
 
-    public NonNullValues(Map<Instruction, Map<Value, NonNullDominance>> nonNullValues) {
-      this.nonNullValues = nonNullValues;
+    public AssumedValues(Map<Instruction, Map<Value, AssumedValueInfo>> assumedValues) {
+      this.assumedValues = assumedValues;
     }
 
     public static Builder builder() {
       return new Builder();
     }
 
-    void computeDominance(BiFunction<Instruction, Value, NonNullDominance> function) {
-      Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
-          nonNullValues.entrySet().iterator();
+    void computeDominance(BiFunction<Instruction, Value, AssumedDominance> function) {
+      Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
+          assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
-        Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+        Entry<Instruction, Map<Value, AssumedValueInfo>> outerEntry = outerIterator.next();
         Instruction instruction = outerEntry.getKey();
-        Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
-        Iterator<Entry<Value, NonNullDominance>> innerIterator =
+        Map<Value, AssumedValueInfo> dominancePerValue = outerEntry.getValue();
+        Iterator<Entry<Value, AssumedValueInfo>> innerIterator =
             dominancePerValue.entrySet().iterator();
         while (innerIterator.hasNext()) {
-          Entry<Value, NonNullDominance> innerEntry = innerIterator.next();
-          Value nonNullValue = innerEntry.getKey();
-          NonNullDominance dominance = innerEntry.getValue();
+          Entry<Value, AssumedValueInfo> innerEntry = innerIterator.next();
+          Value assumedValue = innerEntry.getKey();
+          AssumedValueInfo assumedValueInfo = innerEntry.getValue();
+          AssumedDominance dominance = assumedValueInfo.dominance;
           if (dominance.isEverything()) {
-            assert nonNullValue.isDefinedByInstructionSatisfying(
-                definition -> definition.outValue() == nonNullValue);
+            assert assumedValue.isDefinedByInstructionSatisfying(
+                definition -> definition.outValue() == assumedValue);
             continue;
           }
           assert dominance.isUnknown();
-          dominance = function.apply(instruction, nonNullValue);
-          if ((dominance.isNothing() && !nonNullValue.isArgument()) || dominance.isUnknown()) {
+          dominance = function.apply(instruction, assumedValue);
+          if ((dominance.isNothing() && !assumedValue.isArgument()) || dominance.isUnknown()) {
             innerIterator.remove();
           } else {
-            innerEntry.setValue(dominance);
+            assumedValueInfo.setDominance(dominance);
           }
         }
         if (dominancePerValue.isEmpty()) {
@@ -528,48 +552,48 @@
       }
     }
 
-    boolean contains(Instruction instruction, Value nonNullValue) {
-      Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
-      return dominancePerValue != null && dominancePerValue.containsKey(nonNullValue);
+    boolean contains(Instruction instruction, Value assumedValue) {
+      Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
+      return dominancePerValue != null && dominancePerValue.containsKey(assumedValue);
     }
 
     boolean isEmpty() {
-      return nonNullValues.isEmpty();
+      return assumedValues.isEmpty();
     }
 
-    void forEach(TriConsumer<Instruction, Value, NonNullDominance> consumer) {
-      nonNullValues.forEach(
+    void forEach(TriConsumer<Instruction, Value, AssumedValueInfo> consumer) {
+      assumedValues.forEach(
           (instruction, dominancePerValue) ->
               dominancePerValue.forEach(
-                  (nonNullValue, dominance) ->
-                      consumer.accept(instruction, nonNullValue, dominance)));
+                  (assumedValue, assumedValueInfo) ->
+                      consumer.accept(instruction, assumedValue, assumedValueInfo)));
     }
 
     void removeAll(Map<Instruction, Set<Value>> keys) {
       keys.forEach(
           (instruction, values) -> {
-            Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
+            Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
             if (dominancePerValue != null) {
               values.forEach(dominancePerValue::remove);
               if (dominancePerValue.isEmpty()) {
-                nonNullValues.remove(instruction);
+                assumedValues.remove(instruction);
               }
             }
           });
     }
 
     void removeIf(BiPredicate<Instruction, Value> predicate) {
-      Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
-          nonNullValues.entrySet().iterator();
+      Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
+          assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
-        Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+        Entry<Instruction, Map<Value, AssumedValueInfo>> outerEntry = outerIterator.next();
         Instruction instruction = outerEntry.getKey();
-        Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
-        Iterator<Entry<Value, NonNullDominance>> innerIterator =
+        Map<Value, AssumedValueInfo> dominancePerValue = outerEntry.getValue();
+        Iterator<Entry<Value, AssumedValueInfo>> innerIterator =
             dominancePerValue.entrySet().iterator();
         while (innerIterator.hasNext()) {
-          Value nonNullValue = innerIterator.next().getKey();
-          if (predicate.test(instruction, nonNullValue)) {
+          Value assumedValue = innerIterator.next().getKey();
+          if (predicate.test(instruction, assumedValue)) {
             innerIterator.remove();
           }
         }
@@ -581,40 +605,42 @@
 
     static class Builder {
 
-      private final Map<Instruction, Map<Value, NonNullDominance>> nonNullValues =
+      private final Map<Instruction, Map<Value, AssumedValueInfo>> assumedValues =
           new LinkedHashMap<>();
 
       // Used to avoid unnecessary block splitting during phase 1.
-      private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
+      private final Set<Value> assumedValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
 
-      private void add(Instruction instruction, Value nonNullValue, NonNullDominance dominance) {
-        nonNullValues
+      private void addNonNullValue(
+          Instruction instruction, Value nonNullValue, AssumedDominance dominance) {
+        assumedValues
             .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
-            .put(nonNullValue, dominance);
+            .computeIfAbsent(nonNullValue, ignore -> new AssumedValueInfo(dominance))
+            .setNotNull();
         if (dominance.isEverything()) {
-          nonNullValuesKnownToDominateAllUsers.add(nonNullValue);
+          assumedValuesKnownToDominateAllUsers.add(nonNullValue);
         }
       }
 
       void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
-        add(instruction, nonNullValue, NonNullDominance.everything());
+        addNonNullValue(instruction, nonNullValue, AssumedDominance.everything());
       }
 
       void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
-        add(instruction, nonNullValue, NonNullDominance.unknown());
+        addNonNullValue(instruction, nonNullValue, AssumedDominance.unknown());
       }
 
       public boolean isMaybeNull(Value value) {
-        return !nonNullValuesKnownToDominateAllUsers.contains(value);
+        return !assumedValuesKnownToDominateAllUsers.contains(value);
       }
 
-      public NonNullValues build() {
-        return new NonNullValues(nonNullValues);
+      public AssumedValues build() {
+        return new AssumedValues(assumedValues);
       }
     }
   }
 
-  abstract static class NonNullDominance {
+  abstract static class AssumedDominance {
 
     boolean isEverything() {
       return false;
@@ -632,7 +658,7 @@
       return false;
     }
 
-    SomethingNonNullDominance asSomething() {
+    SomethingAssumedDominance asSomething() {
       return null;
     }
 
@@ -640,76 +666,76 @@
       return false;
     }
 
-    public static Builder builder(Value nonNullValue) {
-      return new Builder(nonNullValue);
+    public static Builder builder(Value assumedValue) {
+      return new Builder(assumedValue);
     }
 
-    public static EverythingNonNullDominance everything() {
-      return EverythingNonNullDominance.getInstance();
+    public static EverythingAssumedDominance everything() {
+      return EverythingAssumedDominance.getInstance();
     }
 
-    public static EverythingElseNonNullDominance everythingElse() {
-      return EverythingElseNonNullDominance.getInstance();
+    public static EverythingElseAssumedDominance everythingElse() {
+      return EverythingElseAssumedDominance.getInstance();
     }
 
-    public static NothingNonNullDominance nothing() {
-      return NothingNonNullDominance.getInstance();
+    public static NothingAssumedDominance nothing() {
+      return NothingAssumedDominance.getInstance();
     }
 
-    public static UnknownNonNullDominance redundant() {
+    public static UnknownAssumedDominance redundant() {
       return unknown();
     }
 
-    public static SomethingNonNullDominance something(
+    public static SomethingAssumedDominance something(
         Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
-      return new SomethingNonNullDominance(dominatedUsers, dominatedPhiUsers);
+      return new SomethingAssumedDominance(dominatedUsers, dominatedPhiUsers);
     }
 
-    public static UnknownNonNullDominance unknown() {
-      return UnknownNonNullDominance.getInstance();
+    public static UnknownAssumedDominance unknown() {
+      return UnknownAssumedDominance.getInstance();
     }
 
     static class Builder {
 
-      private final Value nonNullValue;
+      private final Value assumedValue;
 
       private final Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
       private final Map<Phi, IntList> dominatedPhiUsers = new IdentityHashMap<>();
 
-      private Builder(Value nonNullValue) {
-        this.nonNullValue = nonNullValue;
+      private Builder(Value assumedValue) {
+        this.assumedValue = assumedValue;
       }
 
       void addDominatedUser(Instruction user) {
-        assert nonNullValue.uniqueUsers().contains(user);
+        assert assumedValue.uniqueUsers().contains(user);
         assert !dominatedUsers.contains(user);
         dominatedUsers.add(user);
       }
 
       void addDominatedPhiUser(Phi user, IntList dominatedPredecessorIndices) {
-        assert nonNullValue.uniquePhiUsers().contains(user);
+        assert assumedValue.uniquePhiUsers().contains(user);
         assert !dominatedPhiUsers.containsKey(user);
         dominatedPhiUsers.put(user, dominatedPredecessorIndices);
       }
 
-      NonNullDominance build() {
+      AssumedDominance build() {
         if (dominatedUsers.isEmpty() && dominatedPhiUsers.isEmpty()) {
           return nothing();
         }
-        assert dominatedUsers.size() < nonNullValue.uniqueUsers().size()
-            || dominatedPhiUsers.size() < nonNullValue.uniquePhiUsers().size();
+        assert dominatedUsers.size() < assumedValue.uniqueUsers().size()
+            || dominatedPhiUsers.size() < assumedValue.uniquePhiUsers().size();
         return something(dominatedUsers, dominatedPhiUsers);
       }
     }
   }
 
-  static class EverythingNonNullDominance extends NonNullDominance {
+  static class EverythingAssumedDominance extends AssumedDominance {
 
-    private static final EverythingNonNullDominance INSTANCE = new EverythingNonNullDominance();
+    private static final EverythingAssumedDominance INSTANCE = new EverythingAssumedDominance();
 
-    private EverythingNonNullDominance() {}
+    private EverythingAssumedDominance() {}
 
-    public static EverythingNonNullDominance getInstance() {
+    public static EverythingAssumedDominance getInstance() {
       return INSTANCE;
     }
 
@@ -719,14 +745,14 @@
     }
   }
 
-  static class EverythingElseNonNullDominance extends NonNullDominance {
+  static class EverythingElseAssumedDominance extends AssumedDominance {
 
-    private static final EverythingElseNonNullDominance INSTANCE =
-        new EverythingElseNonNullDominance();
+    private static final EverythingElseAssumedDominance INSTANCE =
+        new EverythingElseAssumedDominance();
 
-    private EverythingElseNonNullDominance() {}
+    private EverythingElseAssumedDominance() {}
 
-    public static EverythingElseNonNullDominance getInstance() {
+    public static EverythingElseAssumedDominance getInstance() {
       return INSTANCE;
     }
 
@@ -736,13 +762,13 @@
     }
   }
 
-  static class NothingNonNullDominance extends NonNullDominance {
+  static class NothingAssumedDominance extends AssumedDominance {
 
-    private static final NothingNonNullDominance INSTANCE = new NothingNonNullDominance();
+    private static final NothingAssumedDominance INSTANCE = new NothingAssumedDominance();
 
-    private NothingNonNullDominance() {}
+    private NothingAssumedDominance() {}
 
-    public static NothingNonNullDominance getInstance() {
+    public static NothingAssumedDominance getInstance() {
       return INSTANCE;
     }
 
@@ -752,12 +778,12 @@
     }
   }
 
-  static class SomethingNonNullDominance extends NonNullDominance {
+  static class SomethingAssumedDominance extends AssumedDominance {
 
     private final Set<Instruction> dominatedUsers;
     private final Map<Phi, IntList> dominatedPhiUsers;
 
-    SomethingNonNullDominance(
+    SomethingAssumedDominance(
         Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
       this.dominatedUsers = dominatedUsers;
       this.dominatedPhiUsers = dominatedPhiUsers;
@@ -777,18 +803,18 @@
     }
 
     @Override
-    SomethingNonNullDominance asSomething() {
+    SomethingAssumedDominance asSomething() {
       return this;
     }
   }
 
-  static class UnknownNonNullDominance extends NonNullDominance {
+  static class UnknownAssumedDominance extends AssumedDominance {
 
-    private static final UnknownNonNullDominance INSTANCE = new UnknownNonNullDominance();
+    private static final UnknownAssumedDominance INSTANCE = new UnknownAssumedDominance();
 
-    private UnknownNonNullDominance() {}
+    private UnknownAssumedDominance() {}
 
-    public static UnknownNonNullDominance getInstance() {
+    public static UnknownAssumedDominance getInstance() {
       return INSTANCE;
     }
 
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 d05d64e..9708120 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
@@ -19,8 +19,6 @@
 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.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -112,7 +110,7 @@
     SingleResolutionResult resolutionResult =
         appView
             .appInfo()
-            .resolveMethod(invokedMethod, invoke.isInvokeInterface())
+            .resolveMethod(invokedMethod, invoke.getInterfaceBit())
             .asSingleResolution();
     if (resolutionResult == null) {
       return;
@@ -309,7 +307,7 @@
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    List<Assume<?>> assumeInstructions = new LinkedList<>();
+    List<Assume> assumeInstructions = new LinkedList<>();
     List<Instruction> constants = new LinkedList<>();
     int argumentsSeen = 0;
     InstructionListIterator iterator = code.entryBlock().listIterator(code);
@@ -356,7 +354,7 @@
         specializedArg = code.createValue(originalArg.getType());
         affectedValues.addAll(originalArg.affectedValues());
         originalArg.replaceUsers(specializedArg);
-        Assume<DynamicTypeAssumption> assumeType =
+        Assume assumeType =
             Assume.createAssumeDynamicTypeInstruction(
                 dynamicUpperBoundType, null, specializedArg, originalArg, instr, appView);
         assumeType.setPosition(instr.getPosition());
@@ -372,7 +370,7 @@
               code.createValue(specializedArg.getType().asReferenceType().asMeetWithNotNull());
           affectedValues.addAll(specializedArg.affectedValues());
           specializedArg.replaceUsers(nonNullArg);
-          Assume<NonNullAssumption> assumeNotNull =
+          Assume assumeNotNull =
               Assume.createAssumeNonNullInstruction(nonNullArg, specializedArg, instr, appView);
           assumeNotNull.setPosition(instr.getPosition());
           assumeInstructions.add(assumeNotNull);
@@ -387,7 +385,7 @@
             + code.method().method.getArity()
             + ", static: "
             + code.method().isStatic();
-    // After packed Argument instructions, add Assume<?> and constant instructions.
+    // After packed Argument instructions, add Assume and constant instructions.
     assert !iterator.peekPrevious().isArgument();
     iterator.previous();
     assert iterator.peekPrevious().isArgument();
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 52844be..172a6b3 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
@@ -176,8 +176,8 @@
 
   public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
     // We need to update the types of all values whose definitions depend on a non-null value.
-    // This is needed to preserve soundness of the types after the Assume<NonNullAssumption>
-    // instructions have been removed.
+    // This is needed to preserve soundness of the types after the Assume instructions have been
+    // removed.
     //
     // As an example, consider a check-cast instruction on the form "z = (T) y". If y used to be
     // defined by a NonNull instruction, then the type analysis could have used this information
@@ -201,7 +201,7 @@
       //
       //   x.foo()
       if (instruction.isAssume()) {
-        Assume<?> assumeInstruction = instruction.asAssume();
+        Assume assumeInstruction = instruction.asAssume();
         Value src = assumeInstruction.src();
         Value dest = assumeInstruction.outValue();
 
@@ -1507,8 +1507,8 @@
           TypeElement dynamicType =
               aliasedValue
                   .definition
-                  .asAssumeDynamicType()
-                  .getAssumption()
+                  .asAssume()
+                  .getDynamicTypeAssumption()
                   .getDynamicUpperBoundType();
           if (dynamicType.isDefinitelyNull()) {
             result = InstanceOfResult.FALSE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 5893a3b..c4bf62f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -113,6 +114,7 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
@@ -167,7 +169,7 @@
     }
 
     // Abort inlining attempt if method -> target access is not right.
-    if (!inliner.hasInliningAccess(method, singleTarget)) {
+    if (resolutionResult.isAccessibleFrom(method, appView.appInfo()).isPossiblyFalse()) {
       whyAreYouNotInliningReporter.reportInaccessible();
       return false;
     }
@@ -254,6 +256,7 @@
   @Override
   public InlineAction computeInlining(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
@@ -277,7 +280,8 @@
       return null;
     }
 
-    if (!passesInliningConstraints(invoke, singleTarget, reason, whyAreYouNotInliningReporter)) {
+    if (!passesInliningConstraints(
+        invoke, resolutionResult, singleTarget, reason, whyAreYouNotInliningReporter)) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 8117d6c..7752d6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -78,7 +77,7 @@
         // ...
         // non_null_rcv <- non-null rcv_c  // <- Update the input rcv to the non-null, too.
         if (current.isAssumeNonNull()) {
-          Assume<NonNullAssumption> nonNull = current.asAssumeNonNull();
+          Assume nonNull = current.asAssume();
           Instruction origin = nonNull.origin();
           if (origin.isInvokeInterface()
               && !origin.asInvokeInterface().getReceiver().hasLocalInfo()
@@ -126,7 +125,11 @@
               DexMethod invokedMethod = invoke.getInvokedMethod();
               DexEncodedMethod newSingleTarget =
                   InvokeVirtual.lookupSingleTarget(
-                      appView, context, invoke.getReceiver(), invokedMethod);
+                      appView,
+                      context,
+                      invoke.getReceiver().getDynamicUpperBoundType(appView),
+                      invoke.getReceiver().getDynamicLowerBoundType(appView),
+                      invokedMethod);
               if (newSingleTarget == singleTarget) {
                 it.replaceCurrentInstruction(
                     new InvokeVirtual(invokedMethod, invoke.outValue(), invoke.arguments()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 4aabc5b..6e2d9c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
@@ -143,7 +142,7 @@
       outValue.replaceUsers(specializedOutValue);
 
       // Insert AssumeDynamicType instruction.
-      Assume<DynamicTypeAssumption> assumeInstruction =
+      Assume assumeInstruction =
           Assume.createAssumeDynamicTypeInstruction(
               dynamicUpperBoundType,
               dynamicLowerBoundType,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index a48dece..eea6b7c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -42,6 +43,7 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
@@ -60,15 +62,18 @@
   @Override
   public InlineAction computeInlining(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    return computeForInvoke(invoke, whyAreYouNotInliningReporter);
+    return computeForInvoke(invoke, resolutionResult, whyAreYouNotInliningReporter);
   }
 
   private InlineAction computeForInvoke(
-      InvokeMethod invoke, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Inliner.InliningInfo info = invokesToInline.get(invoke);
     if (info == null) {
       return null;
@@ -80,7 +85,7 @@
     // with neverInline() flag.
     assert !info.target.getDefinition().getOptimizationInfo().neverInline();
     assert passesInliningConstraints(
-        invoke, info.target, Reason.FORCE, whyAreYouNotInliningReporter);
+        invoke, resolutionResult, info.target, Reason.FORCE, whyAreYouNotInliningReporter);
     return new InlineAction(info.target, invoke, Reason.FORCE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index abf12f7..10d17e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -139,7 +139,7 @@
 
           SingleResolutionResult resolutionResult =
               appInfoWithLiveness
-                  .resolveMethod(invoke.getInvokedMethod(), invoke.isInvokeInterface())
+                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index fe6cbb4..17cea8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -199,29 +200,6 @@
     return false;
   }
 
-  boolean hasInliningAccess(ProgramMethod context, ProgramMethod target) {
-    if (!isVisibleWithFlags(target.getHolderType(), context, target.getDefinition().accessFlags)) {
-      return false;
-    }
-    // The class needs also to be visible for us to have access.
-    return isVisibleWithFlags(target.getHolderType(), context, target.getHolder().accessFlags);
-  }
-
-  private boolean isVisibleWithFlags(DexType target, ProgramMethod context, AccessFlags<?> flags) {
-    if (flags.isPublic()) {
-      return true;
-    }
-    if (flags.isPrivate()) {
-      return NestUtils.sameNest(target, context.getHolderType(), appView);
-    }
-    if (flags.isProtected()) {
-      return appView.appInfo().isSubtype(context.getHolderType(), target)
-          || target.isSamePackage(context.getHolderType());
-    }
-    // package-private
-    return target.isSamePackage(context.getHolderType());
-  }
-
   public synchronized boolean isDoubleInlineSelectedTarget(ProgramMethod method) {
     return doubleInlineSelectedTargets.contains(method);
   }
@@ -975,6 +953,17 @@
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
+
+          SingleResolutionResult resolutionResult =
+              appView
+                  .appInfo()
+                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+                  .asSingleResolution();
+          if (resolutionResult == null) {
+            continue;
+          }
+
+          // TODO(b/156853206): Should not duplicate resolution.
           ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
           if (singleTarget == null) {
             WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(invoke, appView, context);
@@ -989,6 +978,7 @@
           InlineAction action =
               oracle.computeInlining(
                   invoke,
+                  resolutionResult,
                   singleTarget,
                   context,
                   classInitializationAnalysis,
@@ -1084,7 +1074,7 @@
             blockIterator.next();
           }
         } else if (current.isAssumeDynamicType()) {
-          assumeDynamicTypeRemover.removeIfMarked(current.asAssumeDynamicType(), iterator);
+          assumeDynamicTypeRemover.removeIfMarked(current.asAssume(), iterator);
         }
       }
     }
@@ -1160,7 +1150,7 @@
 
     // Add non-null IRs only to the inlinee blocks.
     if (options.enableNonNullTracking) {
-      Assumer nonNullTracker = new NonNullTracker(appView);
+      Assumer nonNullTracker = new AssumeInserter(appView);
       applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks, timing);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index a9e4736..5518ab7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
@@ -23,12 +24,14 @@
 
   boolean passesInliningConstraints(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   InlineAction computeInlining(
       InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
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 8222958..51c1c01 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
@@ -357,13 +357,10 @@
       return;
     }
 
-    // Check if the field is pinned. In that case, it could be written by reflection.
-    if (appView.appInfo().isPinned(target.field)) {
-      return;
-    }
-
     AbstractValue abstractValue;
-    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+    if (field.type.isAlwaysNull(appView)) {
+      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = target.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !target.isStatic()) {
         AbstractValue abstractReceiverValue =
@@ -485,12 +482,15 @@
 
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
-    DexEncodedField target = appView.appInfo().resolveField(current.getField()).getResolvedField();
-    if (target == null || appView.appInfo().isFieldRead(target)) {
+    DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
+    if (field == null || field.isStatic()) {
       return;
     }
 
-    if (target.isStatic()) {
+    // If the field is read, we can't remove the instance-put unless the value of the field is known
+    // to be null (in which case the instance-put is a no-op because it assigns the field the same
+    // value as its default value).
+    if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) {
       return;
     }
 
@@ -500,11 +500,14 @@
   private void replaceStaticPutByInitClassIfNeverRead(
       IRCode code, InstructionListIterator iterator, StaticPut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
-    if (field == null || appView.appInfo().isFieldRead(field)) {
+    if (field == null || !field.isStatic()) {
       return;
     }
 
-    if (!field.isStatic()) {
+    // If the field is read, we can't remove the static-put unless the value of the field is known
+    // to be null (in which case the static-put is a no-op because it assigns the field the same
+    // value as its default value).
+    if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index 3cd05c5..9db947a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -57,7 +57,7 @@
           assert encodedMethod.isPrivateMethod();
           // Call to private method which has now to be interface/virtual
           // (Now call to nest member private method).
-          if (invoke.isInterface()) {
+          if (invoke.getInterfaceBit()) {
             iterator.replaceCurrentInstruction(
                 new InvokeInterface(method, invoke.outValue(), invoke.inValues()));
           } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 0ac7f7b..d9eb7f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -66,14 +66,11 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.ProgramMethodEquivalence;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodMultisetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodMultiset;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.Multiset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -98,10 +95,9 @@
  *       where each list of methods corresponds to methods containing an outlining candidate.
  *   <li>Second, {@link Outliner#selectMethodsForOutlining()} is called to retain the lists of
  *       methods found in the first step that are large enough (see {@link InternalOptions#outline}
- *       {@link OutlineOptions#threshold}), and the methods to be further analyzed for outlining is
- *       returned by {@link Outliner#buildMethodsSelectedForOutlining}. Each selected method is then
- *       converted back to IR and passed to {@link Outliner#identifyOutlineSites(IRCode)}, which
- *       then stores concrete outlining candidates in {@link Outliner#outlineSites}.
+ *       {@link OutlineOptions#threshold}). Each selected method is then converted back to IR and
+ *       passed to {@link Outliner#identifyOutlineSites(IRCode)}, which then stores concrete
+ *       outlining candidates in {@link Outliner#outlineSites}.
  *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
  *       support class</em> containing a static helper method for each outline candidate that occurs
  *       frequently enough. Each selected method is then converted to IR, passed to {@link
@@ -112,10 +108,8 @@
 public class Outliner {
 
   /** Result of first step (see {@link Outliner#createOutlineMethodIdentifierGenerator()}. */
-  private final List<Multiset<Wrapper<ProgramMethod>>> candidateMethodLists = new ArrayList<>();
-  /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
-  private final LongLivedProgramMethodSetBuilder<?> methodsSelectedForOutlining =
-      LongLivedProgramMethodSetBuilder.create();
+  private final List<LongLivedProgramMethodMultisetBuilder> candidateMethodLists =
+      new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
   /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
@@ -1138,12 +1132,12 @@
   // TODO(sgjesse): This does not take several usages in the same method into account.
   private class OutlineMethodIdentifier extends OutlineSpotter {
 
-    private final Map<Outline, Multiset<Wrapper<ProgramMethod>>> candidateMap;
+    private final Map<Outline, LongLivedProgramMethodMultisetBuilder> candidateMap;
 
     OutlineMethodIdentifier(
         ProgramMethod method,
         BasicBlock block,
-        Map<Outline, Multiset<Wrapper<ProgramMethod>>> candidateMap) {
+        Map<Outline, LongLivedProgramMethodMultisetBuilder> candidateMap) {
       super(method, block);
       this.candidateMap = candidateMap;
     }
@@ -1151,14 +1145,12 @@
     @Override
     protected void handle(int start, int end, Outline outline) {
       synchronized (candidateMap) {
-        candidateMap
-            .computeIfAbsent(outline, this::addOutlineMethodList)
-            .add(ProgramMethodEquivalence.get().wrap(method));
+        candidateMap.computeIfAbsent(outline, this::addOutlineMethodList).add(method);
       }
     }
 
-    private Multiset<Wrapper<ProgramMethod>> addOutlineMethodList(Outline outline) {
-      Multiset<Wrapper<ProgramMethod>> result = HashMultiset.create();
+    private LongLivedProgramMethodMultisetBuilder addOutlineMethodList(Outline outline) {
+      LongLivedProgramMethodMultisetBuilder result = LongLivedProgramMethodMultisetBuilder.create();
       candidateMethodLists.add(result);
       return result;
     }
@@ -1284,14 +1276,19 @@
     // out-value of invokes to null), this map must not be used except for identifying methods
     // potentially relevant to outlining. OutlineMethodIdentifier will add method lists to
     // candidateMethodLists whenever it adds an entry to candidateMap.
-    Map<Outline, Multiset<Wrapper<ProgramMethod>>> candidateMap = new HashMap<>();
+    Map<Outline, LongLivedProgramMethodMultisetBuilder> candidateMap = new HashMap<>();
     assert candidateMethodLists.isEmpty();
     assert outlineMethodIdentifierGenerator == null;
     outlineMethodIdentifierGenerator =
         code -> {
-          assert !code.method().getCode().isOutlineCode();
+          ProgramMethod context = code.context();
+          assert !context.getDefinition().getCode().isOutlineCode();
+          if (appView.options().featureSplitConfiguration != null
+              && appView.options().featureSplitConfiguration.isInFeature(context.getHolder())) {
+            return;
+          }
           for (BasicBlock block : code.blocks) {
-            new OutlineMethodIdentifier(code.context(), block, candidateMap).process();
+            new OutlineMethodIdentifier(context, block, candidateMap).process();
           }
         };
   }
@@ -1304,31 +1301,26 @@
   }
 
   public void identifyOutlineSites(IRCode code) {
-    assert !code.method().getCode().isOutlineCode();
-    DexProgramClass clazz = code.context().getHolder();
-    if (appView.options().featureSplitConfiguration != null
-        && appView.options().featureSplitConfiguration.isInFeature(clazz)) {
-      return;
-    }
+    ProgramMethod context = code.context();
+    assert !context.getDefinition().getCode().isOutlineCode();
+    assert appView.options().featureSplitConfiguration == null
+        || !appView.options().featureSplitConfiguration.isInFeature(context.getHolder());
     for (BasicBlock block : code.blocks) {
-      new OutlineSiteIdentifier(code.context(), block).process();
+      new OutlineSiteIdentifier(context, block).process();
     }
   }
 
-  public boolean selectMethodsForOutlining() {
-    assert methodsSelectedForOutlining.isEmpty();
+  public ProgramMethodSet selectMethodsForOutlining() {
+    ProgramMethodSet methodsSelectedForOutlining = ProgramMethodSet.create();
     assert outlineSites.isEmpty();
-    for (Multiset<Wrapper<ProgramMethod>> outlineMethods : candidateMethodLists) {
+    for (LongLivedProgramMethodMultisetBuilder outlineMethods : candidateMethodLists) {
       if (outlineMethods.size() >= appView.options().outline.threshold) {
-        outlineMethods.forEach(wrapper -> methodsSelectedForOutlining.add(wrapper.get()));
+        ProgramMethodMultiset multiset = outlineMethods.build(appView);
+        multiset.forEachEntry((method, ignore) -> methodsSelectedForOutlining.add(method));
       }
     }
     candidateMethodLists.clear();
-    return !methodsSelectedForOutlining.isEmpty();
-  }
-
-  public ProgramMethodSet buildMethodsSelectedForOutlining() {
-    return methodsSelectedForOutlining.build(appView);
+    return methodsSelectedForOutlining;
   }
 
   public DexProgramClass buildOutlinerClass(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 6077882..85f0274 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -9,9 +9,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -23,7 +21,6 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -34,7 +31,6 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
-import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
@@ -103,17 +99,9 @@
   private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final TypeChecker typeChecker;
-
-  private int numberOfInstanceGetOrInstancePutWithNullReceiver = 0;
-  private int numberOfArrayInstructionsWithNullArray = 0;
-  private int numberOfInvokesWithNullArgument = 0;
-  private int numberOfInvokesWithNullReceiver = 0;
-  private int numberOfMonitorWithNullReceiver = 0;
 
   public UninstantiatedTypeOptimization(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.typeChecker = new TypeChecker(appView);
   }
 
   public UninstantiatedTypeOptimizationGraphLense run(
@@ -353,32 +341,12 @@
         if (instruction.throwsOnNullInput()) {
           Value couldBeNullValue = instruction.getNonNullInput();
           if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.context())) {
-            if (instruction.isInstanceGet() || instruction.isInstancePut()) {
-              ++numberOfInstanceGetOrInstancePutWithNullReceiver;
-            } else if (instruction.isInvokeMethodWithReceiver()) {
-              ++numberOfInvokesWithNullReceiver;
-            } else if (instruction.isArrayGet()
-                || instruction.isArrayPut()
-                || instruction.isArrayLength()) {
-              ++numberOfArrayInstructionsWithNullArray;
-            } else if (instruction.isMonitor()) {
-              ++numberOfMonitorWithNullReceiver;
-            } else {
-              assert false;
-            }
             instructionIterator.replaceCurrentInstructionWithThrowNull(
                 appView, code, blockIterator, blocksToBeRemoved, valuesToNarrow);
             continue;
           }
         }
-        if (instruction.isFieldInstruction()) {
-          rewriteFieldInstruction(
-              instruction.asFieldInstruction(),
-              instructionIterator,
-              code,
-              assumeDynamicTypeRemover,
-              valuesToNarrow);
-        } else if (instruction.isInvokeMethod()) {
+        if (instruction.isInvokeMethod()) {
           rewriteInvoke(
               instruction.asInvokeMethod(),
               blockIterator,
@@ -421,75 +389,6 @@
     return true;
   }
 
-  public void logResults() {
-    assert Log.ENABLED;
-    Log.info(
-        getClass(),
-        "Number of instance-get/instance-put with null receiver: %s",
-        numberOfInstanceGetOrInstancePutWithNullReceiver);
-    Log.info(
-        getClass(),
-        "Number of array instructions with null reference: %s",
-        numberOfArrayInstructionsWithNullArray);
-    Log.info(
-        getClass(), "Number of invokes with null argument: %s", numberOfInvokesWithNullArgument);
-    Log.info(
-        getClass(), "Number of invokes with null receiver: %s", numberOfInvokesWithNullReceiver);
-    Log.info(
-        getClass(), "Number of monitor with null receiver: %s", numberOfMonitorWithNullReceiver);
-  }
-
-  // instance-{get|put} with a null receiver has already been rewritten to `throw null`.
-  // At this point, field-instruction whose target field type is uninstantiated will be handled.
-  private void rewriteFieldInstruction(
-      FieldInstruction instruction,
-      InstructionListIterator instructionIterator,
-      IRCode code,
-      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
-      Set<Value> affectedValues) {
-    ProgramMethod context = code.context();
-    DexField field = instruction.getField();
-    DexType fieldType = field.type;
-    if (fieldType.isAlwaysNull(appView)) {
-      // TODO(b/123857022): Should be possible to use definitionFor().
-      DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-      if (encodedField == null) {
-        return;
-      }
-
-      boolean instructionCanBeRemoved = !instruction.instructionInstanceCanThrow(appView, context);
-
-      BasicBlock block = instruction.getBlock();
-      if (instruction.isFieldPut()) {
-        if (!typeChecker.checkFieldPut(instruction)) {
-          // Broken type hierarchy. See FieldTypeTest#test_brokenTypeHierarchy.
-          assert appView.options().testing.allowTypeErrors;
-          return;
-        }
-
-        // We know that the right-hand side must be null, so this is a no-op.
-        if (instructionCanBeRemoved) {
-          instructionIterator.removeOrReplaceByDebugLocalRead();
-        }
-      } else {
-        if (instructionCanBeRemoved) {
-          // Replace the field read by the constant null.
-          assumeDynamicTypeRemover.markUsersForRemoval(instruction.outValue());
-          affectedValues.addAll(instruction.outValue().affectedValues());
-          instructionIterator.replaceCurrentInstruction(code.createConstNull());
-        } else {
-          replaceOutValueByNull(
-              instruction, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
-        }
-      }
-
-      if (block.hasCatchHandlers()) {
-        // This block can no longer throw.
-        block.getCatchHandlers().getUniqueTargets().forEach(BasicBlock::unlinkCatchHandler);
-      }
-    }
-  }
-
   // invoke instructions with a null receiver has already been rewritten to `throw null`.
   // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
   // the invocation with `throw null` if an argument is known to be null and the method is going to
@@ -514,7 +413,6 @@
         if (argument.isAlwaysNull(appView) && facts.get(i)) {
           instructionIterator.replaceCurrentInstructionWithThrowNull(
               appView, code, blockIterator, blocksToBeRemoved, affectedValues);
-          ++numberOfInvokesWithNullArgument;
           return;
         }
       }
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 5e378c2..da8c4cc 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
@@ -19,8 +19,10 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -281,6 +283,16 @@
 
         if (user.isInvokeMethod()) {
           InvokeMethod invokeMethod = user.asInvokeMethod();
+          SingleResolutionResult resolutionResult =
+              appView
+                  .appInfo()
+                  .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
+                  .asSingleResolution();
+          if (resolutionResult == null) {
+            return user; // Not eligible.
+          }
+
+          // TODO(b/156853206): Avoid duplicating resolution.
           DexEncodedMethod singleTargetMethod = invokeMethod.lookupSingleTarget(appView, method);
           if (singleTargetMethod == null) {
             return user; // Not eligible.
@@ -320,7 +332,7 @@
             InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
             InliningInfo inliningInfo =
                 isEligibleDirectVirtualMethodCall(
-                    invoke, singleTarget, indirectUsers, defaultOracle);
+                    invoke, resolutionResult, singleTarget, indirectUsers, defaultOracle);
             if (inliningInfo != null) {
               methodCallsOnInstance.put(invoke, inliningInfo);
               continue;
@@ -529,8 +541,11 @@
             continue;
           }
 
+          ClassTypeElement exactReceiverType =
+              ClassTypeElement.create(eligibleClass.type, Nullability.definitelyNotNull(), appView);
           ProgramMethod singleTarget =
-              invoke.lookupSingleProgramTarget(appView, method, eligibleInstance);
+              invoke.lookupSingleProgramTarget(
+                  appView, method, exactReceiverType, exactReceiverType);
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -875,6 +890,7 @@
 
   private InliningInfo isEligibleDirectVirtualMethodCall(
       InvokeMethodWithReceiver invoke,
+      SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
       Set<Instruction> indirectUsers,
       Supplier<InliningOracle> defaultOracle) {
@@ -892,7 +908,11 @@
     if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
       InliningOracle inliningOracle = defaultOracle.get();
       if (!inliningOracle.passesInliningConstraints(
-          invoke, singleTarget, Reason.SIMPLE, NopWhyAreYouNotInliningReporter.getInstance())) {
+          invoke,
+          resolutionResult,
+          singleTarget,
+          Reason.SIMPLE,
+          NopWhyAreYouNotInliningReporter.getInstance())) {
         return null;
       }
     }
@@ -916,13 +936,16 @@
       Pair<Type, DexMethod> invokeInfo = eligibility.callsReceiver.get(0);
       Type invokeType = invokeInfo.getFirst();
       DexMethod indirectlyInvokedMethod = invokeInfo.getSecond();
-      ResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOn(eligibleClass, indirectlyInvokedMethod);
-      if (!resolutionResult.isSingleResolution()) {
+      SingleResolutionResult indirectResolutionResult =
+          appView
+              .appInfo()
+              .resolveMethodOn(eligibleClass, indirectlyInvokedMethod)
+              .asSingleResolution();
+      if (indirectResolutionResult == null) {
         return null;
       }
       ProgramMethod indirectSingleTarget =
-          resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
+          indirectResolutionResult.getResolutionPair().asProgramMethod();
       if (!isEligibleIndirectVirtualMethodCall(
           indirectlyInvokedMethod, invokeType, indirectSingleTarget)) {
         return null;
@@ -1191,24 +1214,28 @@
         return false;
       }
 
+      SingleResolutionResult resolutionResult =
+          appView
+              .appInfo()
+              .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+              .asSingleResolution();
+      if (resolutionResult == null) {
+        return false;
+      }
+
       // Check if the method is inline-able by standard inliner.
-      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method);
+      // TODO(b/156853206): Should not duplicate resolution.
+      ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method);
       if (singleTarget == null) {
         return false;
       }
 
-      DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(singleTarget));
-      if (holder == null) {
-        return false;
-      }
-
-      ProgramMethod singleTargetMethod = new ProgramMethod(holder, singleTarget);
-
       InliningOracle oracle = defaultOracle.get();
       InlineAction inlineAction =
           oracle.computeInlining(
               invoke,
-              singleTargetMethod,
+              resolutionResult,
+              singleTarget,
               method,
               ClassInitializationAnalysis.trivial(),
               NopWhyAreYouNotInliningReporter.getInstance());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c179446..379fc28 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -446,20 +446,16 @@
       if (dexClass.type != factory.enumType) {
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
-      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may
-      // be interesting to model. A the moment rewrite only Enum#ordinal() and Enum#valueOf.
-      if (debugLogEnabled) {
-        if (singleTarget == factory.enumMethods.compareTo) {
-          return Reason.COMPARE_TO_INVOKE;
-        }
-        if (singleTarget == factory.enumMethods.name) {
-          return Reason.NAME_INVOKE;
-        }
-        if (singleTarget == factory.enumMethods.toString) {
-          return Reason.TO_STRING_INVOKE;
-        }
-      }
-      if (singleTarget == factory.enumMethods.ordinal) {
+      // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
+      if (singleTarget == factory.enumMethods.compareTo) {
+        return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.equals) {
+        return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.name) {
+        return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.toString) {
+        return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.ordinal) {
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMethods.constructor) {
         // Enum constructor call is allowed only if first call of an enum initializer.
@@ -664,8 +660,6 @@
     VALUE_OF_INVOKE,
     VALUES_INVOKE,
     COMPARE_TO_INVOKE,
-    TO_STRING_INVOKE,
-    NAME_INVOKE,
     UNSUPPORTED_LIBRARY_CALL,
     MISSING_INFO_MAP,
     INVALID_FIELD_PUT,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6f6fba1..541cd92 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayAccess;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -46,32 +47,32 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
 
 public class EnumUnboxingRewriter {
 
   public static final String ENUM_UNBOXING_UTILITY_CLASS_NAME = "$r8$EnumUnboxingUtility";
-  public static final String ENUM_UNBOXING_UTILITY_ORDINAL = "$enumboxing$ordinal";
-  private static final String ENUM_UNBOXING_UTILITY_VALUES = "$enumboxing$values";
+  public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
   private static final int REQUIRED_CLASS_FILE_VERSION = 52;
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final EnumValueInfoMapCollection enumsToUnbox;
-  private final Map<DexMethod, DexEncodedMethod> extraUtilityMethods = new ConcurrentHashMap<>();
+  private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
   private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
 
   private final DexMethod ordinalUtilityMethod;
+  private final DexMethod equalsUtilityMethod;
+  private final DexMethod compareToUtilityMethod;
   private final DexMethod valuesUtilityMethod;
 
-  private boolean requiresOrdinalUtilityMethod = false;
-  private boolean requiresValuesUtilityMethod = false;
-
   EnumUnboxingRewriter(AppView<AppInfoWithLiveness> appView, Set<DexType> enumsToUnbox) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
@@ -81,16 +82,28 @@
     }
     this.enumsToUnbox = builder.build();
 
+    // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
     this.ordinalUtilityMethod =
         factory.createMethod(
             factory.enumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_ORDINAL);
+            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
+    this.equalsUtilityMethod =
+        factory.createMethod(
+            factory.enumUnboxingUtilityType,
+            factory.createProto(factory.booleanType, factory.intType, factory.intType),
+            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
+    this.compareToUtilityMethod =
+        factory.createMethod(
+            factory.enumUnboxingUtilityType,
+            factory.createProto(factory.intType, factory.intType, factory.intType),
+            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
+    // Custom methods for generated field $VALUES initialization.
     this.valuesUtilityMethod =
         factory.createMethod(
             factory.enumUnboxingUtilityType,
             factory.createProto(factory.intArrayType, factory.intType),
-            ENUM_UNBOXING_UTILITY_VALUES);
+            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
   }
 
   public EnumValueInfoMapCollection getEnumsToUnbox() {
@@ -104,6 +117,7 @@
       return Sets.newIdentityHashSet();
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
+    Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
@@ -113,14 +127,28 @@
       if (instruction.isInvokeMethodWithReceiver()) {
         InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-        if (invokedMethod == factory.enumMethods.ordinal
-            && isEnumToUnboxOrInt(invokeMethod.getReceiver().getType())) {
-          instruction =
-              new InvokeStatic(
-                  ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
-          iterator.replaceCurrentInstruction(instruction);
-          requiresOrdinalUtilityMethod = true;
-          continue;
+        DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
+        if (enumType != null) {
+          if (invokedMethod == factory.enumMethods.ordinal) {
+            replaceEnumInvoke(
+                iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
+            continue;
+          } else if (invokedMethod == factory.enumMethods.equals) {
+            replaceEnumInvoke(
+                iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
+            continue;
+          } else if (invokedMethod == factory.enumMethods.compareTo) {
+            replaceEnumInvoke(
+                iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
+            continue;
+          } else if (invokedMethod == factory.enumMethods.name
+              || invokedMethod == factory.enumMethods.toString) {
+            DexMethod toStringMethod = computeDefaultToStringUtilityMethod(enumType);
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(
+                    toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+            continue;
+          }
         }
         // TODO(b/147860220): rewrite also other enum methods.
       } else if (instruction.isInvokeStatic()) {
@@ -138,11 +166,13 @@
               rewrittenOutValue = code.createValue(TypeElement.getInt());
               affectedPhis.addAll(outValue.uniquePhiUsers());
             }
-            iterator.replaceCurrentInstruction(
+            InvokeStatic invoke =
                 new InvokeStatic(
                     valueOfMethod,
                     rewrittenOutValue,
-                    Collections.singletonList(invokeStatic.inValues().get(1))));
+                    Collections.singletonList(invokeStatic.inValues().get(1)));
+            iterator.replaceCurrentInstruction(invoke);
+            convertedEnums.put(invoke, enumType);
             continue;
           }
         }
@@ -162,33 +192,39 @@
           affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
           EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
           if (enumValueInfo == null && staticGet.getField().name == factory.enumValuesFieldName) {
-            requiresValuesUtilityMethod = true;
+            utilityMethods.computeIfAbsent(
+                valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
             DexField fieldValues = createValuesField(holder);
             extraUtilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
             DexMethod methodValues = createValuesMethod(holder);
-            extraUtilityMethods.computeIfAbsent(
+            utilityMethods.computeIfAbsent(
                 methodValues,
                 m -> computeValuesEncodedMethod(m, fieldValues, enumValueInfoMap.size()));
             Value rewrittenOutValue =
                 code.createValue(
                     ArrayTypeElement.create(TypeElement.getInt(), definitelyNotNull()));
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of()));
+            InvokeStatic invoke =
+                new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of());
+            iterator.replaceCurrentInstruction(invoke);
+            convertedEnums.put(invoke, holder);
           } else {
             // Replace by ordinal + 1 for null check (null is 0).
             assert enumValueInfo != null
                 : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
-            iterator.replaceCurrentInstruction(
-                code.createIntConstant(enumValueInfo.convertToInt()));
+            ConstNumber intConstant = code.createIntConstant(enumValueInfo.convertToInt());
+            iterator.replaceCurrentInstruction(intConstant);
+            convertedEnums.put(intConstant, holder);
           }
         }
       }
       // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
       if (instruction.isArrayAccess()) {
         ArrayAccess arrayAccess = instruction.asArrayAccess();
-        if (shouldRewriteArrayAccess(arrayAccess)) {
+        DexType enumType = getEnumTypeOrNull(arrayAccess);
+        if (enumType != null) {
           instruction = arrayAccess.withMemberType(MemberType.INT);
           iterator.replaceCurrentInstruction(instruction);
+          convertedEnums.put(instruction, enumType);
         }
         assert validateArrayAccess(arrayAccess);
       }
@@ -197,6 +233,17 @@
     return affectedPhis;
   }
 
+  private void replaceEnumInvoke(
+      InstructionListIterator iterator,
+      InvokeMethodWithReceiver invokeMethod,
+      DexMethod method,
+      Function<DexMethod, DexEncodedMethod> synthesizor) {
+    utilityMethods.computeIfAbsent(method, synthesizor);
+    Instruction instruction =
+        new InvokeStatic(method, invokeMethod.outValue(), invokeMethod.arguments());
+    iterator.replaceCurrentInstruction(instruction);
+  }
+
   private boolean validateArrayAccess(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
     if (arrayType == null) {
@@ -209,14 +256,16 @@
     return true;
   }
 
-  private boolean isEnumToUnboxOrInt(TypeElement type) {
+  private DexType getEnumTypeOrNull(Value receiver, Map<Instruction, DexType> convertedEnums) {
+    TypeElement type = receiver.getType();
     if (type.isInt()) {
-      return true;
+      return convertedEnums.get(receiver.definition);
     }
     if (!type.isClassType()) {
-      return false;
+      return null;
     }
-    return enumsToUnbox.containsEnum(type.asClassType().getClassType());
+    DexType enumType = type.asClassType().getClassType();
+    return enumsToUnbox.containsEnum(enumType) ? enumType : null;
   }
 
   public String compatibleName(DexType type) {
@@ -266,22 +315,36 @@
             factory.enumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.stringType),
             "valueOf" + compatibleName(type));
-    extraUtilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
+    utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
     return valueOf;
   }
 
-  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+  private DexMethod computeDefaultToStringUtilityMethod(DexType type) {
+    assert enumsToUnbox.containsEnum(type);
+    DexMethod toString =
+        factory.createMethod(
+            factory.enumUnboxingUtilityType,
+            factory.createProto(factory.stringType, factory.intType),
+            "toString" + compatibleName(type));
+    utilityMethods.computeIfAbsent(toString, m -> synthesizeToStringUtilityMethod(m, type));
+    return toString;
+  }
+
+  private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
     if (arrayType == null) {
       assert arrayAccess.array().getType().isNullType();
-      return false;
+      return null;
     }
     if (arrayType.getNesting() != 1) {
-      return false;
+      return null;
     }
     TypeElement baseType = arrayType.getBaseType();
-    return baseType.isClassType()
-        && enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
+    if (!baseType.isClassType()) {
+      return null;
+    }
+    DexType classType = baseType.asClassType().getClassType();
+    return enumsToUnbox.containsEnum(classType) ? classType : null;
   }
 
   void synthesizeEnumUnboxingUtilityMethods(
@@ -289,16 +352,9 @@
       throws ExecutionException {
     // Synthesize a class which holds various utility methods that may be called from the IR
     // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
-    List<DexEncodedMethod> requiredMethods = new ArrayList<>(extraUtilityMethods.values());
+    List<DexEncodedMethod> requiredMethods = new ArrayList<>(utilityMethods.values());
     // Sort for deterministic order.
     requiredMethods.sort((m1, m2) -> m1.method.name.slowCompareTo(m2.method.name));
-    if (requiresOrdinalUtilityMethod) {
-      requiredMethods.add(synthesizeOrdinalMethod());
-    }
-    if (requiresValuesUtilityMethod) {
-      requiredMethods.add(synthesizeValuesUtilityMethod());
-    }
-    // TODO(b/147860220): synthesize also other enum methods.
     if (requiredMethods.isEmpty()) {
       return;
     }
@@ -340,6 +396,17 @@
         DexProgramClass::checksumFromType);
   }
 
+  private DexEncodedMethod synthesizeToStringUtilityMethod(DexMethod method, DexType enumType) {
+    CfCode cfCode =
+        new EnumUnboxingCfCodeProvider.EnumUnboxingDefaultToStringCfCodeProvider(
+                appView,
+                factory.enumUnboxingUtilityType,
+                enumType,
+                enumsToUnbox.getEnumValueInfoMap(enumType))
+            .generateCfCode();
+    return synthesizeUtilityMethod(cfCode, method, false);
+  }
+
   private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
@@ -348,14 +415,7 @@
                 enumType,
                 enumsToUnbox.getEnumValueInfoMap(enumType))
             .generateCfCode();
-    return new DexEncodedMethod(
-        method,
-        synthesizedMethodAccessFlags(false),
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        cfCode,
-        REQUIRED_CLASS_FILE_VERSION,
-        true);
+    return synthesizeUtilityMethod(cfCode, method, false);
   }
 
   // TODO(b/150178516): Add a test for this case.
@@ -374,6 +434,19 @@
     return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod, false);
   }
 
+  private DexEncodedMethod synthesizeEqualsMethod() {
+    CfCode cfCode =
+        EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
+    return synthesizeUtilityMethod(cfCode, equalsUtilityMethod, false);
+  }
+
+  private DexEncodedMethod synthesizeCompareToMethod() {
+    CfCode cfCode =
+        EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
+            appView.options(), compareToUtilityMethod);
+    return synthesizeUtilityMethod(cfCode, compareToUtilityMethod, false);
+  }
+
   private DexEncodedMethod synthesizeValuesUtilityMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_values(appView.options(), valuesUtilityMethod);
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 a1a6638..cac5801 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
@@ -1157,7 +1157,7 @@
       nullCheckedBlocks.clear();
       for (Instruction user : argument.uniqueUsers()) {
         if (user.isAssumeNonNull()) {
-          nullCheckedBlocks.add(user.asAssumeNonNull().getBlock());
+          nullCheckedBlocks.add(user.asAssume().getBlock());
         }
         if (user.isIf()
             && user.asIf().isZeroTest()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index fa0e683..cdc1793 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -69,7 +68,7 @@
     outValue.replaceUsers(specializedOutValue);
 
     // Insert AssumeDynamicType instruction.
-    Assume<DynamicTypeAssumption> assumeInstruction =
+    Assume assumeInstruction =
         Assume.createAssumeDynamicTypeInstruction(
             dynamicUpperBoundType, null, specializedOutValue, outValue, invoke, appView);
     assumeInstruction.setPosition(appView.options().debug ? invoke.getPosition() : Position.none());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 499c975..323dd11 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -40,7 +40,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -103,7 +102,7 @@
   }
 
   final Map<CandidateInfo, LongLivedProgramMethodSetBuilder<?>> referencedFrom =
-      new IdentityHashMap<>();
+      new ConcurrentHashMap<>();
 
   // The map storing all the potential candidates for staticizing.
   final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
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 ac915a5..0e0e1c9 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
@@ -43,6 +43,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.BiMap;
@@ -228,7 +229,10 @@
 
       ProgramMethodSet referencedFrom;
       if (classStaticizer.referencedFrom.containsKey(info)) {
-        referencedFrom = classStaticizer.referencedFrom.remove(info).build(appView);
+        LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
+            classStaticizer.referencedFrom.remove(info);
+        assert referencedFromBuilder != null;
+        referencedFrom = referencedFromBuilder.build(appView);
         materializedReferencedFromCollections.put(info, referencedFrom);
       } else {
         referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 53122b0..a01be35 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -767,7 +767,7 @@
         }
         // If there are aliasing instructions, they should be removed before new-instance.
         if (instr.isAssume() && buildersToRemove.contains(instr.outValue().getAliasedValue())) {
-          Assume<?> assumeInstruction = instr.asAssume();
+          Assume assumeInstruction = instr.asAssume();
           Value src = assumeInstruction.src();
           Value dest = assumeInstruction.outValue();
           dest.replaceUsers(src);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index f9f66e8..61bdbbc 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -36,6 +37,49 @@
     super(appView, holder);
   }
 
+  public static class EnumUnboxingDefaultToStringCfCodeProvider extends EnumUnboxingCfCodeProvider {
+
+    private DexType enumType;
+    private EnumValueInfoMap map;
+
+    public EnumUnboxingDefaultToStringCfCodeProvider(
+        AppView<?> appView, DexType holder, DexType enumType, EnumValueInfoMap map) {
+      super(appView, holder);
+      this.enumType = enumType;
+      this.map = map;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      // Generated static method, for class com.x.MyEnum {A,B} would look like:
+      // String UtilityClass#com.x.MyEnum_toString(int i) {
+      // if (i == 1) { return "A";}
+      // if (i == 2) { return "B";}
+      // throw null;
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (i == 1) { return "A";}
+      // if (i == 2) { return "B";}
+      map.forEach(
+          (field, enumValueInfo) -> {
+            CfLabel dest = new CfLabel();
+            instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
+            instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
+            instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+            instructions.add(new CfConstString(field.name));
+            instructions.add(new CfReturn(ValueType.OBJECT));
+            instructions.add(dest);
+          });
+
+      // throw null;
+      instructions.add(new CfConstNull());
+      instructions.add(new CfThrow());
+
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
   public static class EnumUnboxingValueOfCfCodeProvider extends EnumUnboxingCfCodeProvider {
 
     private DexType enumType;
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
index e329c4d..b8355c3 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -21,7 +21,9 @@
 import com.android.tools.r8.shaking.ProguardPathList;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
@@ -292,19 +294,23 @@
     }
 
     public RelocatorCommand build() throws CompilationFailedException {
-      try {
-        if (printHelp || printVersion) {
-          return new RelocatorCommand(printHelp, printVersion);
-        }
-        reporter.failIfPendingErrors();
-        validate();
-        reporter.failIfPendingErrors();
-        DexItemFactory factory = new DexItemFactory();
-        return new RelocatorCommand(
-            mapping.build(), app.build(), reporter, factory, consumer, threadCount);
-      } catch (AbortException e) {
-        throw new CompilationFailedException(e);
-      }
+      Box<RelocatorCommand> result = new Box<>();
+      ExceptionUtils.withCompilationHandler(
+          reporter,
+          () -> {
+            if (printHelp || printVersion) {
+              result.set(new RelocatorCommand(printHelp, printVersion));
+              return;
+            }
+            reporter.failIfPendingErrors();
+            validate();
+            reporter.failIfPendingErrors();
+            DexItemFactory factory = new DexItemFactory();
+            result.set(
+                new RelocatorCommand(
+                    mapping.build(), app.build(), reporter, factory, consumer, threadCount));
+          });
+      return result.get();
     }
 
     // Helper to signify an error.
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 6e8f93b..8a54cca 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -242,6 +242,9 @@
   /** Set of types that were found to be missing during the first round of tree shaking. */
   private Set<DexType> initialMissingTypes;
 
+  /** Set of types that was pruned during the first round of tree shaking. */
+  private Set<DexType> initialPrunedTypes;
+
   /** Mapping from each unused interface to the set of live types that implements the interface. */
   private final Map<DexProgramClass, Set<DexProgramClass>> unusedInterfaceTypes =
       new IdentityHashMap<>();
@@ -448,6 +451,11 @@
     this.initialMissingTypes = initialMissingTypes;
   }
 
+  public void setInitialPrunedTypes(Set<DexType> initialPrunedTypes) {
+    assert mode.isFinalTreeShaking();
+    this.initialPrunedTypes = initialPrunedTypes;
+  }
+
   public void addDeadProtoTypeCandidate(DexType type) {
     assert type.isProgramType(appView);
     addDeadProtoTypeCandidate(appView.definitionFor(type).asProgramClass());
@@ -2878,13 +2886,12 @@
 
     // Verify the references on the pruned application after type synthesis.
     assert verifyReferences(app);
+    assert verifyMissingTypes();
 
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             app,
             SetUtils.mapIdentityHashSet(deadProtoTypeCandidates, DexProgramClass::getType),
-            // TODO(b/155959821): We should be able to assert that missing types is a subset of
-            //   initialMissingTypes + synthesized types.
             mode.isFinalTreeShaking()
                 ? Sets.union(initialMissingTypes, missingTypes)
                 : missingTypes,
@@ -2959,6 +2966,22 @@
             });
   }
 
+  private boolean verifyMissingTypes() {
+    if (initialMissingTypes == null) {
+      assert !mode.isFinalTreeShaking();
+      return true;
+    }
+    missingTypes.forEach(
+        missingType -> {
+          assert initialMissingTypes.contains(missingType)
+                  // TODO(b/157107464): See if we can clean this up.
+                  || initialPrunedTypes.contains(missingType)
+                  || missingType.isD8R8SynthesizedClassType()
+              : missingType;
+        });
+    return true;
+  }
+
   private boolean verifyReferences(DexApplication app) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     for (DexProgramClass clazz : liveTypes.getItems()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 743f0df..5ff9c97 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -23,12 +23,14 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Set<DexType> initialMissingTypes) {
+      Set<DexType> initialMissingTypes,
+      Set<DexType> initialPrunedTypes) {
     Enqueuer enqueuer =
         new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
     enqueuer.setInitialMissingTypes(initialMissingTypes);
+    enqueuer.setInitialPrunedTypes(initialPrunedTypes);
     return enqueuer;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
index 2e34cd7..59a6a14 100644
--- a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -18,7 +18,7 @@
 
 public class CfLineToMethodMapper {
 
-  private final Map<String, Int2ReferenceOpenHashMap<String>> sourceMethodMapping = new HashMap<>();
+  private Map<String, Int2ReferenceOpenHashMap<String>> sourceMethodMapping = null;
   private final AndroidApp inputApp;
   private static final String NAME_DESCRIPTOR_SEPARATOR = ";;";
 
@@ -28,7 +28,8 @@
 
   public String lookupNameAndDescriptor(String binaryName, int lineNumber)
       throws ResourceException {
-    if (sourceMethodMapping.isEmpty()) {
+    if (sourceMethodMapping == null) {
+      sourceMethodMapping = new HashMap<>();
       readLineNumbersFromClassFiles();
     }
     Int2ReferenceOpenHashMap<String> lineMappings = sourceMethodMapping.get(binaryName);
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index e5ba3f1..28d1ed5 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.position.Position;
+import com.google.common.collect.ObjectArrays;
 import java.io.IOException;
 import java.nio.file.FileSystemException;
 import java.nio.file.Paths;
@@ -97,6 +98,10 @@
       suppressed.add(innerMostCause);
       innerMostCause = innerMostCause.getCause();
     }
+    // Add the full stack as a suppressed stack on the inner cause.
+    if (topMostException != innerMostCause) {
+      innerMostCause.addSuppressed(topMostException);
+    }
 
     // If no abort is seen, the exception is not reported, so report it now.
     if (!hasBeenReported) {
@@ -116,10 +121,9 @@
         new CompilationFailedException(message.toString(), innerMostCause);
     // Replace its stack by the cause stack and insert version info at the top.
     String filename = "Version_" + Version.LABEL + ".java";
-    rethrow.setStackTrace(
-        new StackTraceElement[] {
-          new StackTraceElement(Version.class.getSimpleName(), "fakeStackEntry", filename, 0)
-        });
+    StackTraceElement versionElement =
+        new StackTraceElement(Version.class.getSimpleName(), "fakeStackEntry", filename, 0);
+    rethrow.setStackTrace(ObjectArrays.concat(versionElement, rethrow.getStackTrace()));
     return rethrow;
   }
 
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 43a53c7..8c92acf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1160,7 +1160,8 @@
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
     public boolean allowInvokeErrors = false;
     public boolean disableL8AnnotationRemoval = false;
-    public boolean allowClassInlinerGracefulExit = true;
+    public boolean allowClassInlinerGracefulExit =
+        System.getProperty("com.android.tools.r8.disallowClassInlinerGracefulExit") == null;
     public boolean reportUnusedProguardConfigurationRules = false;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
diff --git a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
new file mode 100644
index 0000000..ac07151
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.Predicate;
+
+public class ObjectUtils {
+
+  public static <T> boolean getBooleanOrElse(T object, Predicate<T> fn, boolean orElse) {
+    if (object != null) {
+      return fn.test(object);
+    }
+    return orElse;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index f7f9d1f..f251b0c 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.Unreachable;
 
 public class Reporter implements DiagnosticsHandler {
 
@@ -56,14 +55,13 @@
    */
   public RuntimeException fatalError(Diagnostic error) {
     error(error);
-    failIfPendingErrors();
-    throw new Unreachable();
+    throw abort;
   }
 
   /** @throws AbortException if any error was reported. */
   public synchronized void failIfPendingErrors() {
     if (abort != null) {
-      throw abort;
+      throw new RuntimeException(abort);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMultisetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMultisetBuilder.java
new file mode 100644
index 0000000..f3ba06b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMultisetBuilder.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+public class LongLivedProgramMethodMultisetBuilder {
+
+  private final Multiset<DexMethod> backing = HashMultiset.create();
+
+  private LongLivedProgramMethodMultisetBuilder() {}
+
+  public static LongLivedProgramMethodMultisetBuilder create() {
+    return new LongLivedProgramMethodMultisetBuilder();
+  }
+
+  public void add(ProgramMethod method) {
+    backing.add(method.getReference());
+  }
+
+  public int size() {
+    return backing.size();
+  }
+
+  public ProgramMethodMultiset build(AppView<AppInfoWithLiveness> appView) {
+    ProgramMethodMultiset result = ProgramMethodMultiset.createHash();
+    backing.forEachEntry(
+        (oldMethod, occurrences) -> {
+          DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod);
+          DexProgramClass holder = appView.definitionForHolder(method).asProgramClass();
+          result.createAndAdd(holder, holder.lookupMethod(method), occurrences);
+        });
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java
new file mode 100644
index 0000000..ae8974c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ProgramMethodEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import java.util.function.ObjIntConsumer;
+
+public class ProgramMethodMultiset {
+
+  private final Multiset<Wrapper<ProgramMethod>> backing;
+
+  private ProgramMethodMultiset(Multiset<Wrapper<ProgramMethod>> backing) {
+    this.backing = backing;
+  }
+
+  public static ProgramMethodMultiset createHash() {
+    return new ProgramMethodMultiset(HashMultiset.create());
+  }
+
+  public void createAndAdd(DexProgramClass holder, DexEncodedMethod method, int occurrences) {
+    backing.add(wrap(new ProgramMethod(holder, method)), occurrences);
+  }
+
+  public void forEachEntry(ObjIntConsumer<ProgramMethod> consumer) {
+    backing.forEachEntry((wrapper, occurrences) -> consumer.accept(wrapper.get(), occurrences));
+  }
+
+  private static Wrapper<ProgramMethod> wrap(ProgramMethod method) {
+    return ProgramMethodEquivalence.get().wrap(method);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index e621784..9e26ecd 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -29,6 +29,8 @@
   // Ordered list of injar entries.
   private List<Path> injars = new ArrayList<>();
 
+  private int minApiLevel = -1;
+
   private DXTestBuilder(TestState state) {
     super(state, D8Command.builder(), Backend.DEX);
   }
@@ -51,6 +53,9 @@
       Path outJar = dxOutputFolder.resolve("output.jar");
 
       List<String> args = new ArrayList<>();
+      if (minApiLevel >= 0) {
+        args.add("--min-sdk-version=" + minApiLevel);
+      }
       args.add("--output=" + outJar.toString());
       args.addAll(injars.stream().map(Path::toString).collect(Collectors.toList()));
       ProcessResult result =
@@ -118,4 +123,10 @@
     injars.addAll(files);
     return self();
   }
+
+  @Override
+  public DXTestBuilder setMinApi(int minApiLevel) {
+    this.minApiLevel = minApiLevel;
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index ca9abd4..9a36a04 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.junit.rules.TemporaryFolder;
@@ -57,6 +58,7 @@
   private final KotlinTargetVersion targetVersion;
   private final List<Path> sources = new ArrayList<>();
   private final List<Path> classpath = new ArrayList<>();
+  private final List<String> additionalArguments = new ArrayList<>();
   private boolean useJvmAssertions;
   private Path output = null;
 
@@ -84,6 +86,11 @@
     return new KotlinCompilerTool(jdk, state, kotlinCompiler, kotlinTargetVersion);
   }
 
+  public KotlinCompilerTool addArguments(String... arguments) {
+    Collections.addAll(additionalArguments, arguments);
+    return this;
+  }
+
   public KotlinCompilerTool addSourceFiles(Path... files) {
     return addSourceFiles(Arrays.asList(files));
   }
@@ -181,6 +188,7 @@
           .map(Path::toString)
           .collect(Collectors.joining(isWindows() ? ";" : ":")));
     }
+    cmdline.addAll(additionalArguments);
     ProcessBuilder builder = new ProcessBuilder(cmdline);
     return ToolHelper.runProcess(builder);
   }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 9463a49..ba1d485 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -234,7 +233,7 @@
   }
 
   @Override
-  public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
+  public ProguardTestBuilder setMinApi(int minApiLevel) {
     if (backend == Backend.DEX) {
       throw new Unimplemented("No support for setting min api");
     }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 4f8806c..b5c518c 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -120,6 +120,52 @@
   }
 
   @Test
+  public void passFeatureSplit() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
+    Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
+    Path library = ToolHelper.getDefaultAndroidJar();
+    Path output = working.resolve("classes.dex");
+    Path featureOutput = working.resolve("feature.zip");
+    assertFalse(Files.exists(output));
+    assertFalse(Files.exists(featureOutput));
+    ProcessResult result =
+        ToolHelper.forkR8(
+            working,
+            input.toString(),
+            "--lib",
+            library.toAbsolutePath().toString(),
+            "--feature",
+            inputFeature.toAbsolutePath().toString(),
+            featureOutput.toAbsolutePath().toString(),
+            "--no-tree-shaking");
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(Files.exists(output));
+    assertTrue(Files.exists(featureOutput));
+  }
+
+  @Test
+  public void featureOnlyOneArgument() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
+    Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
+    Path library = ToolHelper.getDefaultAndroidJar();
+    Path output = working.resolve("classes.dex");
+    assertFalse(Files.exists(output));
+    ProcessResult result =
+        ToolHelper.forkR8(
+            working,
+            input.toString(),
+            "--lib",
+            library.toAbsolutePath().toString(),
+            "--no-tree-shaking",
+            "--feature",
+            inputFeature.toAbsolutePath().toString());
+    assertNotEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stderr.contains("Missing parameter for"));
+  }
+
+  @Test
   public void flagsFile() throws Throwable {
     Path working = temp.getRoot().toPath();
     Path library = ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 8ae9c0f..b7869e4 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -215,17 +215,16 @@
   }
 
   public T setMinApi(AndroidApiLevel minApiLevel) {
-    assert builder.getMinApiLevel() > 0 || this.defaultMinApiLevel != null
-        : "Tests must use this method to set min API level, and not"
-            + " BaseCompilerCommand.Builder.setMinApiLevel()";
     if (backend == Backend.DEX) {
-      this.defaultMinApiLevel = null;
-      builder.setMinApiLevel(minApiLevel.getLevel());
+      return setMinApi(minApiLevel.getLevel());
     }
     return self();
   }
 
   public T setMinApi(int minApiLevel) {
+    assert builder.getMinApiLevel() > 0 || this.defaultMinApiLevel != null
+        : "Tests must use this method to set min API level, and not"
+            + " BaseCompilerCommand.Builder.setMinApiLevel()";
     if (backend == Backend.DEX) {
       this.defaultMinApiLevel = null;
       builder.setMinApiLevel(minApiLevel);
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 0cab77c..ac3ef82 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -7,6 +7,8 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.not;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.hamcrest.Matcher;
 
@@ -34,13 +36,29 @@
 
   // Match exact.
 
-  TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers);
+  default TestDiagnosticMessages assertDiagnosticsMatch(Matcher matcher) {
+    return assertDiagnosticsMatch(Collections.singletonList(matcher));
+  }
 
-  TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers);
+  TestDiagnosticMessages assertDiagnosticsMatch(Collection<Matcher<Diagnostic>> matchers);
 
-  TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers);
+  default TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic> matcher) {
+    return assertInfosMatch(Collections.singletonList(matcher));
+  }
 
-  TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers);
+  TestDiagnosticMessages assertInfosMatch(Collection<Matcher<Diagnostic>> matchers);
+
+  default TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic> matcher) {
+    return assertWarningsMatch(Collections.singletonList(matcher));
+  }
+
+  TestDiagnosticMessages assertWarningsMatch(Collection<Matcher<Diagnostic>> matchers);
+
+  default TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic> matcher) {
+    return assertErrorsMatch(Collections.singletonList(matcher));
+  }
+
+  TestDiagnosticMessages assertErrorsMatch(Collection<Matcher<Diagnostic>> matchers);
 
   // Match one.
 
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 2635732..19a700d 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -174,7 +174,7 @@
   }
 
   private static void assertDiagnosticsMatch(
-      Iterable<Diagnostic> diagnostics, String tag, List<Matcher<Diagnostic>> matchers) {
+      Iterable<Diagnostic> diagnostics, String tag, Collection<Matcher<Diagnostic>> matchers) {
     // Match is unordered, but we make no attempts to find the maximum match.
     int diagnosticsCount = 0;
     Set<Diagnostic> matchedDiagnostics = new HashSet<>();
@@ -236,26 +236,26 @@
   }
 
   @Override
-  public TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers) {
-    assertDiagnosticsMatch(getAllDiagnostics(), "diagnostics", Arrays.asList(matchers));
+  public TestDiagnosticMessages assertDiagnosticsMatch(Collection<Matcher<Diagnostic>> matchers) {
+    assertDiagnosticsMatch(getAllDiagnostics(), "diagnostics", matchers);
     return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers) {
-    assertDiagnosticsMatch(getInfos(), "infos", Arrays.asList(matchers));
+  public TestDiagnosticMessages assertInfosMatch(Collection<Matcher<Diagnostic>> matchers) {
+    assertDiagnosticsMatch(getInfos(), "infos", matchers);
     return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers) {
-    assertDiagnosticsMatch(getWarnings(), "warnings", Arrays.asList(matchers));
+  public TestDiagnosticMessages assertWarningsMatch(Collection<Matcher<Diagnostic>> matchers) {
+    assertDiagnosticsMatch(getWarnings(), "warnings", matchers);
     return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers) {
-    assertDiagnosticsMatch(getErrors(), "errors", Arrays.asList(matchers));
+  public TestDiagnosticMessages assertErrorsMatch(Collection<Matcher<Diagnostic>> matchers) {
+    assertDiagnosticsMatch(getErrors(), "errors", matchers);
     return this;
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f19c7ce..3d00718 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -229,17 +229,16 @@
     ART_7_0_0_HOST(Version.V7_0_0, Kind.HOST),
     ART_8_1_0_TARGET(Version.V8_1_0, Kind.TARGET),
     ART_8_1_0_HOST(Version.V8_1_0, Kind.HOST),
+    ART_DEFAULT(Version.DEFAULT, Kind.HOST),
     ART_9_0_0_TARGET(Version.V9_0_0, Kind.TARGET),
     ART_9_0_0_HOST(Version.V9_0_0, Kind.HOST),
     ART_10_0_0_TARGET(Version.V10_0_0, Kind.TARGET),
-    ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST),
-    ART_DEFAULT(Version.DEFAULT, Kind.HOST);
+    ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
         Arrays.stream(DexVm.values()).collect(ImmutableMap.toImmutableMap(
             DexVm::toString, Function.identity()));
 
-
     public enum Version {
       V4_0_4("4.0.4"),
       V4_4_4("4.4.4"),
@@ -247,9 +246,9 @@
       V6_0_1("6.0.1"),
       V7_0_0("7.0.0"),
       V8_1_0("8.1.0"),
+      DEFAULT("default"),
       V9_0_0("9.0.0"),
-      V10_0_0("10.0.0"),
-      DEFAULT("default");
+      V10_0_0("10.0.0");
 
       Version(String shortName) {
         this.shortName = shortName;
@@ -260,7 +259,7 @@
       }
 
       public boolean isLatest() {
-        return this == DEFAULT;
+        return this == V10_0_0;
       }
 
       public boolean isNewerThan(Version other) {
@@ -286,7 +285,7 @@
       }
 
       public static Version last() {
-        return DEFAULT;
+        return V10_0_0;
       }
 
       static {
@@ -896,6 +895,7 @@
     return dexVm == DexVm.ART_DEFAULT;
   }
 
+  @Deprecated
   public static DexVm getDexVm() {
     String artVersion = System.getProperty("dex_vm");
     if (artVersion == null) {
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index eca0441..069a504 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -90,10 +90,6 @@
     return dexVmVersion.isAtLeast(Version.V7_0_0);
   }
 
-  public static boolean fromAndroidO(Version dexVmVersion) {
-    return dexVmVersion.isAtLeast(Version.DEFAULT);
-  }
-
   private static List<Path> findAllJarsIn(Path root) {
     try {
       return Files.walk(root)
diff --git a/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java b/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java
index bfc400d..fbb29e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java
+++ b/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java
@@ -4,47 +4,73 @@
 
 package com.android.tools.r8.desugar.b72538146;
 
-import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderOrEqualThan;
-import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-@RunWith(VmTestRunner.class)
+@RunWith(Parameterized.class)
 public class B72538146 extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public B72538146(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
-  @IgnoreIfVmOlderOrEqualThan(Version.V6_0_1)
   public void test() throws Exception {
-    // Build the main app from source compiled separately using the Android API for classloading.
-    AndroidApp.Builder builder = AndroidApp.builder();
-    builder.addProgramFile(
-        Paths.get("build/test/examplesAndroidApi/classes/classloader/Runner.class"));
-    AndroidApp app = compileWithD8(builder.build());
-
     // Compile the parent and child applications into separate dex applications.
-    Path parent = temp.newFolder("parent").toPath().resolve("classes.zip");
-    Path child = temp.newFolder("child").toPath().resolve("classes.zip");
-    AndroidApp parentApp = readClasses(
-        Parent.class,
-        Parent.Inner1.class,
-        Parent.Inner2.class,
-        Parent.Inner3.class,
-        Parent.Inner4.class);
-    compileWithD8(parentApp).write(parent, OutputMode.DexIndexed);
+    List<Class<?>> parentClasses =
+        ImmutableList.of(
+            Parent.class,
+            Parent.Inner1.class,
+            Parent.Inner2.class,
+            Parent.Inner3.class,
+            Parent.Inner4.class);
 
-    AndroidApp childApp = readClasses(Child.class);
-    compileWithD8(childApp).write(child, OutputMode.DexIndexed);
+    Path parent =
+        testForD8()
+            .addProgramClasses(parentClasses)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
+    Path child =
+        testForD8()
+            .addProgramClasses(Child.class)
+            .addClasspathClasses(parentClasses)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
 
     // Run the classloader test loading the two dex applications.
-    String result = runOnArt(app, "classloader.Runner",
-        parent.toString(), child.toString(), "com.android.tools.r8.desugar.b72538146.Child");
-    assertEquals("SUCCESS", result);
+    testForD8()
+        .addProgramFiles(
+            Paths.get("build/test/examplesAndroidApi/classes/classloader/Runner.class"))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(
+            parameters.getRuntime(),
+            "classloader.Runner",
+            parent.toString(),
+            child.toString(),
+            Child.class.getTypeName())
+        .assertSuccessWithOutput("SUCCESS");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
index 6fdfe97..03e7f70 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -40,6 +40,7 @@
   public void testR8CompiledWithD8() throws Exception {
     testForD8()
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .compile()
         .inspect(Java11D8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index 7151dfa..32cfb18 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -138,7 +138,7 @@
   }
 
   @Test(expected = CompilationFailedException.class)
-  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.DEFAULT) // No desugaring
+  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V10_0_0) // No desugaring
   public void testInvokeDefault1() throws Exception {
     ensureSameOutput(
         TestMainDefault1.class.getCanonicalName(),
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
index 1f19d89..b9eabe6 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
@@ -28,6 +28,7 @@
 import java.io.StringWriter;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.hamcrest.Matcher;
+import org.hamcrest.core.IsAnything;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,8 +36,6 @@
 @RunWith(Parameterized.class)
 public class ErrorDuringIrConversionTest extends TestBase {
 
-  static final String EXPECTED = StringUtils.lines("Hello, world");
-
   static final Origin ORIGIN =
       new Origin(Origin.root()) {
         @Override
@@ -61,10 +60,21 @@
       CompilationFailedException e, Matcher<String> messageMatcher, Matcher<String> stackMatcher) {
     // Check that the failure exception exiting the compiler contains origin info in the message.
     assertThat(e.getMessage(), messageMatcher);
-    // Check that the stack trace has the version marker.
     StringWriter writer = new StringWriter();
     e.printStackTrace(new PrintWriter(writer));
-    assertThat(writer.toString(), stackMatcher);
+    String fullStackTrace = writer.toString();
+    // Extract the top cause stack.
+    int topStackTraceEnd = fullStackTrace.indexOf("Caused by:");
+    String topStackTrace = fullStackTrace.substring(0, topStackTraceEnd);
+    String restStackTrace = fullStackTrace.substring(topStackTraceEnd);
+    // Check that top stack trace always has the version marker.
+    assertThat(topStackTrace, containsString("fakeStackEntry"));
+    // Check that top stack has the D8 entry (from tests the non-renamed entry is ToolHelper.runD8).
+    assertThat(topStackTrace, containsString("com.android.tools.r8.ToolHelper.runD8("));
+    // Check that the stack trace always has the suppressed info.
+    assertThat(restStackTrace, containsString(StringUtils.LINE_SEPARATOR + "\tSuppressed:"));
+    // Custom test checks.
+    assertThat(restStackTrace, stackMatcher);
   }
 
   private static void throwNPE() {
@@ -90,9 +100,7 @@
               });
     } catch (CompilationFailedException e) {
       checkCompilationFailedException(
-          e,
-          containsString(ORIGIN.toString()),
-          allOf(containsString("fakeStackEntry"), containsString("throwNPE")));
+          e, containsString(ORIGIN.toString()), containsString("throwNPE"));
       return;
     }
     fail("Expected compilation to fail");
@@ -122,8 +130,7 @@
                         diagnosticOrigin(Origin.unknown())));
               });
     } catch (CompilationFailedException e) {
-      checkCompilationFailedException(
-          e, containsString(ORIGIN.toString()), containsString("fakeStackEntry"));
+      checkCompilationFailedException(e, containsString(ORIGIN.toString()), new IsAnything<>());
       return;
     }
     fail("Expected compilation to fail");
@@ -173,10 +180,9 @@
           // There may be no fail-if-error barrier inside any origin association, thus only the
           // top level message can be expected here.
           containsString("Compilation failed to complete"),
-          // The stack trace must contain both the version, the frame for the hook above, and one
+          // The stack trace must contain the reportErrors frame for the hook above, and one
           // of the error messages.
           allOf(
-              containsString("fakeStackEntry"),
               containsString("reportErrors"),
               anyOf(containsString("FOO!"), containsString("BAR!"), containsString("BAZ!"))));
       return;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
new file mode 100644
index 0000000..de86bd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EqualsCompareToEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EqualsCompareToEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> success = EnumEqualscompareTo.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(EqualsCompareToEnumUnboxingTest.class)
+            .addKeepMainRule(EnumEqualscompareTo.class)
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m ->
+                    assertEnumIsUnboxed(
+                        success.getDeclaredClasses()[0], success.getSimpleName(), m))
+            .run(parameters.getRuntime(), success)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  static class EnumEqualscompareTo {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B
+    }
+
+    public static void main(String[] args) {
+      equalsTest();
+      compareToTest();
+    }
+
+    @SuppressWarnings({"ConstantConditions", "EqualsWithItself", "ResultOfMethodCallIgnored"})
+    private static void equalsTest() {
+      System.out.println(MyEnum.A.equals(MyEnum.B));
+      System.out.println(false);
+      System.out.println(MyEnum.A.equals(MyEnum.A));
+      System.out.println(true);
+      System.out.println(MyEnum.A.equals(null));
+      System.out.println(false);
+      try {
+        ((MyEnum) null).equals(null);
+      } catch (NullPointerException npe) {
+        System.out.println("npe " + npe.getMessage());
+        System.out.println("npe " + npe.getMessage());
+      }
+    }
+
+    @SuppressWarnings({"ConstantConditions", "EqualsWithItself", "ResultOfMethodCallIgnored"})
+    private static void compareToTest() {
+      System.out.println(MyEnum.B.compareTo(MyEnum.A) > 0);
+      System.out.println(true);
+      System.out.println(MyEnum.A.compareTo(MyEnum.B) < 0);
+      System.out.println(true);
+      System.out.println(MyEnum.A.compareTo(MyEnum.A) == 0);
+      System.out.println(true);
+      try {
+        ((MyEnum) null).equals(null);
+      } catch (NullPointerException npe) {
+        System.out.println("npe " + npe.getMessage());
+        System.out.println("npe " + npe.getMessage());
+      }
+      try {
+        MyEnum.A.compareTo(null);
+      } catch (NullPointerException npe) {
+        System.out.println("npe " + npe.getMessage());
+        System.out.println("npe " + npe.getMessage());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
new file mode 100644
index 0000000..d8d781e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ToStringEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public ToStringEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> success = EnumNameToString.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ToStringEnumUnboxingTest.class)
+            .addKeepMainRule(EnumNameToString.class)
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m ->
+                    assertEnumIsUnboxed(
+                        success.getDeclaredClasses()[0], success.getSimpleName(), m))
+            .run(parameters.getRuntime(), success)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  static class EnumNameToString {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.toString());
+      System.out.println(MyEnum.A.name());
+      System.out.println(MyEnum.B.toString());
+      System.out.println(MyEnum.B.name());
+      try {
+        System.out.println(((MyEnum) null).toString());
+      } catch (NullPointerException e) {
+        System.out.println("npeToString " + e.getMessage());
+        System.out.println("npeToString " + e.getMessage());
+      }
+      try {
+        System.out.println(((MyEnum) null).name());
+      } catch (NullPointerException e) {
+        System.out.println("npeName " + e.getMessage());
+        System.out.println("npeName " + e.getMessage());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
new file mode 100644
index 0000000..b7643a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ToStringOverrideEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public ToStringOverrideEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> success = EnumNameToString.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ToStringOverrideEnumUnboxingTest.class)
+            .addKeepMainRule(EnumNameToString.class)
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsBoxed(success.getDeclaredClasses()[0], success.getSimpleName(), m))
+            .run(parameters.getRuntime(), success)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  static class EnumNameToString {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B {
+        @Override
+        public String toString() {
+          return "bezinga";
+        }
+      }
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.toString());
+      System.out.println(MyEnum.A.name());
+      System.out.println(MyEnum.B.toString());
+      System.out.println("bezinga");
+      System.out.println(MyEnum.B.name());
+      System.out.println("B");
+      try {
+        System.out.println(((MyEnum) null).toString());
+      } catch (NullPointerException e) {
+        System.out.println("npeToString " + e.getMessage());
+        System.out.println("npeToString " + e.getMessage());
+      }
+      try {
+        System.out.println(((MyEnum) null).name());
+      } catch (NullPointerException e) {
+        System.out.println("npeName " + e.getMessage());
+        System.out.println("npeName " + e.getMessage());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
index 6dbb356..c8ea9ec 100644
--- a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
+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.IgnoreIfVmOlderThan;
@@ -24,6 +25,7 @@
     runAndCheckVerification(
         D8Command.builder()
             .addProgramFiles(Paths.get(JAR))
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
             .setMode(CompilationMode.DEBUG)
             .setMinApiLevel(MIN_SDK),
         JAR);
@@ -35,6 +37,7 @@
     runAndCheckVerification(
         D8Command.builder()
             .addProgramFiles(Paths.get(JAR))
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
             .setMode(CompilationMode.RELEASE)
             .setMinApiLevel(MIN_SDK),
         JAR);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index ce6df5c..06f2813 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -24,7 +24,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.NonNullTracker;
+import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.NonNullTrackerTestBase;
 import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
@@ -51,7 +51,7 @@
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(mainClass.getName()).method(signature);
     IRCode irCode = fooSubject.buildIR();
-    new NonNullTracker(appView).insertAssumeInstructions(irCode, Timing.empty());
+    new AssumeInserter(appView).insertAssumeInstructions(irCode, Timing.empty());
     inspector.accept(appView, irCode);
     verifyLastInvoke(irCode, npeCaught);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 06305cf..dac8b92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -40,9 +40,9 @@
     IRCode code = fooSubject.buildIR();
     checkCountOfNonNull(code, 0);
 
-    NonNullTracker nonNullTracker = new NonNullTracker(appView);
+    AssumeInserter assumeInserter = new AssumeInserter(appView);
 
-    nonNullTracker.insertAssumeInstructions(code, Timing.empty());
+    assumeInserter.insertAssumeInstructions(code, Timing.empty());
     assertTrue(code.isConsistentSSA());
     checkCountOfNonNull(code, expectedNumberOfNonNull);
 
@@ -69,8 +69,7 @@
             || (prev.isIf() && prev.asIf().isZeroTest())
             || !curr.getBlock().getPredecessors().contains(prev.getBlock()));
         // Make sure non-null is used or inserted for arguments.
-        assertTrue(
-            curr.outValue().numberOfAllUsers() > 0 || curr.asAssumeNonNull().src().isArgument());
+        assertTrue(curr.outValue().numberOfAllUsers() > 0 || curr.asAssume().src().isArgument());
         count++;
       }
     }
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 8ff71b8..75c45d1 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
@@ -374,7 +374,7 @@
               .getMethod()
               .name
               .toString()
-              .equals(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_ORDINAL)) {
+              .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX)) {
         ++invokeCount;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
index d55906a..438aa81 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -82,7 +82,7 @@
             .compile()
             .inspect(this::checkOutlineFromFeature);
 
-    // Check that parts of method1 and method2 in FeatureClass was outlined.
+    // Check that parts of method1, ..., method4 in FeatureClass was outlined.
     ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
     assertThat(featureClass, isPresent());
     String outlineClassName =
@@ -92,8 +92,10 @@
             .get(OutlineOptions.CLASS_NAME);
     assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
     assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
 
-    compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("1234");
+    compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("123456");
   }
 
   private void checkNoOutlineFromFeature(CodeInspector inspector) {
@@ -106,6 +108,7 @@
   @Test
   public void testWithSplit() throws Exception {
     Path featureCode = temp.newFile("feature.zip").toPath();
+
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(TestClass.class, FeatureAPI.class)
@@ -118,7 +121,7 @@
             .compile()
             .inspect(this::checkNoOutlineFromFeature);
 
-    // Check that parts of method1 and method2 in FeatureClass was not outlined.
+    // Check that parts of method1, ..., method4 in FeatureClass was not outlined.
     CodeInspector featureInspector = new CodeInspector(featureCode);
     ClassSubject featureClass = featureInspector.clazz(FeatureClass.class);
     assertThat(featureClass, isPresent());
@@ -129,6 +132,8 @@
             .get(OutlineOptions.CLASS_NAME);
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+    assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
+    assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
 
     // Run the code without the feature code present.
     compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("12");
@@ -137,7 +142,7 @@
     compileResult
         .addRunClasspathFiles(featureCode)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput("1234");
+        .assertSuccessWithOutput("123456");
   }
 
   public static class TestClass {
@@ -193,14 +198,23 @@
     }
 
     public static void feature(int i) {
-      FeatureClass.method1(new FeatureClass(i), new FeatureClass(i + 1));
+      method1(i, i + 1);
+      method3(new FeatureClass(i + 2), new FeatureClass(i + 3));
     }
 
-    public static void method1(FeatureClass fc1, FeatureClass fc2) {
+    public static void method1(int i1, int i2) {
+      System.out.print(i1 + "" + i2);
+    }
+
+    public static void method2(int i1, int i2) {
+      System.out.print(i1 + "" + i2);
+    }
+
+    public static void method3(FeatureClass fc1, FeatureClass fc2) {
       System.out.print(fc1.getI() + "" + fc2.getI());
     }
 
-    public static void method2(FeatureClass fc1, FeatureClass fc2) {
+    public static void method4(FeatureClass fc1, FeatureClass fc2) {
       System.out.print(fc1.getI() + "" + fc2.getI());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
new file mode 100644
index 0000000..904f69f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.coroutines;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinxCoroutinesTestRunner extends KotlinTestBase {
+
+  private static final String PKG = "kotlinx-coroutines-1.3.6";
+  private static final Path BASE_LIBRARY =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "deps_all-1.3.6-SNAPSHOT.jar");
+  private static final Path TEST_SOURCES =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "kotlinx-coroutines-test-test-sources");
+  private static final List<Path> DEPENDENCIES =
+      ImmutableList.of(
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "atomicfu-0.14.3.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "hamcrest-core-1.3.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "junit-4.13.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "kotlin-test-1.3.72.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "kotlin-test-junit-1.3.71.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "kotlinx.coroutines.testbase.jar"),
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, PKG, "kotlinx.coroutines.test.main.jar"));
+
+  // Tests that do not run correctly in general - that is these tests are expected to fail always.
+  private Set<String> notWorkingTests =
+      Sets.newHashSet("kotlinx.coroutines.test.TestDispatchersTest");
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  private final TestParameters parameters;
+
+  public KotlinxCoroutinesTestRunner(TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void runKotlinxCoroutinesTests_smoke() throws Exception {
+    Path baseJar =
+        kotlinc(KOTLINC, targetVersion)
+            .addArguments(
+                "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
+                "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
+                "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi")
+            .addClasspathFiles(DEPENDENCIES)
+            .addClasspathFiles(BASE_LIBRARY)
+            .addSourceFiles(TEST_SOURCES)
+            .compile();
+    runTestsInJar(baseJar, BASE_LIBRARY);
+  }
+
+  private void runTestsInJar(Path testJar, Path deps) throws Exception {
+    List<Path> dependencies = new ArrayList<>(DEPENDENCIES);
+    dependencies.add(deps);
+    dependencies.add(testJar);
+    ZipUtils.iter(
+        testJar.toString(),
+        (entry, input) -> {
+          if (!entry.isDirectory() && entry.getName().endsWith("Test.class")) {
+            runTest(dependencies, entry.getName());
+          }
+        });
+  }
+
+  private void runTest(List<Path> dependencies, String name) throws IOException {
+    String testName = name.replace("/", ".").replace(".class", "");
+    if (notWorkingTests.contains(testName)) {
+      return;
+    }
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            parameters.getRuntime().asCf(), dependencies, "org.junit.runner.JUnitCore", testName);
+    assertEquals(0, processResult.exitCode);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
index eb670d6..d420618 100644
--- a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
+++ b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
@@ -52,7 +52,7 @@
     try {
       SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
       fail("Should have thrown an error");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertEquals(1, testDiagnosticMessages.getErrors().size());
       Diagnostic diagnostic = testDiagnosticMessages.getErrors().get(0);
       assertEquals(
@@ -75,7 +75,7 @@
     try {
       SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
       fail("Should have thrown an error");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertEquals(1, testDiagnosticMessages.getErrors().size());
       Diagnostic diagnostic = testDiagnosticMessages.getErrors().get(0);
       assertEquals(
@@ -98,7 +98,7 @@
     try {
       SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
       fail("Should have thrown an error");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertEquals(1, testDiagnosticMessages.getErrors().size());
       Diagnostic diagnostic = testDiagnosticMessages.getErrors().get(0);
       assertEquals(
@@ -116,7 +116,7 @@
     try {
       SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
       fail("Should have thrown an error");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertEquals(1, testDiagnosticMessages.getErrors().size());
       Diagnostic diagnostic = testDiagnosticMessages.getErrors().get(0);
       assertEquals(
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index 5d06942..cc558a1 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -11,13 +11,14 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.code.AddIntLit8;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import dalvik.annotation.optimization.ReachabilitySensitive;
@@ -26,7 +27,6 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -68,40 +68,48 @@
 @RunWith(Parameterized.class)
 public class ReachabilitySensitiveTest extends TestBase {
 
+  private final TestParameters parameters;
   private final Tool tool;
 
-  @Parameters(name = "{0}")
-  public static List<Object> data() {
-    return ImmutableList.of(Tool.D8, Tool. R8);
+  @Parameters(name = "{0} tool: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        ImmutableList.of(Tool.D8, Tool.R8));
   }
 
-  public ReachabilitySensitiveTest(Tool tool) {
+  public ReachabilitySensitiveTest(TestParameters parameters, Tool tool) {
+    this.parameters = parameters;
     this.tool = tool;
   }
 
+  private int getNumRegisters() {
+    // With API level >= Q we are allowed to re-use the receiver's register.
+    // See also InternalOptions.canHaveThisJitCodeDebuggingBug().
+    assert parameters.isDexRuntime();
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q) ? 2 : 3;
+  }
+
   @Test
   public void testNoAnnotation()
       throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
-    CodeInspector inspector = tool == Tool.R8
-        ? compileR8(TestClass.class)
-        : compile(TestClass.class);
+    CodeInspector inspector =
+        tool == Tool.R8 ? compileR8(TestClass.class) : compile(TestClass.class);
     DexCode code =
         inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
     // Computation of k is constant folded and the value takes up one register. System.out takes
-    // up another register and the receiver is the last.
-    Assume.assumeTrue(
-        "TODO(b/144966342): Why 2 on Q?",
-        ToolHelper.getDexVm().isOlderThanOrEqual(ToolHelper.DexVm.ART_9_0_0_HOST));
-    assertEquals(3, code.registerSize);
+    // up another register and the receiver is the last, unless on Q+.
+    assertEquals(getNumRegisters(), code.registerSize);
     checkNoLocals(code);
   }
 
   @Test
   public void testFieldAnnotation()
       throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
-    CodeInspector inspector = tool == Tool.R8
-        ? compileR8(TestClassWithAnnotatedField.class)
-        : compile(TestClassWithAnnotatedField.class);
+    CodeInspector inspector =
+        tool == Tool.R8
+            ? compileR8(TestClassWithAnnotatedField.class)
+            : compile(TestClassWithAnnotatedField.class);
     checkAnnotatedCode(
         inspector
             .method(TestClassWithAnnotatedField.class.getMethod("method"))
@@ -113,9 +121,10 @@
   @Test
   public void testMethodAnnotation()
       throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
-    CodeInspector inspector = tool == Tool.R8
-        ? compileR8(TestClassWithAnnotatedMethod.class)
-        : compile(TestClassWithAnnotatedMethod.class);
+    CodeInspector inspector =
+        tool == Tool.R8
+            ? compileR8(TestClassWithAnnotatedMethod.class)
+            : compile(TestClassWithAnnotatedMethod.class);
     checkAnnotatedCode(
         inspector
             .method(TestClassWithAnnotatedMethod.class.getMethod("method"))
@@ -127,9 +136,10 @@
   private void checkNoLocals(DexCode code) {
     // Even if we preserve live range of locals, we do not output locals information
     // as this is a release build.
-    assertTrue((code.getDebugInfo() == null) ||
-        Arrays.stream(code.getDebugInfo().events)
-            .allMatch(event -> !(event instanceof StartLocal)));
+    assertTrue(
+        (code.getDebugInfo() == null)
+            || Arrays.stream(code.getDebugInfo().events)
+                .allMatch(event -> !(event instanceof StartLocal)));
   }
 
   private void checkAnnotatedCode(DexCode code) {
@@ -155,6 +165,7 @@
       throws CompilationFailedException, IOException, ExecutionException {
     return testForD8()
         .addProgramClasses(classes)
+        .setMinApi(parameters.getApiLevel())
         .setMode(CompilationMode.RELEASE)
         .compile()
         .inspector();
@@ -170,6 +181,7 @@
         .addProgramClasses(classes)
         // TODO(ager): This will be in android.jar over time. For now, make it part of the app.
         .addProgramClasses(ReachabilitySensitive.class)
+        .setMinApi(parameters.getApiLevel())
         .setMode(CompilationMode.RELEASE)
         // Keep the input class and its methods.
         .addKeepRules(keepRules)
diff --git a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
index 7e56259..e63bcbb 100644
--- a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
+++ b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
@@ -69,7 +69,7 @@
             builder -> {
               builder.appendArtOption("-Xusejit:true");
             },
-            DexVm.ART_10_0_0_HOST);
+            DexVm.fromVersion(DexVm.Version.last()));
     assertEquals(0, artResult.exitCode);
     assertFalse(artResult.stderr.contains("Expected NullPointerException"));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 2e123d7..ecb398a 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -16,20 +16,17 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.hamcrest.Matcher;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,25 +45,109 @@
   }
 
   private enum Mode {
-    NO_INVOKE,
-    INVOKE_UNVERIFIABLE_METHOD,
-    INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS;
+    NO_INVOKE {
 
-    public String instruction() {
-      switch (this) {
-        case NO_INVOKE:
-          return "";
-
-        case INVOKE_UNVERIFIABLE_METHOD:
-          return "invokestatic UnverifiableClass/unverifiableMethod()V";
-
-        case INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS:
-          return "invokestatic UnverifiableClass/verifiableMethod()V";
-
-        default:
-          throw new Unreachable();
+      @Override
+      public String getExpectedOutput(
+          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+        return StringUtils.joinLines("Hello!", "Goodbye!", "");
       }
-    }
+
+      @Override
+      public String instruction() {
+        return "";
+      }
+    },
+    INVOKE_UNVERIFIABLE_METHOD {
+
+      @Override
+      public String getExpectedOutput(
+          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+        if (!useInterface) {
+          return StringUtils.joinLines("Hello!", "");
+        }
+
+        switch (compiler) {
+          case D8:
+          case DX:
+            switch (runtime.asDex().getVm().getVersion()) {
+              case V4_0_4:
+              case V4_4_4:
+              case V10_0_0:
+                return StringUtils.joinLines("Hello!", "Goodbye!", "");
+
+              case V7_0_0:
+                return StringUtils.joinLines(
+                    "Hello!",
+                    "Unexpected outcome of checkcast",
+                    "Unexpected outcome of instanceof",
+                    "Goodbye!",
+                    "");
+
+              default:
+                // Fallthrough.
+            }
+
+          case R8:
+          case PROGUARD:
+            return StringUtils.joinLines(
+                "Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+
+          case R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES:
+            return StringUtils.joinLines(
+                "Hello!",
+                "Unexpected outcome of getstatic",
+                "Unexpected outcome of checkcast",
+                "Goodbye!",
+                "");
+
+          case JAVAC:
+            return StringUtils.joinLines("Hello!", "Goodbye!", "");
+
+          default:
+            throw new Unreachable();
+        }
+      }
+
+      @Override
+      public String instruction() {
+          return "invokestatic UnverifiableClass/unverifiableMethod()V";
+      }
+    },
+    INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS {
+
+      @Override
+      public String getExpectedOutput(
+          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+        if (useInterface) {
+          return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
+        }
+
+        switch (compiler) {
+          case R8:
+          case R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES:
+          case PROGUARD:
+            // The unverifiable method has been removed as a result of tree shaking, so the code
+            // does not fail with a verification error when trying to load class UnverifiableClass.
+            return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
+
+          default:
+            // The code fails with a verification error because the verifiableMethod() is being
+            // called on UnverifiableClass, which does not verify due to unverifiableMethod().
+            return StringUtils.joinLines("Hello!", "");
+        }
+      }
+
+      @Override
+      public String instruction() {
+        return "invokestatic UnverifiableClass/verifiableMethod()V";
+      }
+    };
+
+    public abstract String getExpectedOutput(
+        Compiler compiler, TestRuntime runtime, boolean useInterface);
+
+    public abstract String instruction();
   }
 
   private final TestParameters parameters;
@@ -191,11 +272,17 @@
       assert parameters.isDexRuntime();
 
       DXTestRunResult dxResult =
-          testForDX().addProgramFiles(inputJar).run(parameters.getRuntime(), mainClass.name);
+          testForDX()
+              .addProgramFiles(inputJar)
+              .setMinApi(parameters.getApiLevel())
+              .run(parameters.getRuntime(), mainClass.name);
       checkTestRunResult(dxResult, Compiler.DX);
 
       D8TestRunResult d8Result =
-          testForD8().addProgramFiles(inputJar).run(parameters.getRuntime(), mainClass.name);
+          testForD8()
+              .addProgramFiles(inputJar)
+              .setMinApi(parameters.getApiLevel())
+              .run(parameters.getRuntime(), mainClass.name);
       checkTestRunResult(d8Result, Compiler.D8);
     }
 
@@ -254,14 +341,6 @@
   }
 
   private void checkTestRunResult(TestRunResult<?> result, Compiler compiler) {
-    Assume.assumeFalse(
-        "Triage (b/144966342)",
-        parameters.getRuntime().isDex()
-            && parameters
-                .getRuntime()
-                .asDex()
-                .getMinApiLevel()
-                .isGreaterThanOrEqualTo(AndroidApiLevel.Q));
     switch (mode) {
       case NO_INVOKE:
         result.assertSuccessWithOutput(getExpectedOutput(compiler));
@@ -309,58 +388,7 @@
   }
 
   private String getExpectedOutput(Compiler compiler) {
-    if (mode == Mode.NO_INVOKE) {
-      return StringUtils.joinLines("Hello!", "Goodbye!", "");
-    }
-    if (mode == Mode.INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS) {
-      if (useInterface) {
-        return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
-      } else {
-        if (compiler == Compiler.R8
-            || compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
-            || compiler == Compiler.PROGUARD) {
-          // The unverifiable method has been removed as a result of tree shaking, so the code does
-          // not fail with a verification error when trying to load class `UnverifiableClass`.
-          return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
-        } else {
-          // The code fails with a verification error because the verifiableMethod() is being called
-          // on `UnverifiableClass`, which does not verify due to unverifiableMethod().
-          return StringUtils.joinLines("Hello!", "");
-        }
-      }
-    }
-    assert mode == Mode.INVOKE_UNVERIFIABLE_METHOD;
-    if (useInterface) {
-      if (compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES) {
-        return StringUtils.joinLines(
-            "Hello!",
-            "Unexpected outcome of getstatic",
-            "Unexpected outcome of checkcast",
-            "Goodbye!",
-            "");
-      } else if (compiler == Compiler.R8 || compiler == Compiler.PROGUARD) {
-        return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
-      } else if (compiler == Compiler.DX || compiler == Compiler.D8) {
-        if (ToolHelper.getDexVm().getVersion() == Version.V4_0_4
-            || ToolHelper.getDexVm().getVersion() == Version.V4_4_4) {
-          return StringUtils.joinLines("Hello!", "Goodbye!", "");
-        } else if (ToolHelper.getDexVm().getVersion() == Version.V7_0_0) {
-          return StringUtils.joinLines(
-              "Hello!",
-              "Unexpected outcome of checkcast",
-              "Unexpected outcome of instanceof",
-              "Goodbye!",
-              "");
-        } else {
-          return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
-        }
-      } else {
-        assert compiler == Compiler.JAVAC;
-        return StringUtils.joinLines("Hello!", "Goodbye!", "");
-      }
-    } else {
-      return StringUtils.joinLines("Hello!", "");
-    }
+    return mode.getExpectedOutput(compiler, parameters.getRuntime(), useInterface);
   }
 
   private Matcher<String> getMatcherForExpectedError() {
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 3b39b815..4cada28 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -766,7 +766,7 @@
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
       parser.parse(path);
       fail("Expect to fail due to the lack of file name.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
     }
   }
@@ -795,7 +795,7 @@
       new ProguardConfigurationParser(new DexItemFactory(), reporter)
           .parse(path);
       fail();
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
     }
   }
@@ -807,7 +807,7 @@
       new ProguardConfigurationParser(new DexItemFactory(), reporter)
           .parse(path);
       fail();
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
     }
   }
@@ -832,7 +832,7 @@
       parser.parse(createConfigurationForTesting(
           Collections.singletonList("-injars abc.jar(*.zip;*.class)")));
       fail();
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertEquals(1, handler.errors.size());
     }
   }
@@ -1026,7 +1026,7 @@
       reset();
       parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
       fail("Expect to fail due to unsupported option.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(handler.errors, null, 1, 1, "Unsupported option", option);
     }
   }
@@ -1427,7 +1427,7 @@
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
       parser.parse(createConfigurationForTesting(ImmutableList.of("-keepattributes xxx,")));
       fail();
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       assertTrue(
           handler.errors.get(0).getDiagnosticMessage().contains("Expected list element at "));
     }
@@ -1613,7 +1613,7 @@
         reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
         fail("Expect to fail due to the lack of path filter.");
-      } catch (AbortException e) {
+      } catch (RuntimeException e) {
         checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
       }
     }
@@ -1628,7 +1628,7 @@
         reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
         fail("Expect to fail due to the lack of path filter.");
-      } catch (AbortException e) {
+      } catch (RuntimeException e) {
         checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
       }
     }
@@ -1643,7 +1643,7 @@
         reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
         fail("Expect to fail due to the lack of path filter.");
-      } catch (AbortException e) {
+      } catch (RuntimeException e) {
         checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
       }
     }
@@ -1819,7 +1819,7 @@
     try {
       parser.parse(proguardConfig);
       fail("Expect to fail due to unsupported constructor name pattern.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(
           handler.errors, proguardConfig, 5, 3, "Unexpected character", "method name");
     }
@@ -2501,7 +2501,7 @@
     try {
       parser.parse(createConfigurationForTesting(ImmutableList.of("-printusage <>")));
       fail("Expect to fail due to the lack of file name.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(handler.errors, null, 1, 15, "Value of system property '' not found");
     }
   }
@@ -2518,7 +2518,7 @@
       parser.parse(
           createConfigurationForTesting(ImmutableList.of("-printusage <" + property + ">")));
       fail("Expect to fail due to the lack of file name.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(
           handler.errors, null, 1, 16, "Value of system property '" + property + "' not found");
     }
@@ -2539,7 +2539,7 @@
       reset();
       parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value)));
       fail("Expect to fail due to un-closed quote.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checker.get();
     }
   }
@@ -2759,7 +2759,7 @@
       new ProguardConfigurationParser(new DexItemFactory(), reporter)
           .parse(proguardConfigurationFile);
       fail("Expected to fail since the type name cannot be negated.");
-    } catch (AbortException e) {
+    } catch (RuntimeException e) {
       checkDiagnostics(
           handler.errors,
           proguardConfigurationFile,
@@ -2794,7 +2794,7 @@
       try {
         parser.parse(proguardConfig);
         fail("Expect to fail due to unsupported constructor name pattern.");
-      } catch (AbortException e) {
+      } catch (RuntimeException e) {
         int column = initName.contains("void") ? initName.indexOf("void") + 8
             : (initName.contains("XYZ") ? initName.indexOf(">") + 4 : 3);
         if (initName.contains("XYZ")) {
diff --git a/third_party/kotlinx-coroutines-1.3.6.tar.gz.sha1 b/third_party/kotlinx-coroutines-1.3.6.tar.gz.sha1
new file mode 100644
index 0000000..6b4a3b8
--- /dev/null
+++ b/third_party/kotlinx-coroutines-1.3.6.tar.gz.sha1
@@ -0,0 +1 @@
+fe2b5c46bec807d11fcbd5572f9f7ba9d755cefd
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index ad9f63f..809a1fe 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -91,6 +91,12 @@
                     help='Archive find-min-xmx results on GCS',
                     default=False,
                     action='store_true')
+  result.add_option('--no-extra-pgconf', '--no_extra_pgconf',
+                    help='Build without the following extra rules: ' +
+                         '-printconfiguration, -printmapping, -printseeds, ' +
+                         '-printusage',
+                    default=False,
+                    action='store_true')
   result.add_option('--timeout',
                     type='int',
                     default=0,
@@ -529,6 +535,8 @@
         args.extend(['--main-dex-rules', rules])
     if 'allow-type-errors' in values:
       extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
+    extra_args.append(
+        '-Dcom.android.tools.r8.disallowClassInlinerGracefulExit=1')
 
   if options.debug_agent:
     if not options.compiler_build == 'full':
@@ -575,9 +583,10 @@
           pg_outdir = os.path.dirname(outdir)
         else:
           pg_outdir = outdir
-        additional_pg_conf = GenerateAdditionalProguardConfiguration(
-            temp, os.path.abspath(pg_outdir))
-        args.extend(['--pg-conf', additional_pg_conf])
+        if not options.no_extra_pgconf:
+          additional_pg_conf = GenerateAdditionalProguardConfiguration(
+              temp, os.path.abspath(pg_outdir))
+          args.extend(['--pg-conf', additional_pg_conf])
       build = not options.no_build and not options.golem
       stderr_path = os.path.join(temp, 'stderr')
       with open(stderr_path, 'w') as stderr: