Revert "Move insertion of assume-dynamic-type to AssumeInserter"

This reverts commit fc582f8d14277dee21727b950132664783ac5563.

Reason for revert: Fails on apps bot and chrome-200430

Change-Id: I34885ac76f0a014dc7dfc83b639f1d5fc7965ace
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index cf475fa..5781482 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -393,6 +393,7 @@
     assert internal.neverMergePrefixes.contains("j$.");
 
     // Assert some of R8 optimizations are disabled.
+    assert !internal.enableDynamicTypeOptimization;
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 9b0f9de..b1072df 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -162,6 +162,7 @@
     assert !internal.passthroughDexCode;
 
     // Assert some of R8 optimizations are disabled.
+    assert !internal.enableDynamicTypeOptimization;
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5abbc25..656f3aa 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,6 +842,7 @@
             ? LineNumberOptimization.ON
             : LineNumberOptimization.OFF;
 
+    assert internal.enableDynamicTypeOptimization || !proguardConfiguration.isOptimizing();
     assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
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 7aed1d9..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
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -15,6 +16,7 @@
 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;
 
@@ -23,11 +25,11 @@
   private static final String ERROR_MESSAGE =
       "Expected Assume instructions to be removed after IR processing.";
 
-  private DynamicTypeAssumption dynamicTypeAssumption;
+  private final DynamicTypeAssumption dynamicTypeAssumption;
   private final NonNullAssumption nonNullAssumption;
   private final Instruction origin;
 
-  public Assume(
+  private Assume(
       DynamicTypeAssumption dynamicTypeAssumption,
       NonNullAssumption nonNullAssumption,
       Value dest,
@@ -36,6 +38,9 @@
       AppView<?> appView) {
     super(dest, src);
     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
@@ -73,7 +78,7 @@
   }
 
   public boolean verifyInstructionIsNeeded(AppView<?> appView) {
-    if (hasDynamicTypeAssumption()) {
+    if (isAssumeDynamicType()) {
       assert dynamicTypeAssumption.verifyCorrectnessOfValues(outValue(), src(), appView);
     }
     return true;
@@ -107,7 +112,13 @@
 
   @Override
   public String getInstructionName() {
-    return "Assume";
+    if (isAssumeDynamicType()) {
+      return "AssumeDynamicType";
+    }
+    if (isAssumeNonNull()) {
+      return "AssumeNonNull";
+    }
+    throw new Unimplemented();
   }
 
   @Override
@@ -120,15 +131,13 @@
     return this;
   }
 
-  public boolean hasDynamicTypeAssumption() {
+  @Override
+  public boolean isAssumeDynamicType() {
     return dynamicTypeAssumption != null;
   }
 
-  public void unsetDynamicTypeAssumption() {
-    dynamicTypeAssumption = null;
-  }
-
-  public boolean hasNonNullAssumption() {
+  @Override
+  public boolean isAssumeNonNull() {
     return nonNullAssumption != null;
   }
 
@@ -140,7 +149,7 @@
     if (outType.isPrimitiveType()) {
       return false;
     }
-    if (hasDynamicTypeAssumption()) {
+    if (isAssumeDynamicType()) {
       outType = dynamicTypeAssumption.getDynamicUpperBoundType();
     }
     if (appView.appInfo().hasLiveness()) {
@@ -211,11 +220,14 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    if (hasNonNullAssumption()) {
+    if (isAssumeDynamicType()) {
+      return src().getType();
+    }
+    if (isAssumeNonNull()) {
       assert src().getType().isReferenceType();
       return src().getType().asReferenceType().asMeetWithNotNull();
     }
-    return src().getType();
+    throw new Unimplemented();
   }
 
   @Override
@@ -243,14 +255,15 @@
     assert super.verifyTypes(appView);
 
     TypeElement inType = src().getType();
-    assert inType.isReferenceType() : inType;
-
     TypeElement outType = getOutType();
-    if (hasNonNullAssumption()) {
-      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
+    if (isAssumeDynamicType()) {
+      assert inType.isReferenceType() : inType;
+      assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     } else {
-      assert outType.equals(inType)
+      assert isAssumeNonNull() : this;
+      assert inType.isReferenceType() : inType;
+      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
     return true;
@@ -263,21 +276,21 @@
     //     assumption became "truth."
     //   2) invoke-interface could be devirtualized, while its dynamic type and/or non-null receiver
     //     are still valid.
-    StringBuilder builder = new StringBuilder(super.toString());
-    if (hasNonNullAssumption()) {
-      builder.append("; not null");
+    String originString =
+        origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)";
+    if (isAssumeNonNull()) {
+      return super.toString() + originString;
     }
-    if (hasDynamicTypeAssumption()) {
-      if (hasOutValue()) {
-        if (!dynamicTypeAssumption.dynamicUpperBoundType.equalUpToNullability(outValue.getType())) {
-          builder.append("; upper bound: ").append(dynamicTypeAssumption.dynamicUpperBoundType);
-        }
-      }
-      if (dynamicTypeAssumption.dynamicLowerBoundType != null) {
-        builder.append("; lower bound: ").append(dynamicTypeAssumption.dynamicLowerBoundType);
-      }
+    if (isAssumeDynamicType()) {
+      return super.toString()
+          + "; upper bound: "
+          + dynamicTypeAssumption.dynamicUpperBoundType
+          + (dynamicTypeAssumption.dynamicLowerBoundType != null
+              ? "; lower bound: " + dynamicTypeAssumption.dynamicLowerBoundType
+              : "")
+          + originString;
     }
-    return builder.toString();
+    return super.toString();
   }
 
   public static class DynamicTypeAssumption {
@@ -285,7 +298,7 @@
     private final TypeElement dynamicUpperBoundType;
     private final ClassTypeElement dynamicLowerBoundType;
 
-    public DynamicTypeAssumption(
+    private DynamicTypeAssumption(
         TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
       this.dynamicUpperBoundType = dynamicUpperBoundType;
       this.dynamicLowerBoundType = dynamicLowerBoundType;
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 9197455..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
@@ -609,17 +609,17 @@
     // We can only type check the program if we have subtyping information. Therefore, we do not
     // require that the program type checks in D8.
     if (appView.enableWholeProgramOptimizations()) {
-      assert validAssumeInstructions(appView);
+      assert validAssumeDynamicTypeInstructions(appView);
       assert new TypeChecker(appView.withLiveness()).check(this);
     }
     assert blocks.stream().allMatch(block -> block.verifyTypes(appView));
     return true;
   }
 
-  private boolean validAssumeInstructions(AppView<?> appView) {
+  private boolean validAssumeDynamicTypeInstructions(AppView<?> appView) {
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.isAssume()) {
+        if (instruction.isAssumeDynamicType()) {
           assert instruction.asAssume().verifyInstructionIsNeeded(appView);
         }
       }
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 6fd3f9b..4682ecc 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
@@ -717,12 +717,12 @@
     return null;
   }
 
-  public final boolean isAssumeWithDynamicTypeAssumption() {
-    return isAssume() && asAssume().hasDynamicTypeAssumption();
+  public boolean isAssumeDynamicType() {
+    return false;
   }
 
-  public final boolean isAssumeWithNonNullAssumption() {
-    return isAssume() && asAssume().hasNonNullAssumption();
+  public boolean isAssumeNonNull() {
+    return false;
   }
 
   public boolean isBinop() {
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 f3a889f..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
@@ -150,8 +150,7 @@
       return false;
     }
     // Check that the receiver information comes from a dynamic type.
-    if (!getReceiver()
-        .isDefinedByInstructionSatisfying(Instruction::isAssumeWithDynamicTypeAssumption)) {
+    if (!getReceiver().definition.isAssumeDynamicType()) {
       return false;
     }
     // Now, it can be that the upper bound is more precise than the lower:
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 be5340c..f49c1e3 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
@@ -504,10 +504,6 @@
     return debugData != null && !debugData.users.isEmpty();
   }
 
-  public boolean hasNonDebugUsers() {
-    return hasUsers() || hasPhiUsers();
-  }
-
   public boolean hasPhiUsers() {
     return !phiUsers.isEmpty();
   }
@@ -954,8 +950,8 @@
    */
   public boolean isNeverNull() {
     assert type.isReferenceType();
-    return isDefinedByInstructionSatisfying(Instruction::isAssumeWithNonNullAssumption)
-        || type.isDefinitelyNotNull();
+    return (definition != null && definition.isAssumeNonNull())
+        || type.nullability().isDefinitelyNotNull();
   }
 
   public boolean isArgument() {
@@ -1174,19 +1170,14 @@
     Value root = getAliasedValue();
     if (root.isPhi()) {
       assert getSpecificAliasedValue(
-              value ->
-                  value.isDefinedByInstructionSatisfying(
-                      Instruction::isAssumeWithDynamicTypeAssumption))
+              value -> !value.isPhi() && value.definition.isAssumeDynamicType())
           == null;
       return root.getDynamicUpperBoundType(appView);
     }
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue =
-        getSpecificAliasedValue(
-            value ->
-                value.isDefinedByInstructionSatisfying(
-                    Instruction::isAssumeWithDynamicTypeAssumption));
+        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 instruction that
@@ -1239,8 +1230,7 @@
     }
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
-    Value aliasedValue =
-        getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption());
+    Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
     if (aliasedValue != null) {
       ClassTypeElement lattice =
           aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
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 9d55dda..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
@@ -53,6 +53,7 @@
 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;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -166,7 +167,8 @@
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
 
-  public final AssumeInserter assumeInserter;
+  // Assumers that will insert Assume instructions.
+  public final Collection<Assumer> assumers = new ArrayList<>();
   private final DynamicTypeOptimization dynamicTypeOptimization;
 
   final AssertionsRewriter assertionsRewriter;
@@ -262,7 +264,6 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = null;
-      this.assumeInserter = null;
       return;
     }
     this.lambdaRewriter =
@@ -290,12 +291,20 @@
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      assumeInserter = new AssumeInserter(appViewWithLiveness);
+      if (options.enableNonNullTracking) {
+        assumers.add(new AssumeInserter(appViewWithLiveness));
+      }
       this.classInliner =
           options.enableClassInlining && options.enableInlining ? new ClassInliner() : null;
       this.classStaticizer =
           options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
-      this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
+      this.dynamicTypeOptimization =
+          options.enableDynamicTypeOptimization
+              ? new DynamicTypeOptimization(appViewWithLiveness)
+              : null;
+      if (dynamicTypeOptimization != null) {
+        assumers.add(dynamicTypeOptimization);
+      }
       this.fieldAccessAnalysis =
           FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
       this.libraryMethodOverrideAnalysis =
@@ -338,7 +347,6 @@
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
     } else {
-      this.assumeInserter = null;
       this.classInliner = null;
       this.classStaticizer = null;
       this.dynamicTypeOptimization = null;
@@ -1251,9 +1259,9 @@
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    if (assumeInserter != null) {
-      assumeInserter.insertAssumeInstructions(code, timing);
-    }
+    timing.begin("Insert assume instructions");
+    CodeRewriter.insertAssumeInstructions(code, assumers, timing);
+    timing.end();
 
     previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
 
@@ -1589,7 +1597,7 @@
       timing.end();
     }
 
-    if (assumeInserter != null) {
+    if (!assumers.isEmpty()) {
       timing.begin("Remove assume instructions");
       CodeRewriter.removeAssumeInstructions(appView, code);
       timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index e54ec6c..7211439 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -44,6 +44,8 @@
 
   void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method);
 
+  void methodNeverReturnsNull(DexEncodedMethod method);
+
   void methodNeverReturnsNormally(DexEncodedMethod method);
 
   void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
similarity index 60%
rename from src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
rename to src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 41f72cb..7f45db3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -16,68 +16,60 @@
 import java.util.Set;
 
 /**
- * When we have Assume instructions we generally verify that the Assume instructions contribute with
- * non-trivial information to the IR (e.g., the dynamic type should be more precise than the static
- * type).
+ * When we have Assume instructions with a DynamicTypeAssumption we generally verify that the
+ * dynamic type in the Assume node is always at least as precise as the static type of the
+ * corresponding value.
  *
  * <p>Therefore, when this property may no longer hold for an Assume instruction, we need to remove
  * it.
  *
  * <p>This class is a helper class to remove these instructions. Unlike {@link
  * CodeRewriter#removeAssumeInstructions} this class does not unconditionally remove all Assume
- * instructions.
+ * instructions, not does it remove all Assume instructions with a DynamicTypeAssumption.
  */
-public class AssumeRemover {
+public class AssumeDynamicTypeRemover {
 
   private final AppView<?> appView;
   private final IRCode code;
 
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
-  private final Set<Assume> assumeInstructionsToRemove = Sets.newIdentityHashSet();
+  private final Set<Assume> assumeDynamicTypeInstructionsToRemove = Sets.newIdentityHashSet();
 
   private boolean mayHaveIntroducedTrivialPhi = false;
 
-  public AssumeRemover(AppView<?> appView, IRCode code) {
+  public AssumeDynamicTypeRemover(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
   }
 
-  public Set<Value> getAffectedValues() {
-    return affectedValues;
-  }
-
   public boolean mayHaveIntroducedTrivialPhi() {
     return mayHaveIntroducedTrivialPhi;
   }
 
-  public void markAssumeDynamicTypeUsersForRemoval(Value value) {
+  public void markForRemoval(Assume assumeDynamicTypeInstruction) {
+    assumeDynamicTypeInstructionsToRemove.add(assumeDynamicTypeInstruction);
+  }
+
+  public void markUsersForRemoval(Value value) {
     for (Instruction user : value.aliasedUsers()) {
-      if (user.isAssume()) {
-        Assume assumeInstruction = user.asAssume();
-        assumeInstruction.unsetDynamicTypeAssumption();
-        if (!assumeInstruction.hasNonNullAssumption()) {
-          assumeInstruction.unsetDynamicTypeAssumption();
-        }
+      if (user.isAssumeDynamicType()) {
+        markForRemoval(user.asAssume());
       }
     }
   }
 
-  private void markForRemoval(Assume assumeInstruction) {
-    assumeInstructionsToRemove.add(assumeInstruction);
-  }
-
   public void removeIfMarked(
-      Assume assumeInstruction, InstructionListIterator instructionIterator) {
-    if (assumeInstructionsToRemove.remove(assumeInstruction)) {
-      Value inValue = assumeInstruction.src();
-      Value outValue = assumeInstruction.outValue();
+      Assume assumeDynamicTypeInstruction, InstructionListIterator instructionIterator) {
+    if (assumeDynamicTypeInstructionsToRemove.remove(assumeDynamicTypeInstruction)) {
+      Value inValue = assumeDynamicTypeInstruction.src();
+      Value outValue = assumeDynamicTypeInstruction.outValue();
 
       // Check if we need to run the type analysis for the affected values of the out-value.
       if (!outValue.getType().equals(inValue.getType())) {
         affectedValues.addAll(outValue.affectedValues());
       }
 
-      if (outValue.hasPhiUsers()) {
+      if (outValue.numberOfPhiUsers() > 0) {
         mayHaveIntroducedTrivialPhi = true;
       }
 
@@ -86,20 +78,16 @@
     }
   }
 
-  public AssumeRemover removeMarkedInstructions() {
-    return removeMarkedInstructions(null);
-  }
-
-  public AssumeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
-    if (!assumeInstructionsToRemove.isEmpty()) {
+  public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
+    if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) {
       for (BasicBlock block : code.blocks) {
-        if (blocksToBeRemoved != null && blocksToBeRemoved.contains(block)) {
+        if (blocksToBeRemoved.contains(block)) {
           continue;
         }
         InstructionListIterator instructionIterator = block.listIterator(code);
         while (instructionIterator.hasNext()) {
           Instruction instruction = instructionIterator.next();
-          if (instruction.isAssume()) {
+          if (instruction.isAssumeDynamicType()) {
             removeIfMarked(instruction.asAssume(), instructionIterator);
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index b4ccb7d..611a0a1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -3,19 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 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.DexMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.graph.DexField;
 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.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -32,11 +29,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TriConsumer;
-import com.android.tools.r8.utils.TriFunction;
-import com.android.tools.r8.utils.TriPredicate;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -50,23 +44,24 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 
-public class AssumeInserter {
+public class AssumeInserter implements Assumer {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final InternalOptions options;
 
   public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
-    this.options = appView.options();
   }
 
+  @Override
   public void insertAssumeInstructions(IRCode code, Timing timing) {
     insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
   }
 
+  @Override
   public void insertAssumeInstructionsInBlocks(
       IRCode code,
       BasicBlockIterator blockIterator,
@@ -94,7 +89,7 @@
     timing.end();
 
     timing.begin("Part 3: Compute dominated users");
-    Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues =
+    Map<Instruction, Set<Value>> redundantKeys =
         computeDominanceForAssumedValues(code, assumedValues);
     timing.end();
     if (assumedValues.isEmpty()) {
@@ -102,7 +97,7 @@
     }
 
     timing.begin("Part 4: Remove redundant dominated assume instructions");
-    removeRedundantDominatedAssumeInstructions(assumedValues, redundantAssumedValues);
+    removeRedundantDominatedAssumeInstructions(assumedValues, redundantKeys);
     timing.end();
     if (assumedValues.isEmpty()) {
       return;
@@ -150,15 +145,29 @@
         }
       }
 
+      Value outValue = current.outValue();
       if (current.isInvokeMethod()) {
-        // Case (2) and (3).
-        needsAssumeInstruction |=
-            computeAssumedValuesForInvokeMethod(
-                code, current.asInvokeMethod(), assumedValuesBuilder);
+        InvokeMethod invoke = current.asInvokeMethod();
+        if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
+          // Case (2) and (3).
+          needsAssumeInstruction |=
+              computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
+        }
       } else if (current.isFieldGet()) {
         // Case (4), field-get instructions that are guaranteed to read a non-null value.
-        needsAssumeInstruction |=
-            computeAssumedValuesForFieldGet(current.asFieldInstruction(), assumedValuesBuilder);
+        FieldInstruction fieldInstruction = current.asFieldInstruction();
+        DexField field = fieldInstruction.getField();
+        if (isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+          DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
+          if (encodedField != null) {
+            FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
+            if (optimizationInfo.getDynamicUpperBoundType() != null
+                && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
+              assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
+              needsAssumeInstruction = true;
+            }
+          }
+        }
       }
 
       // If we need to insert an assume instruction into a block with catch handlers, we split the
@@ -193,35 +202,6 @@
     }
   }
 
-  private boolean computeAssumedValuesForInvokeMethod(
-      IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
-    if (!invoke.hasOutValue() && invoke.getInvokedMethod().proto.parameters.isEmpty()) {
-      return false;
-    }
-
-    DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.holder.isArrayType()
-        && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
-      return computeAssumedValuesFromArrayClone(invoke, assumedValuesBuilder);
-    }
-
-    return computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
-  }
-
-  private boolean computeAssumedValuesFromArrayClone(
-      InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
-    Value outValue = invoke.outValue();
-    if (outValue == null || !outValue.hasNonDebugUsers()) {
-      return false;
-    }
-
-    TypeElement dynamicUpperBoundType =
-        TypeElement.fromDexType(invoke.getInvokedMethod().holder, definitelyNotNull(), appView);
-    assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
-        invoke, outValue, dynamicUpperBoundType, null);
-    return true;
-  }
-
   private boolean computeAssumedValuesFromSingleTarget(
       IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
@@ -234,13 +214,11 @@
 
     // Case (2), invocations that are guaranteed to return a non-null value.
     Value outValue = invoke.outValue();
-    if (outValue != null && outValue.hasNonDebugUsers()) {
-      needsAssumeInstruction =
-          computeAssumedValuesForOutValue(
-              invoke,
-              optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
-              optimizationInfo.getDynamicLowerBoundType(),
-              assumedValuesBuilder);
+    if (outValue != null
+        && optimizationInfo.neverReturnsNull()
+        && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+      assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
+      needsAssumeInstruction = true;
     }
 
     // Case (3), parameters that are not null after the invocation.
@@ -261,116 +239,31 @@
     return needsAssumeInstruction;
   }
 
-  private boolean computeAssumedValuesForFieldGet(
-      FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) {
-    Value outValue = fieldGet.outValue();
-    if (!outValue.hasNonDebugUsers()) {
-      return false;
-    }
-
-    DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
-    if (field == null) {
-      return false;
-    }
-
-    FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-    return computeAssumedValuesForOutValue(
-        fieldGet,
-        optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
-        optimizationInfo.getDynamicLowerBoundType(),
-        assumedValuesBuilder);
-  }
-
-  private boolean computeAssumedValuesForOutValue(
-      Instruction instruction,
-      TypeElement dynamicUpperBoundType,
-      ClassTypeElement dynamicLowerBoundType,
-      AssumedValues.Builder assumedValuesBuilder) {
-    Value outValue = instruction.outValue();
-
-    // Do not insert dynamic type information if it does not refine the static type.
-    boolean isRedundant =
-        !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView)
-            && dynamicLowerBoundType == null;
-    if (isRedundant) {
-      return false;
-    }
-
-    // Do not insert dynamic type information if the dynamic type only refines the nullability.
-    if (dynamicUpperBoundType.equalUpToNullability(outValue.getType())
-        && dynamicLowerBoundType == null) {
-      assert dynamicUpperBoundType.isDefinitelyNotNull();
-      assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(instruction, outValue);
-    } else {
-      assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
-          instruction, outValue, dynamicUpperBoundType, dynamicLowerBoundType);
-    }
-    return true;
-  }
-
   private void removeRedundantAssumeInstructions(AssumedValues assumedValues) {
     assumedValues.removeIf(
-        (instruction, assumedValue, assumedValueInfo) -> {
-          // Assumed values with dynamic type information are never redundant.
-          if (assumedValueInfo.hasDynamicTypeInfo()) {
-            return false;
-          }
-
-          assert assumedValueInfo.isNonNull();
-
-          // Otherwise, it is redundant if it is defined by another instruction that guarantees its
-          // non-nullness.
+        (instruction, assumedValue) -> {
           if (assumedValue.isPhi()) {
             return false;
           }
-
           Instruction definition = assumedValue.definition;
-          if (definition == instruction) {
-            return false;
-          }
-
-          AssumedValueInfo otherAssumedValueInfo =
-              assumedValues.getAssumedValueInfo(definition, assumedValue);
-          if (otherAssumedValueInfo == null) {
-            return false;
-          }
-
-          if (!otherAssumedValueInfo.isNonNull()) {
-            // This is not redundant, but we can strenghten it with the dynamic type information
-            // from the other assume instruction.
-            assumedValueInfo.setDynamicTypeAssumption(
-                otherAssumedValueInfo.getDynamicTypeAssumption());
-            return false;
-          }
-
-          return true;
+          return definition != instruction && assumedValues.contains(definition, assumedValue);
         });
   }
 
-  private Map<Instruction, Map<Value, AssumedValueInfo>> computeDominanceForAssumedValues(
+  private Map<Instruction, Set<Value>> computeDominanceForAssumedValues(
       IRCode code, AssumedValues assumedValues) {
-    Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues = new IdentityHashMap<>();
+    Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
     LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
     Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
     assumedValues.computeDominance(
-        (instruction, assumedValue, assumedValueInfo) -> {
-          Map<Value, AssumedValueInfo> alreadyAssumedValues =
-              redundantAssumedValues.get(instruction);
-          if (alreadyAssumedValues != null) {
-            AssumedValueInfo alreadyAssumedValueInfo = alreadyAssumedValues.get(assumedValue);
-            if (alreadyAssumedValueInfo != null) {
-              if (assumedValueInfo.isSubsumedBy(alreadyAssumedValueInfo)) {
-                // Returning redundant() will cause the entry (instruction, assumedValue) to be
-                // removed.
-                return AssumedDominance.redundant();
-              }
-
-              // This assume value is dominated by the other assume value, so strengthen this one.
-              assumedValueInfo.strengthenWith(alreadyAssumedValueInfo);
-            }
+        (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 the out-value of some instruction it is known to dominate all users.
+          // If this value is non-null since its definition, then it is known to dominate all users.
           if (assumedValue == instruction.outValue()) {
             return AssumedDominance.everything();
           }
@@ -434,9 +327,9 @@
 
               // Record that there is no need to insert an assume instruction for the non-null-value
               // after the given user in case the user is also a null check for the non-null-value.
-              redundantAssumedValues
-                  .computeIfAbsent(user, ignore -> new IdentityHashMap<>())
-                  .put(assumedValue, assumedValueInfo);
+              redundantKeys
+                  .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
+                  .add(assumedValue);
             }
           }
           for (Phi user : assumedValue.uniquePhiUsers()) {
@@ -448,92 +341,27 @@
           }
           return dominance.build();
         });
-    return redundantAssumedValues;
+    return redundantKeys;
   }
 
   private void removeRedundantDominatedAssumeInstructions(
-      AssumedValues assumedValues,
-      Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues) {
-    assumedValues.removeAll(redundantAssumedValues);
+      AssumedValues assumedValues, Map<Instruction, Set<Value>> redundantKeys) {
+    assumedValues.removeAll(redundantKeys);
   }
 
   private void materializeAssumeInstructions(IRCode code, AssumedValues assumedValues) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
         new IdentityHashMap<>();
-
-    // We materialize the assume instructions in two steps. First, we materialize all the assume
-    // instructions that do not dominate everything. These assume instructions can refine previous
-    // assume instructions, so we materialize those first as they are "stronger".
-    //
-    // Example:
-    //   1. Object value = getNullableValueWithDynamicType();
-    //   2. Object nullableValueWithDynamicType = assume(value, ...)
-    //   3. checkNotNull(value);
-    //   4. Object nonNullValueWithDynamicType = assume(value, ...)
-    //   5. return value;
-    //
-    // In this example, we first materialize the assume instruction in line 4, and replace the
-    // dominated use of `value` in line 5 by the new assumed value `nonNullValueWithDynamicType`.
-    // Afterwards, we materialize the assume instruction in line 2, and replace all remaining users
-    // of `value` by `nullableValueWithDynamicType`.
-    //
-    // Result:
-    //   1. Object value = getNullableValueWithDynamicType();
-    //   2. Object nullableValueWithDynamicType = assume(value, ...)
-    //   3. checkNotNull(nullableValueWithDynamicType);
-    //   4. Object nonNullValueWithDynamicType = assume(value, ...)
-    //   5. return nonNullValueWithDynamicType;
-    materializeSelectedAssumeInstructions(
-        code,
-        assumedValues,
-        affectedValues,
-        pendingInsertions,
-        assumedValueInfo -> !assumedValueInfo.dominance.isEverything());
-    materializeSelectedAssumeInstructions(
-        code,
-        assumedValues,
-        affectedValues,
-        pendingInsertions,
-        assumedValueInfo -> assumedValueInfo.dominance.isEverything());
-    pendingInsertions.forEach(
-        (block, pendingInsertionsPerInstruction) -> {
-          InstructionListIterator instructionIterator = block.listIterator(code);
-          while (instructionIterator.hasNext() && !pendingInsertionsPerInstruction.isEmpty()) {
-            Instruction instruction = instructionIterator.next();
-            List<Instruction> pendingAssumeInstructions =
-                pendingInsertionsPerInstruction.remove(instruction);
-            if (pendingAssumeInstructions != null) {
-              pendingAssumeInstructions.forEach(instructionIterator::add);
-            }
-          }
-        });
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-  }
-
-  private void materializeSelectedAssumeInstructions(
-      IRCode code,
-      AssumedValues assumedValues,
-      Set<Value> affectedValues,
-      Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions,
-      Predicate<AssumedValueInfo> predicate) {
-    assumedValues.removeIf(
+    assumedValues.forEach(
         (instruction, assumedValue, assumedValueInfo) -> {
-          if (!predicate.test(assumedValueInfo)) {
-            return false;
-          }
-
           BasicBlock block = instruction.getBlock();
           BasicBlock insertionBlock = getInsertionBlock(instruction);
 
           AssumedDominance dominance = assumedValueInfo.getDominance();
           Value newValue =
               code.createValue(
-                  assumedValueInfo.isNonNull()
-                      ? assumedValue.getType().asReferenceType().asMeetWithNotNull()
-                      : assumedValue.getType(),
+                  assumedValue.getType().asReferenceType().asMeetWithNotNull(),
                   assumedValue.getLocalInfo());
           if (dominance.isEverything()) {
             assumedValue.replaceUsers(newValue);
@@ -550,7 +378,8 @@
                       while (iterator.hasNext()) {
                         Value operand = user.getOperand(iterator.nextInt());
                         if (operand != assumedValue) {
-                          assert operand.isDefinedByInstructionSatisfying(Instruction::isAssume);
+                          assert operand.isDefinedByInstructionSatisfying(
+                              Instruction::isAssumeNonNull);
                           iterator.remove();
                         }
                       }
@@ -563,13 +392,7 @@
           affectedValues.addAll(newValue.affectedValues());
 
           Assume assumeInstruction =
-              new Assume(
-                  assumedValueInfo.dynamicTypeAssumption,
-                  assumedValueInfo.nonNullAssumption,
-                  newValue,
-                  assumedValue,
-                  instruction,
-                  appView);
+              Assume.createAssumeNonNullInstruction(newValue, assumedValue, instruction, appView);
           assumeInstruction.setPosition(instruction.getPosition());
           if (insertionBlock != block) {
             insertionBlock.listIterator(code).add(assumeInstruction);
@@ -579,8 +402,22 @@
                 .computeIfAbsent(instruction, ignore -> new ArrayList<>())
                 .add(assumeInstruction);
           }
-          return true;
         });
+    pendingInsertions.forEach(
+        (block, pendingInsertionsPerInstruction) -> {
+          InstructionListIterator instructionIterator = block.listIterator(code);
+          while (instructionIterator.hasNext() && !pendingInsertionsPerInstruction.isEmpty()) {
+            Instruction instruction = instructionIterator.next();
+            List<Instruction> pendingAssumeInstructions =
+                pendingInsertionsPerInstruction.remove(instruction);
+            if (pendingAssumeInstructions != null) {
+              pendingAssumeInstructions.forEach(instructionIterator::add);
+            }
+          }
+        });
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
   }
 
   private BasicBlock getInsertionBlock(Instruction instruction) {
@@ -624,6 +461,10 @@
     return type.isReferenceType() && type.asReferenceType().isNullable();
   }
 
+  private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) {
+    return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0;
+  }
+
   private static boolean isNullableReferenceTypeWithOtherNonDebugUsers(
       Value value, Instruction ignore) {
     if (isNullableReferenceType(value)) {
@@ -642,7 +483,6 @@
   static class AssumedValueInfo {
 
     AssumedDominance dominance;
-    DynamicTypeAssumption dynamicTypeAssumption;
     NonNullAssumption nonNullAssumption;
 
     AssumedValueInfo(AssumedDominance dominance) {
@@ -657,47 +497,9 @@
       this.dominance = dominance;
     }
 
-    boolean hasDynamicTypeInfo() {
-      return dynamicTypeAssumption != null;
-    }
-
-    DynamicTypeAssumption getDynamicTypeAssumption() {
-      return dynamicTypeAssumption;
-    }
-
-    void setDynamicTypeAssumption(DynamicTypeAssumption dynamicTypeAssumption) {
-      this.dynamicTypeAssumption = dynamicTypeAssumption;
-    }
-
-    void setDynamicTypeAssumption(
-        TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
-      dynamicTypeAssumption =
-          new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType);
-      if (dynamicUpperBoundType.isDefinitelyNotNull()) {
-        setNotNull();
-      }
-    }
-
-    boolean isNonNull() {
-      return nonNullAssumption != null;
-    }
-
     void setNotNull() {
       nonNullAssumption = NonNullAssumption.get();
     }
-
-    boolean isSubsumedBy(AssumedValueInfo other) {
-      return !hasDynamicTypeInfo() && other.isNonNull();
-    }
-
-    void strengthenWith(AssumedValueInfo info) {
-      if (info.isNonNull()) {
-        setNotNull();
-      }
-      if (!hasDynamicTypeInfo() && info.hasDynamicTypeInfo()) {
-        setDynamicTypeAssumption(info.getDynamicTypeAssumption());
-      }
-    }
   }
 
   static class AssumedValues {
@@ -717,8 +519,7 @@
       return new Builder();
     }
 
-    void computeDominance(
-        TriFunction<Instruction, Value, AssumedValueInfo, AssumedDominance> function) {
+    void computeDominance(BiFunction<Instruction, Value, AssumedDominance> function) {
       Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
           assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
@@ -738,7 +539,7 @@
             continue;
           }
           assert dominance.isUnknown();
-          dominance = function.apply(instruction, assumedValue, assumedValueInfo);
+          dominance = function.apply(instruction, assumedValue);
           if ((dominance.isNothing() && !assumedValue.isArgument()) || dominance.isUnknown()) {
             innerIterator.remove();
           } else {
@@ -751,9 +552,9 @@
       }
     }
 
-    AssumedValueInfo getAssumedValueInfo(Instruction instruction, Value assumedValue) {
+    boolean contains(Instruction instruction, Value assumedValue) {
       Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
-      return dominancePerValue != null ? dominancePerValue.get(assumedValue) : null;
+      return dominancePerValue != null && dominancePerValue.containsKey(assumedValue);
     }
 
     boolean isEmpty() {
@@ -768,12 +569,12 @@
                       consumer.accept(instruction, assumedValue, assumedValueInfo)));
     }
 
-    void removeAll(Map<Instruction, Map<Value, AssumedValueInfo>> keys) {
+    void removeAll(Map<Instruction, Set<Value>> keys) {
       keys.forEach(
-          (instruction, redundantAssumedValues) -> {
+          (instruction, values) -> {
             Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
             if (dominancePerValue != null) {
-              redundantAssumedValues.keySet().forEach(dominancePerValue::remove);
+              values.forEach(dominancePerValue::remove);
               if (dominancePerValue.isEmpty()) {
                 assumedValues.remove(instruction);
               }
@@ -781,7 +582,7 @@
           });
     }
 
-    void removeIf(TriPredicate<Instruction, Value, AssumedValueInfo> predicate) {
+    void removeIf(BiPredicate<Instruction, Value> predicate) {
       Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
           assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
@@ -791,10 +592,8 @@
         Iterator<Entry<Value, AssumedValueInfo>> innerIterator =
             dominancePerValue.entrySet().iterator();
         while (innerIterator.hasNext()) {
-          Entry<Value, AssumedValueInfo> innerEntry = innerIterator.next();
-          Value assumedValue = innerEntry.getKey();
-          AssumedValueInfo assumedValueInfo = innerEntry.getValue();
-          if (predicate.test(instruction, assumedValue, assumedValueInfo)) {
+          Value assumedValue = innerIterator.next().getKey();
+          if (predicate.test(instruction, assumedValue)) {
             innerIterator.remove();
           }
         }
@@ -810,49 +609,29 @@
           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 updateAssumedValueInfo(
-          Instruction instruction,
-          Value assumedValue,
-          AssumedDominance dominance,
-          Consumer<AssumedValueInfo> consumer) {
-        AssumedValueInfo assumedValueInfo =
-            assumedValues
-                .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
-                .computeIfAbsent(assumedValue, ignore -> new AssumedValueInfo(dominance));
-        consumer.accept(assumedValueInfo);
-        if (dominance.isEverything() && assumedValueInfo.isNonNull()) {
-          nonNullValuesKnownToDominateAllUsers.add(assumedValue);
+      private void addNonNullValue(
+          Instruction instruction, Value nonNullValue, AssumedDominance dominance) {
+        assumedValues
+            .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
+            .computeIfAbsent(nonNullValue, ignore -> new AssumedValueInfo(dominance))
+            .setNotNull();
+        if (dominance.isEverything()) {
+          assumedValuesKnownToDominateAllUsers.add(nonNullValue);
         }
       }
 
-      void addAssumedValueKnownToDominateAllUsers(
-          Instruction instruction,
-          Value assumedValue,
-          TypeElement dynamicUpperBoundType,
-          ClassTypeElement dynamicLowerBoundType) {
-        updateAssumedValueInfo(
-            instruction,
-            assumedValue,
-            AssumedDominance.everything(),
-            assumedValueInfo ->
-                assumedValueInfo.setDynamicTypeAssumption(
-                    dynamicUpperBoundType, dynamicLowerBoundType));
-      }
-
       void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
-        updateAssumedValueInfo(
-            instruction, nonNullValue, AssumedDominance.everything(), AssumedValueInfo::setNotNull);
+        addNonNullValue(instruction, nonNullValue, AssumedDominance.everything());
       }
 
       void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
-        updateAssumedValueInfo(
-            instruction, nonNullValue, AssumedDominance.unknown(), AssumedValueInfo::setNotNull);
+        addNonNullValue(instruction, nonNullValue, AssumedDominance.unknown());
       }
 
       public boolean isMaybeNull(Value value) {
-        return !nonNullValuesKnownToDominateAllUsers.contains(value);
+        return !assumedValuesKnownToDominateAllUsers.contains(value);
       }
 
       public AssumedValues build() {
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 4531e4d..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
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
@@ -84,6 +85,7 @@
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.base.Suppliers;
@@ -157,6 +159,21 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
+  public static void insertAssumeInstructions(
+      IRCode code, Collection<Assumer> assumers, Timing timing) {
+    insertAssumeInstructionsInBlocks(code, assumers, alwaysTrue(), timing);
+  }
+
+  public static void insertAssumeInstructionsInBlocks(
+      IRCode code, Collection<Assumer> assumers, Predicate<BasicBlock> blockTester, Timing timing) {
+    timing.begin("Insert assume instructions");
+    for (Assumer assumer : assumers) {
+      assumer.insertAssumeInstructionsInBlocks(code, code.listIterator(), blockTester, timing);
+      assert code.isConsistentSSA();
+    }
+    timing.end();
+  }
+
   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 instructions have been
@@ -1232,7 +1249,7 @@
       return false;
     }
 
-    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
+    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     boolean changed = false;
     boolean mayHaveRemovedTrivialPhi = false;
     Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -1265,7 +1282,7 @@
             // return false unless it is object.
             if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
               affectedValues.addAll(outValue.affectedValues());
-              assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
+              assumeDynamicTypeRemover.markUsersForRemoval(outValue);
               mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
               outValue.replaceUsers(argument);
               invoke.setOutValue(null);
@@ -1275,12 +1292,12 @@
         }
       }
     }
-    assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
+    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     if (!blocksToBeRemoved.isEmpty()) {
       code.removeBlocks(blocksToBeRemoved);
       code.removeAllDeadAndTrivialPhis(affectedValues);
       assert code.getUnreachableBlocks().isEmpty();
-    } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) {
+    } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) {
       code.removeAllDeadAndTrivialPhis(affectedValues);
     }
     if (!affectedValues.isEmpty()) {
@@ -1485,9 +1502,7 @@
       if (result == InstanceOfResult.UNKNOWN) {
         Value aliasedValue =
             inValue.getSpecificAliasedValue(
-                value ->
-                    value.isDefinedByInstructionSatisfying(
-                        Instruction::isAssumeWithDynamicTypeAssumption));
+                value -> !value.isPhi() && value.definition.isAssumeDynamicType());
         if (aliasedValue != null) {
           TypeElement dynamicType =
               aliasedValue
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 cb8f858..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
@@ -53,7 +53,6 @@
 
   public void devirtualizeInvokeInterface(IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
     ProgramMethod context = code.context();
     Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
     DominatorTree dominatorTree = new DominatorTree(code);
@@ -77,7 +76,7 @@
         // (out <-) invoke-virtual rcv_c, ... C#foo
         // ...
         // non_null_rcv <- non-null rcv_c  // <- Update the input rcv to the non-null, too.
-        if (current.isAssumeWithNonNullAssumption()) {
+        if (current.isAssumeNonNull()) {
           Assume nonNull = current.asAssume();
           Instruction origin = nonNull.origin();
           if (origin.isInvokeInterface()
@@ -254,8 +253,8 @@
                 it.next();
               }
             }
+
             affectedValues.addAll(receiver.affectedValues());
-            assumeRemover.markAssumeDynamicTypeUsersForRemoval(receiver);
             if (!receiver.hasLocalInfo()) {
               receiver.replaceSelectiveUsers(
                   newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableMap.of());
@@ -267,8 +266,6 @@
         }
       }
     }
-    assumeRemover.removeMarkedInstructions();
-    affectedValues.addAll(assumeRemover.getAffectedValues());
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
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 20e0515..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
@@ -4,19 +4,36 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.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.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.ListIterator;
+import java.util.function.Predicate;
 
-public class DynamicTypeOptimization {
+public class DynamicTypeOptimization implements Assumer {
 
   private final AppView<AppInfoWithLiveness> appView;
 
@@ -24,6 +41,125 @@
     this.appView = appView;
   }
 
+  @Override
+  public void insertAssumeInstructions(IRCode code, Timing timing) {
+    insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
+  }
+
+  @Override
+  public void insertAssumeInstructionsInBlocks(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      Predicate<BasicBlock> blockTester,
+      Timing timing) {
+    timing.begin("Insert assume dynamic type instructions");
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (blockTester.test(block)) {
+        insertAssumeDynamicTypeInstructionsInBlock(code, blockIterator, block);
+      }
+    }
+    timing.end();
+  }
+
+  // TODO(b/127461806): Should also insert AssumeDynamicType instructions after instanceof
+  //  instructions.
+  private void insertAssumeDynamicTypeInstructionsInBlock(
+      IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
+    InstructionListIterator instructionIterator = block.listIterator(code);
+    while (instructionIterator.hasNext()) {
+      Instruction current = instructionIterator.next();
+      if (!current.hasOutValue() || !current.outValue().isUsed()) {
+        continue;
+      }
+
+      TypeElement dynamicUpperBoundType;
+      ClassTypeElement dynamicLowerBoundType;
+      if (current.isInvokeMethod()) {
+        InvokeMethod invoke = current.asInvokeMethod();
+        DexMethod invokedMethod = invoke.getInvokedMethod();
+
+        DexType staticReturnTypeRaw = invokedMethod.proto.returnType;
+        if (!staticReturnTypeRaw.isReferenceType()) {
+          continue;
+        }
+
+        if (invokedMethod.holder.isArrayType()
+            && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
+          dynamicUpperBoundType =
+              TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
+          dynamicLowerBoundType = null;
+        } else {
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+          if (singleTarget == null) {
+            continue;
+          }
+
+          MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+          if (optimizationInfo.returnsArgument()) {
+            // Don't insert an assume-instruction since we will replace all usages of the out-value
+            // by the corresponding argument.
+            continue;
+          }
+
+          dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType();
+          dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+        }
+      } else if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        DexEncodedField encodedField =
+            appView.appInfo().resolveField(staticGet.getField()).getResolvedField();
+        if (encodedField == null) {
+          continue;
+        }
+
+        dynamicUpperBoundType = encodedField.getOptimizationInfo().getDynamicUpperBoundType();
+        dynamicLowerBoundType = encodedField.getOptimizationInfo().getDynamicLowerBoundType();
+      } else {
+        continue;
+      }
+
+      Value outValue = current.outValue();
+      boolean isTrivial =
+          (dynamicUpperBoundType == null
+                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView))
+              && dynamicLowerBoundType == null;
+      if (isTrivial) {
+        continue;
+      }
+
+      if (dynamicUpperBoundType == null) {
+        dynamicUpperBoundType = outValue.getType();
+      }
+
+      // Split block if needed (only debug instructions are allowed after the throwing
+      // instruction, if any).
+      BasicBlock insertionBlock =
+          block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
+
+      // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
+      Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
+      outValue.replaceUsers(specializedOutValue);
+
+      // Insert AssumeDynamicType instruction.
+      Assume assumeInstruction =
+          Assume.createAssumeDynamicTypeInstruction(
+              dynamicUpperBoundType,
+              dynamicLowerBoundType,
+              specializedOutValue,
+              outValue,
+              current,
+              appView);
+      assumeInstruction.setPosition(
+          appView.options().debug ? current.getPosition() : Position.none());
+      if (insertionBlock == block) {
+        instructionIterator.add(assumeInstruction);
+      } else {
+        insertionBlock.listIterator(code).add(assumeInstruction);
+      }
+    }
+  }
+
   /**
    * Computes the dynamic return type of the given method.
    *
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 1c5ff8b..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
@@ -932,7 +932,7 @@
       OptimizationFeedback feedback,
       InliningIRProvider inliningIRProvider,
       Timing timing) {
-    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
+    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     BasicBlockIterator blockIterator = code.listIterator();
     ClassInitializationAnalysis classInitializationAnalysis =
@@ -1025,7 +1025,7 @@
           // Mark AssumeDynamicType instruction for the out-value for removal, if any.
           Value outValue = invoke.outValue();
           if (outValue != null) {
-            assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
+            assumeDynamicTypeRemover.markUsersForRemoval(outValue);
           }
 
           boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod();
@@ -1073,14 +1073,14 @@
             IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
             blockIterator.next();
           }
-        } else if (current.isAssume()) {
-          assumeRemover.removeIfMarked(current.asAssume(), iterator);
+        } else if (current.isAssumeDynamicType()) {
+          assumeDynamicTypeRemover.removeIfMarked(current.asAssume(), iterator);
         }
       }
     }
     assert inlineeStack.isEmpty();
-    assumeRemover.removeMarkedInstructions(blocksToRemove);
-    assumeRemover.finish();
+    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToRemove);
+    assumeDynamicTypeRemover.finish();
     classInitializationAnalysis.finish();
     code.removeBlocks(blocksToRemove);
     code.removeAllDeadAndTrivialPhis();
@@ -1129,33 +1129,50 @@
       BasicBlockIterator blockIterator,
       BasicBlock block,
       Timing timing) {
+    InternalOptions options = appView.options();
+    boolean skip =
+        !(options.enableDynamicTypeOptimization
+            || options.enableNonNullTracking
+            || options.enableValuePropagation);
+    if (skip) {
+      return;
+    }
+
     BasicBlock state = IteratorUtils.peekNext(blockIterator);
 
     Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks);
 
     // Run member value propagation on the inlinee blocks.
-    if (appView.options().enableValuePropagation) {
+    if (options.enableValuePropagation) {
       rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
       applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
     }
 
     // Add non-null IRs only to the inlinee blocks.
-    insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing);
+    if (options.enableNonNullTracking) {
+      Assumer nonNullTracker = new AssumeInserter(appView);
+      applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks, timing);
+    }
 
+    // Add dynamic type assumptions only to the inlinee blocks.
+    if (options.enableDynamicTypeOptimization) {
+      applyAssumerToInlinee(
+          new DynamicTypeOptimization(appView), code, blockIterator, block, inlineeBlocks, timing);
+    }
     // Restore the old state of the iterator.
     rewindBlockIteratorToFirstInlineeBlock(blockIterator, state);
     // TODO(b/72693244): need a test where refined env in inlinee affects the caller.
   }
 
-  private void insertAssumeInstructions(
+  private void applyAssumerToInlinee(
+      Assumer assumer,
       IRCode code,
       BasicBlockIterator blockIterator,
       BasicBlock block,
       Set<BasicBlock> inlineeBlocks,
       Timing timing) {
     rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
-    new AssumeInserter(appView)
-        .insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
+    assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
     assert !blockIterator.hasNext();
   }
 
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 da5994e..d381afb 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
@@ -326,7 +326,7 @@
   }
 
   public void rewrite(IRCode code) {
-    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
+    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -352,13 +352,13 @@
               blockIterator,
               instructionIterator,
               code,
-              assumeRemover,
+              assumeDynamicTypeRemover,
               affectedValues,
               blocksToBeRemoved);
         }
       }
     }
-    assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
+    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     code.removeBlocks(blocksToBeRemoved);
     code.removeAllDeadAndTrivialPhis(affectedValues);
     code.removeUnreachableBlocks();
@@ -398,7 +398,7 @@
       ListIterator<BasicBlock> blockIterator,
       InstructionListIterator instructionIterator,
       IRCode code,
-      AssumeRemover assumeRemover,
+      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToBeRemoved) {
     DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context());
@@ -421,7 +421,8 @@
 
     DexType returnType = target.method.proto.returnType;
     if (returnType.isAlwaysNull(appView)) {
-      replaceOutValueByNull(invoke, instructionIterator, code, assumeRemover, affectedValues);
+      replaceOutValueByNull(
+          invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
     }
   }
 
@@ -429,13 +430,13 @@
       Instruction instruction,
       InstructionListIterator instructionIterator,
       IRCode code,
-      AssumeRemover assumeRemover,
+      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
     assert instructionIterator.peekPrevious() == instruction;
     if (instruction.hasOutValue()) {
       Value outValue = instruction.outValue();
       if (outValue.numberOfAllUsers() > 0) {
-        assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
+        assumeDynamicTypeRemover.markUsersForRemoval(outValue);
         instructionIterator.previous();
         affectedValues.addAll(outValue.affectedValues());
         outValue.replaceUsers(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 0e3e339..59e24a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -18,7 +18,7 @@
 import java.util.BitSet;
 import java.util.Set;
 
-public class DefaultMethodOptimizationInfo extends MethodOptimizationInfo {
+public class DefaultMethodOptimizationInfo implements MethodOptimizationInfo {
 
   public static final MethodOptimizationInfo DEFAULT_INSTANCE = new DefaultMethodOptimizationInfo();
 
@@ -124,6 +124,11 @@
   }
 
   @Override
+  public boolean neverReturnsNull() {
+    return UNKNOWN_NEVER_RETURNS_NULL;
+  }
+
+  @Override
   public boolean neverReturnsNormally() {
     return UNKNOWN_NEVER_RETURNS_NORMALLY;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 0365122..09ad5e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -26,11 +26,6 @@
 
   public abstract TypeElement getDynamicUpperBoundType();
 
-  public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
-    TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
-    return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
-  }
-
   public abstract boolean isDead();
 
   public abstract boolean valueHasBeenPropagated();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 1b79eb8..16ce797 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -15,7 +15,7 @@
 import java.util.BitSet;
 import java.util.Set;
 
-public abstract class MethodOptimizationInfo {
+public interface MethodOptimizationInfo {
 
   enum InlinePreference {
     NeverInline,
@@ -23,66 +23,63 @@
     Default
   }
 
-  public abstract boolean isDefaultMethodOptimizationInfo();
+  boolean isDefaultMethodOptimizationInfo();
 
-  public abstract boolean isUpdatableMethodOptimizationInfo();
+  boolean isUpdatableMethodOptimizationInfo();
 
-  public abstract UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
+  UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
 
-  public abstract boolean cannotBeKept();
+  boolean cannotBeKept();
 
-  public abstract boolean classInitializerMayBePostponed();
+  boolean classInitializerMayBePostponed();
 
-  public abstract TypeElement getDynamicUpperBoundType();
+  TypeElement getDynamicUpperBoundType();
 
-  public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
-    TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
-    return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
-  }
+  ClassTypeElement getDynamicLowerBoundType();
 
-  public abstract ClassTypeElement getDynamicLowerBoundType();
+  ParameterUsage getParameterUsages(int parameter);
 
-  public abstract ParameterUsage getParameterUsages(int parameter);
+  BitSet getNonNullParamOrThrow();
 
-  public abstract BitSet getNonNullParamOrThrow();
+  BitSet getNonNullParamOnNormalExits();
 
-  public abstract BitSet getNonNullParamOnNormalExits();
+  boolean hasBeenInlinedIntoSingleCallSite();
 
-  public abstract boolean hasBeenInlinedIntoSingleCallSite();
+  boolean isReachabilitySensitive();
 
-  public abstract boolean isReachabilitySensitive();
+  boolean returnsArgument();
 
-  public abstract boolean returnsArgument();
+  int getReturnedArgument();
 
-  public abstract int getReturnedArgument();
+  boolean neverReturnsNull();
 
-  public abstract boolean neverReturnsNormally();
+  boolean neverReturnsNormally();
 
-  public abstract BridgeInfo getBridgeInfo();
+  BridgeInfo getBridgeInfo();
 
-  public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility();
+  ClassInlinerEligibilityInfo getClassInlinerEligibility();
 
-  public abstract Set<DexType> getInitializedClassesOnNormalExit();
+  Set<DexType> getInitializedClassesOnNormalExit();
 
-  public abstract InstanceInitializerInfo getInstanceInitializerInfo();
+  InstanceInitializerInfo getInstanceInitializerInfo();
 
-  public abstract boolean isInitializerEnablingJavaVmAssertions();
+  boolean isInitializerEnablingJavaVmAssertions();
 
-  public abstract AbstractValue getAbstractReturnValue();
+  AbstractValue getAbstractReturnValue();
 
-  public abstract boolean forceInline();
+  boolean forceInline();
 
-  public abstract boolean neverInline();
+  boolean neverInline();
 
-  public abstract boolean checksNullReceiverBeforeAnySideEffect();
+  boolean checksNullReceiverBeforeAnySideEffect();
 
-  public abstract boolean triggersClassInitBeforeAnySideEffect();
+  boolean triggersClassInitBeforeAnySideEffect();
 
-  public abstract boolean mayHaveSideEffects();
+  boolean mayHaveSideEffects();
 
-  public abstract boolean returnValueOnlyDependsOnArguments();
+  boolean returnValueOnlyDependsOnArguments();
 
-  public abstract boolean returnValueHasBeenPropagated();
+  boolean returnValueHasBeenPropagated();
 
-  public abstract UpdatableMethodOptimizationInfo mutableCopy();
+  UpdatableMethodOptimizationInfo mutableCopy();
 }
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 21ec659..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
@@ -360,12 +360,14 @@
       return;
     }
     Value returnValue = firstExit.returnValue();
+    boolean isNeverNull = returnValue.getType().isReferenceType() && returnValue.isNeverNull();
     for (int i = 1; i < normalExits.size(); i++) {
       Return exit = normalExits.get(i).exit().asReturn();
       Value value = exit.returnValue();
       if (value != returnValue) {
         returnValue = null;
       }
+      isNeverNull &= value.getType().isReferenceType() && value.isNeverNull();
     }
     if (returnValue != null) {
       Value aliasedValue = returnValue.getAliasedValue();
@@ -380,6 +382,9 @@
         }
       }
     }
+    if (isNeverNull) {
+      feedback.methodNeverReturnsNull(method);
+    }
   }
 
   private void computeInstanceInitializerInfo(
@@ -1151,7 +1156,7 @@
       // Collect basic blocks that check nullability of the parameter.
       nullCheckedBlocks.clear();
       for (Instruction user : argument.uniqueUsers()) {
-        if (user.isAssumeWithNonNullAssumption()) {
+        if (user.isAssumeNonNull()) {
           nullCheckedBlocks.add(user.asAssume().getBlock());
         }
         if (user.isIf()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index d9db007..d4eef20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -201,6 +201,11 @@
   }
 
   @Override
+  public synchronized void methodNeverReturnsNull(DexEncodedMethod method) {
+    getMethodOptimizationInfoForUpdating(method).markNeverReturnsNull();
+  }
+
+  @Override
   public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 49ffaf9..435f05f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -90,6 +90,9 @@
   public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {}
 
   @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {}
+
+  @Override
   public void methodNeverReturnsNormally(DexEncodedMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 221fbd9..9696162 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -104,7 +104,7 @@
   @Override
   public void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {
-    method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type);
+    // Ignored.
   }
 
   @Override
@@ -124,6 +124,11 @@
   }
 
   @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {
+    method.getMutableOptimizationInfo().markNeverReturnsNull();
+  }
+
+  @Override
   public void methodNeverReturnsNormally(DexEncodedMethod method) {
     // Ignored.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 1728999..c1012ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -22,7 +22,7 @@
 import java.util.Set;
 import java.util.function.Function;
 
-public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
+public class UpdatableMethodOptimizationInfo implements MethodOptimizationInfo {
 
   private Set<DexType> initializedClassesOnNormalExit =
       DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
@@ -71,9 +71,9 @@
   private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4;
   private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8;
   private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
-  private static final int UNUSED_FLAG_1 = 0x20;
+  private static final int NEVER_RETURNS_NULL_FLAG = 0x20;
   private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
-  private static final int UNUSED_FLAG_2 = 0x80;
+  private static final int UNUSED_FLAG = 0x80;
   private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
   private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
   private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
@@ -97,10 +97,11 @@
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments())
             * RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG;
-    defaultFlags |= 0 * UNUSED_FLAG_1;
+    defaultFlags |=
+        BooleanUtils.intValue(defaultOptInfo.neverReturnsNull()) * NEVER_RETURNS_NULL_FLAG;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
-    defaultFlags |= 0 * UNUSED_FLAG_2;
+    defaultFlags |= 0 * UNUSED_FLAG;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
             * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -292,6 +293,11 @@
   }
 
   @Override
+  public boolean neverReturnsNull() {
+    return isFlagSet(NEVER_RETURNS_NULL_FLAG);
+  }
+
+  @Override
   public boolean neverReturnsNormally() {
     return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG);
   }
@@ -396,6 +402,10 @@
     setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
+  void markNeverReturnsNull() {
+    setFlag(NEVER_RETURNS_NULL_FLAG);
+  }
+
   void markNeverReturnsNormally() {
     setFlag(NEVER_RETURNS_NORMALLY_FLAG);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index ad6313e..3a7e099 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -44,7 +44,8 @@
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
-    if (appView.enableWholeProgramOptimizations()) {
+    if (appView.enableWholeProgramOptimizations()
+        && appView.options().enableDynamicTypeOptimization) {
       // Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
       register(new EnumMethodOptimizer(appView));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 92c78af..a677b43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -4,15 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
@@ -60,16 +57,7 @@
     for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
       DexEncodedMethod definition = lookupMethod(method);
       if (definition != null) {
-        TypeElement staticType =
-            TypeElement.fromDexType(method.proto.returnType, maybeNull(), appView);
-        feedback.methodReturnsObjectWithUpperBoundType(
-            definition,
-            appView,
-            definition
-                .getOptimizationInfo()
-                .getDynamicUpperBoundTypeOrElse(staticType)
-                .asReferenceType()
-                .asDefinitelyNotNull());
+        feedback.methodNeverReturnsNull(definition);
       }
     }
   }
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 29250dc..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
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
-import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -391,10 +390,7 @@
   }
 
   private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
-    AssumeInserter assumeInserter = converter.assumeInserter;
-    if (assumeInserter != null) {
-      assumeInserter.insertAssumeInstructions(code, Timing.empty());
-    }
+    CodeRewriter.insertAssumeInstructions(code, converter.assumers, Timing.empty());
   }
 
   private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
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 5a286b7..8c92acf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -172,12 +172,14 @@
 
   void disableAllOptimizations() {
     disableGlobalOptimizations();
+    enableNonNullTracking = false;
     enableNameReflectionOptimization = false;
     enableStringConcatenationOptimization = false;
   }
 
   public void disableGlobalOptimizations() {
     enableArgumentRemoval = false;
+    enableDynamicTypeOptimization = false;
     enableInlining = false;
     enableClassInlining = false;
     enableClassStaticizer = false;
@@ -213,6 +215,7 @@
   public boolean libraryInterfacesMayHaveStaticInitialization = false;
 
   // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
+  public boolean enableDynamicTypeOptimization = true;
   public boolean enableFieldAssignmentTracker = true;
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
@@ -221,6 +224,7 @@
   public boolean enableArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
   public boolean enableDevirtualization = true;
+  public boolean enableNonNullTracking = true;
   public boolean enableInlining =
       !Version.isDevelopmentVersion()
           || System.getProperty("com.android.tools.r8.disableinlining") == null;
diff --git a/src/main/java/com/android/tools/r8/utils/TriPredicate.java b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
deleted file mode 100644
index 42b76a7..0000000
--- a/src/main/java/com/android/tools/r8/utils/TriPredicate.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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;
-
-public interface TriPredicate<S, T, U> {
-
-  boolean test(S s, T t, U u);
-}
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 a9f77ce..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
@@ -62,7 +62,7 @@
     while (it.hasNext()) {
       prev = curr != null && !curr.isGoto() ? curr : prev;
       curr = it.next();
-      if (curr.isAssumeWithNonNullAssumption()) {
+      if (curr.isAssumeNonNull()) {
         // Make sure non-null is added to the right place.
         assertTrue(prev == null
             || prev.throwsOnNullInput()
@@ -160,11 +160,11 @@
             if (count == 0) {
               // First one in the very first line: its value should not be replaced by NonNullMarker
               // because this instruction will happen _before_ non-null.
-              assertFalse(iput.value().definition.isAssumeWithNonNullAssumption());
+              assertFalse(iput.value().definition.isAssumeNonNull());
             } else if (count == 1) {
               // Second one after a safe invocation, which should use the value added by
               // NonNullMarker.
-              assertTrue(iput.object().definition.isAssumeWithNonNullAssumption());
+              assertTrue(iput.object().definition.isAssumeNonNull());
             }
             count++;
           }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
index ba6d920..4cab645 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -7,11 +7,13 @@
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -24,14 +26,17 @@
 @RunWith(Parameterized.class)
 public class DynamicTypeOptimizationTest extends TestBase {
 
+  private final boolean enableDynamicTypeOptimization;
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameterized.Parameters(name = "{1}, enable dynamic type optimization: {0}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
   }
 
-  public DynamicTypeOptimizationTest(TestParameters parameters) {
+  public DynamicTypeOptimizationTest(
+      boolean enableDynamicTypeOptimization, TestParameters parameters) {
+    this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
     this.parameters = parameters;
   }
 
@@ -42,8 +47,10 @@
         .addKeepMainRule(TestClass.class)
         // Keep B to ensure that we will treat it as being instantiated.
         .addKeepClassRulesWithAllowObfuscation(B.class)
+        .addOptionsModification(
+            options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
+        .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
@@ -74,7 +81,8 @@
     MethodSubject testInstanceOfRemovalMethod =
         mainClassSubject.uniqueMethodWithName("testInstanceOfRemoval");
     assertThat(testInstanceOfRemovalMethod, isPresent());
-    assertTrue(
+    assertEquals(
+        enableDynamicTypeOptimization,
         testInstanceOfRemovalMethod
             .streamInstructions()
             .noneMatch(instruction -> instruction.isInstanceOf(aClassSubject.getFinalName())));
@@ -84,16 +92,27 @@
     MethodSubject testMethodInliningMethod =
         mainClassSubject.uniqueMethodWithName("testMethodInlining");
     assertThat(testMethodInliningMethod, isPresent());
-    assertTrue(interfaceSubject.uniqueMethodWithName("world").isAbsent());
+    assertEquals(
+        enableDynamicTypeOptimization, interfaceSubject.uniqueMethodWithName("world").isAbsent());
+    if (!enableDynamicTypeOptimization) {
+      assertThat(
+          testMethodInliningMethod, invokesMethod(interfaceSubject.uniqueMethodWithName("world")));
+    }
 
     // Verify that exclamationMark() has been rebound in testMethodRebinding() unless the dynamic
     // type optimization is disabled.
     MethodSubject testMethodRebindingMethod =
         mainClassSubject.uniqueMethodWithName("testMethodRebinding");
     assertThat(testMethodRebindingMethod, isPresent());
-    assertThat(
-        testMethodRebindingMethod,
-        invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
+    if (enableDynamicTypeOptimization) {
+      assertThat(
+          testMethodRebindingMethod,
+          invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
+    } else {
+      assertThat(
+          testMethodRebindingMethod,
+          invokesMethod(interfaceSubject.uniqueMethodWithName("exclamationMark")));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 791bbae..1cbeaae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -110,14 +111,16 @@
     }
   }
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{1}, enable dynamic type optimization: {0}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
   }
 
+  private final boolean enableDynamicTypeOptimization;
   private final TestParameters parameters;
 
-  public InstanceOfRemovalTest(TestParameters parameters) {
+  public InstanceOfRemovalTest(boolean enableDynamicTypeOptimization, TestParameters parameters) {
+    this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
     this.parameters = parameters;
   }
 
@@ -158,8 +161,10 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(A.class, B.class, TestClass.class)
             .addKeepMainRule(TestClass.class)
+            .addOptionsModification(
+                options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
             .enableInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
+            .setMinApi(parameters.getRuntime())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
@@ -177,6 +182,7 @@
     MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
     Iterator<InstructionSubject> barInstructionIterator =
         barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
-    assertEquals(4, Streams.stream(barInstructionIterator).count());
+    assertEquals(
+        enableDynamicTypeOptimization ? 4 : 6, Streams.stream(barInstructionIterator).count());
   }
 }