Insert assume-not-null instructions based on assume rules

Bug: 174285670
Change-Id: Ib8ad1ba1e5e04e8a9133441e336f5e45b4de95a7
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 7ec3198..fa42fcd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -64,8 +64,18 @@
 
   public boolean instructionInstanceCanThrow(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    SuccessfulFieldResolutionResult resolutionResult =
-        appView.appInfo().resolveField(field, context).asSuccessfulResolution();
+    return internalInstructionInstanceCanThrow(
+        appView,
+        context,
+        assumption,
+        appView.appInfo().resolveField(field, context).asSuccessfulResolution());
+  }
+
+  boolean internalInstructionInstanceCanThrow(
+      AppView<?> appView,
+      ProgramMethod context,
+      SideEffectAssumption assumption,
+      SuccessfulFieldResolutionResult resolutionResult) {
     if (resolutionResult == null) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index c38c5c0..20377f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -121,12 +122,13 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
 
-      if (instructionInstanceCanThrow(appView, context, assumption)) {
+      SuccessfulFieldResolutionResult resolutionResult =
+          appInfoWithLiveness.resolveField(getField()).asSuccessfulResolution();
+      if (internalInstructionInstanceCanThrow(appView, context, assumption, resolutionResult)) {
         return true;
       }
 
-      DexEncodedField encodedField =
-          appInfoWithLiveness.resolveField(getField()).getResolvedField();
+      DexEncodedField encodedField = resolutionResult.getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
 
       if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 82d67b0..1017f53 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -28,7 +29,6 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
 
 public class StaticPut extends FieldInstruction implements StaticFieldInstruction {
 
@@ -99,22 +99,14 @@
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
-      // MemberValuePropagation will replace the field read only if the target field has bound
-      // -assumevalues rule whose return value is *single*.
-      //
-      // Note that, in principle, class initializer of the field's holder may have side effects.
-      // However, with -assumevalues, we assume that the developer wants to remove field accesses.
-      ProguardMemberRule rule = appInfoWithLiveness.assumedValues.get(getField());
-      if (rule != null && rule.getReturnValue().isSingleValue()) {
-        return false;
-      }
 
-      if (instructionInstanceCanThrow(appView, context, assumption)) {
+      SuccessfulFieldResolutionResult resolutionResult =
+          appInfoWithLiveness.resolveField(getField()).asSuccessfulResolution();
+      if (internalInstructionInstanceCanThrow(appView, context, assumption, resolutionResult)) {
         return true;
       }
 
-      DexEncodedField encodedField =
-          appInfoWithLiveness.resolveField(getField()).getResolvedField();
+      DexEncodedField encodedField = resolutionResult.getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
 
       boolean isDeadProtoExtensionField =
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 d6eae9a..ddb7ff5 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
@@ -6,11 +6,12 @@
 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.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -33,7 +34,9 @@
 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.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TriConsumer;
 import com.android.tools.r8.utils.TriFunction;
@@ -56,12 +59,10 @@
 
 public class AssumeInserter {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final InternalOptions options;
+  private final AppView<AppInfoWithLiveness> appView;
 
-  public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public AssumeInserter(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.options = appView.options();
   }
 
   public void insertAssumeInstructions(IRCode code, Timing timing) {
@@ -225,7 +226,26 @@
 
   private boolean computeAssumedValuesFromSingleTarget(
       IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+            .asSingleResolution();
+    if (resolutionResult == null) {
+      return false;
+    }
+
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+    if (invoke.hasUsedOutValue() && invoke.getOutType().isReferenceType()) {
+      AssumeInfo assumeInfo =
+          AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
+      if (assumeInfo != null
+          && assumeInfo.hasReturnInfo()
+          && assumeInfo.getReturnInfo().isNonNull()) {
+        assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, invoke.outValue());
+      }
+    }
+
     if (singleTarget == null) {
       return false;
     }
@@ -234,12 +254,11 @@
     MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
 
     // Case (2), invocations that are guaranteed to return a non-null value.
-    Value outValue = invoke.outValue();
-    if (outValue != null && outValue.hasNonDebugUsers()) {
+    if (invoke.hasUsedOutValue()) {
       needsAssumeInstruction =
           computeAssumedValuesForOutValue(
               invoke,
-              optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+              optimizationInfo.getDynamicUpperBoundTypeOrElse(invoke.getOutType()),
               optimizationInfo.getDynamicLowerBoundType(),
               assumedValuesBuilder);
     }
@@ -264,20 +283,31 @@
 
   private boolean computeAssumedValuesForFieldGet(
       FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) {
-    Value outValue = fieldGet.outValue();
-    if (!outValue.hasNonDebugUsers()) {
+    if (fieldGet.hasUnusedOutValue()) {
       return false;
     }
 
-    DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
-    if (field == null) {
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(fieldGet.getField()).asSuccessfulResolution();
+    if (resolutionResult == null) {
       return false;
     }
 
-    FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+    DexClassAndField field = resolutionResult.getResolutionPair();
+
+    if (field.getType().isReferenceType()) {
+      AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
+      if (assumeInfo != null
+          && assumeInfo.hasReturnInfo()
+          && assumeInfo.getReturnInfo().isNonNull()) {
+        assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(fieldGet, fieldGet.outValue());
+      }
+    }
+
+    FieldOptimizationInfo optimizationInfo = field.getDefinition().getOptimizationInfo();
     return computeAssumedValuesForOutValue(
         fieldGet,
-        optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+        optimizationInfo.getDynamicUpperBoundTypeOrElse(fieldGet.getOutType()),
         optimizationInfo.getDynamicLowerBoundType(),
         assumedValuesBuilder);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 2950d4c..d92d375 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -7,19 +7,20 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -34,8 +35,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -54,48 +56,17 @@
   // Fields for which we have reported warnings to due Proguard configuration rules.
   private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
 
-  private enum RuleType {
-    NONE,
-    ASSUME_NO_SIDE_EFFECTS,
-    ASSUME_VALUES
-  }
-
-  private static class ProguardMemberRuleLookup {
-
-    final RuleType type;
-    final ProguardMemberRule rule;
-
-    ProguardMemberRuleLookup(RuleType type, ProguardMemberRule rule) {
-      this.type = type;
-      this.rule = rule;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (!(other instanceof ProguardMemberRuleLookup)) {
-        return false;
-      }
-      ProguardMemberRuleLookup otherLookup = (ProguardMemberRuleLookup) other;
-      return type == otherLookup.type && rule == otherLookup.rule;
-    }
-
-    @Override
-    public int hashCode() {
-      return type.ordinal() * 31 + rule.hashCode();
-    }
-  }
-
   public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.reporter = appView.options().reporter;
   }
 
-  private boolean mayPropagateValueFor(DexEncodedField field) {
-    if (field.isProgramField(appView)) {
-      return appView.appInfo().mayPropagateValueFor(field.field);
+  private boolean mayPropagateValueFor(DexClassAndField field) {
+    if (field.isProgramField()) {
+      return appView.appInfo().mayPropagateValueFor(field.getReference());
     }
-    return appView.appInfo().assumedValues.containsKey(field.field)
-        || appView.appInfo().noSideEffects.containsKey(field.field);
+    return appView.appInfo().assumedValues.containsKey(field.getReference())
+        || appView.appInfo().noSideEffects.containsKey(field.getReference());
   }
 
   private boolean mayPropagateValueFor(DexClassAndMethod method) {
@@ -106,36 +77,25 @@
         || appView.appInfo().noSideEffects.containsKey(method.getReference());
   }
 
-  private ProguardMemberRuleLookup lookupMemberRule(DexClassAndMethod method) {
-    return method != null ? lookupMemberRule(method.getDefinition()) : null;
-  }
-
-  private ProguardMemberRuleLookup lookupMemberRule(DexDefinition definition) {
-    if (definition == null) {
-      return null;
-    }
-    DexReference reference = definition.getReference();
-    ProguardMemberRule rule = appView.appInfo().noSideEffects.get(reference);
-    if (rule != null) {
-      return new ProguardMemberRuleLookup(RuleType.ASSUME_NO_SIDE_EFFECTS, rule);
-    }
-    rule = appView.appInfo().assumedValues.get(reference);
-    if (rule != null) {
-      return new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
-    }
-    return null;
-  }
-
-  private Instruction constantReplacementFromProguardRule(
-      ProguardMemberRule rule, IRCode code, Instruction instruction) {
-    if (rule == null || !rule.hasReturnValue()) {
+  private Instruction createReplacementFromAssumeInfo(
+      AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
+    if (!assumeInfo.hasReturnInfo()) {
       return null;
     }
 
-    ProguardMemberRuleReturnValue returnValueRule = rule.getReturnValue();
+    ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo();
 
     // Check if this value can be assumed constant.
     if (returnValueRule.isSingleValue()) {
+      if (instruction.getOutType().isReferenceType()) {
+        if (returnValueRule.getSingleValue() == 0) {
+          return appView
+              .abstractValueFactory()
+              .createNullValue()
+              .createMaterializingInstruction(appView, code, instruction);
+        }
+        return null;
+      }
       return appView.abstractValueFactory()
           .createSingleNumberValue(returnValueRule.getSingleValue())
           .createMaterializingInstruction(appView, code, instruction);
@@ -177,31 +137,33 @@
     return null;
   }
 
-  private void setValueRangeFromProguardRule(ProguardMemberRule rule, Value value) {
-    if (rule.hasReturnValue() && rule.getReturnValue().isValueRange()) {
-      assert !rule.getReturnValue().isSingleValue();
-      value.setValueRange(rule.getReturnValue().getValueRange());
+  private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) {
+    if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) {
+      assert !assumeInfo.getReturnInfo().isSingleValue();
+      value.setValueRange(assumeInfo.getReturnInfo().getValueRange());
     }
   }
 
-  private boolean tryConstantReplacementFromProguard(
+  private boolean applyAssumeInfoIfPossible(
       IRCode code,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
       Instruction current,
-      ProguardMemberRuleLookup lookup) {
-    Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
+      AssumeInfo assumeInfo) {
+    Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
     if (replacement == null) {
       // Check to see if a value range can be assumed.
-      setValueRangeFromProguardRule(lookup.rule, current.outValue());
+      if (current.getOutType().isPrimitiveType()) {
+        setValueRangeFromAssumeInfo(assumeInfo, current.outValue());
+      }
       return false;
     }
     affectedValues.addAll(current.outValue().affectedValues());
-    if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
+    if (assumeInfo.isAssumeNoSideEffects()) {
       iterator.replaceCurrentInstruction(replacement);
     } else {
-      assert lookup.type == RuleType.ASSUME_VALUES;
+      assert assumeInfo.isAssumeValues();
       BasicBlock block = current.getBlock();
       Position position = current.getPosition();
       if (current.hasOutValue()) {
@@ -240,43 +202,33 @@
       return;
     }
 
-    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
-    ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget);
-    if (lookup == null) {
-      // -assumenosideeffects rules are applied to upward visible and overriding methods, but only
-      // references that have actual definitions are marked by the root set builder. So, here, we
-      // try again with a resolved target, not the direct definition, which may not exist.
-      DexEncodedMethod resolutionTarget =
-          appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget();
-      lookup = lookupMemberRule(resolutionTarget);
-    }
-
-    if (lookup != null) {
-      // Check to see if a constant value can be assumed.
-      // But, if the current matched rule is -assumenosideeffects without the return value, it won't
-      // be transformed into a replacement instruction. Check if there is -assumevalues rule bound
-      // to the target.
-      if (singleTarget != null
-          && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
-          && !lookup.rule.hasReturnValue()) {
-        ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.getReference());
-        if (rule != null) {
-          lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
-        }
-      }
-      if (tryConstantReplacementFromProguard(
-          code, affectedValues, blocks, iterator, invoke, lookup)) {
-        return;
-      }
-    }
-
-    // No Proguard rule could replace the instruction check for knowledge about the return value.
-    if (singleTarget == null || !mayPropagateValueFor(singleTarget)) {
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
+    if (resolutionResult == null) {
       return;
     }
 
-    AbstractValue abstractReturnValue =
-        singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue();
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
+    if (lookup != null
+        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, invoke, lookup)) {
+      return;
+    }
+
+    // No Proguard rule could replace the instruction check for knowledge about the return value.
+    if (singleTarget != null && !mayPropagateValueFor(singleTarget)) {
+      return;
+    }
+
+    AbstractValue abstractReturnValue;
+    if (invokedMethod.getReturnType().isAlwaysNull(appView)) {
+      abstractReturnValue = appView.abstractValueFactory().createNullValue();
+    } else if (singleTarget != null) {
+      abstractReturnValue =
+          singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue();
+    } else {
+      abstractReturnValue = UnknownValue.getInstance();
+    }
 
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
@@ -293,7 +245,7 @@
 
         if (invoke.isInvokeMethodWithReceiver()) {
           iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
-        } else if (invoke.isInvokeStatic()) {
+        } else if (invoke.isInvokeStatic() && singleTarget != null) {
           iterator.replaceCurrentInstructionByInitClassIfPossible(
               appView, code, singleTarget.getHolderType());
         }
@@ -308,7 +260,10 @@
         } else {
           iterator.add(replacement);
         }
-        singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated();
+
+        if (singleTarget != null) {
+          singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated();
+        }
       }
     }
   }
@@ -322,8 +277,9 @@
     DexField field = current.getField();
 
     // TODO(b/123857022): Should be able to use definitionFor().
-    DexEncodedField target = appView.appInfo().resolveField(field).getResolvedField();
-    if (target == null) {
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(field).asSuccessfulResolution();
+    if (resolutionResult == null) {
       boolean replaceCurrentInstructionWithConstNull =
           appView.withGeneratedExtensionRegistryShrinker(
               shrinker -> shrinker.wasRemoved(field), false);
@@ -333,7 +289,9 @@
       return;
     }
 
-    if (target.isStatic() != current.isStaticGet()) {
+    DexClassAndField target = resolutionResult.getResolutionPair();
+    DexEncodedField definition = target.getDefinition();
+    if (definition.isStatic() != current.isStaticGet()) {
       return;
     }
 
@@ -342,33 +300,35 @@
     }
 
     // Check if there is a Proguard configuration rule that specifies the value of the field.
-    ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+    AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target);
     if (lookup != null
-        && tryConstantReplacementFromProguard(
-            code, affectedValues, blocks, iterator, current, lookup)) {
+        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) {
       return;
     }
 
     AbstractValue abstractValue;
     if (field.getType().isAlwaysNull(appView)) {
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
-    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
-      abstractValue = target.getOptimizationInfo().getAbstractValue();
-      if (abstractValue.isUnknown() && !target.isStatic()) {
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(definition)) {
+      abstractValue = definition.getOptimizationInfo().getAbstractValue();
+      if (abstractValue.isUnknown() && !definition.isStatic()) {
         AbstractValue abstractReceiverValue =
             current.asInstanceGet().object().getAbstractValue(appView, code.context());
         if (abstractReceiverValue.isSingleFieldValue()) {
           abstractValue =
-              abstractReceiverValue.asSingleFieldValue().getState().getAbstractFieldValue(target);
+              abstractReceiverValue
+                  .asSingleFieldValue()
+                  .getState()
+                  .getAbstractFieldValue(definition);
         }
       }
-    } else if (target.isStatic()) {
+    } else if (definition.isStatic()) {
       // This is guaranteed to read the static value of the field.
-      abstractValue = target.getStaticValue().toAbstractValue(appView.abstractValueFactory());
+      abstractValue = definition.getStaticValue().toAbstractValue(appView.abstractValueFactory());
       // Verify that the optimization info is consistent with the static value.
-      assert target.getOptimizationInfo().getAbstractValue().isUnknown()
-          || !target.hasExplicitStaticValue()
-          || abstractValue == target.getOptimizationInfo().getAbstractValue();
+      assert definition.getOptimizationInfo().getAbstractValue().isUnknown()
+          || !definition.hasExplicitStaticValue()
+          || abstractValue == definition.getOptimizationInfo().getAbstractValue();
     } else {
       // This is guaranteed to read the default value of the field.
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
@@ -411,7 +371,8 @@
         } else {
           iterator.add(replacement);
         }
-        feedback.markFieldAsPropagated(target);
+
+        feedback.markFieldAsPropagated(definition);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
new file mode 100644
index 0000000..a704abb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
+
+import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+
+public class AssumeInfo {
+
+  public enum AssumeType {
+    ASSUME_NO_SIDE_EFFECTS,
+    ASSUME_VALUES;
+
+    AssumeType meet(AssumeType type) {
+      return this == ASSUME_NO_SIDE_EFFECTS || type == ASSUME_NO_SIDE_EFFECTS
+          ? ASSUME_NO_SIDE_EFFECTS
+          : ASSUME_VALUES;
+    }
+  }
+
+  private final AssumeType type;
+  private final ProguardMemberRule rule;
+
+  public AssumeInfo(AssumeType type, ProguardMemberRule rule) {
+    this.type = type;
+    this.rule = rule;
+  }
+
+  public boolean hasReturnInfo() {
+    return rule.hasReturnValue();
+  }
+
+  public ProguardMemberRuleReturnValue getReturnInfo() {
+    return rule.getReturnValue();
+  }
+
+  public boolean isAssumeNoSideEffects() {
+    return type == AssumeType.ASSUME_NO_SIDE_EFFECTS;
+  }
+
+  public boolean isAssumeValues() {
+    return type == AssumeType.ASSUME_VALUES;
+  }
+
+  public AssumeInfo meet(AssumeInfo lookup) {
+    return new AssumeInfo(type.meet(lookup.type), rule.hasReturnValue() ? rule : lookup.rule);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null) {
+      return false;
+    }
+    if (!(other instanceof AssumeInfo)) {
+      return false;
+    }
+    AssumeInfo assumeInfo = (AssumeInfo) other;
+    return type == assumeInfo.type && rule == assumeInfo.rule;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.ordinal() * 31 + rule.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
new file mode 100644
index 0000000..5ddba94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardMemberRule;
+
+public class AssumeInfoLookup {
+
+  public static AssumeInfo lookupAssumeInfo(
+      AppView<AppInfoWithLiveness> appView,
+      SingleResolutionResult resolutionResult,
+      DexClassAndMethod singleTarget) {
+    AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
+    if (resolutionLookup == null) {
+      return singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+    }
+    AssumeInfo singleTargetLookup =
+        singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+    return singleTargetLookup != null
+        ? resolutionLookup.meet(singleTargetLookup)
+        : resolutionLookup;
+  }
+
+  public static AssumeInfo lookupAssumeInfo(
+      AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
+    DexMember<?, ?> reference = member.getReference();
+    ProguardMemberRule assumeNoSideEffectsRule = appView.appInfo().noSideEffects.get(reference);
+    ProguardMemberRule assumeValuesRule = appView.appInfo().assumedValues.get(reference);
+    if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
+      return null;
+    }
+    AssumeType type =
+        assumeNoSideEffectsRule != null
+            ? AssumeType.ASSUME_NO_SIDE_EFFECTS
+            : AssumeType.ASSUME_VALUES;
+    if ((assumeNoSideEffectsRule != null && assumeNoSideEffectsRule.hasReturnValue())
+        || assumeValuesRule == null) {
+      return new AssumeInfo(type, assumeNoSideEffectsRule);
+    }
+    return new AssumeInfo(type, assumeValuesRule);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index c8545c7..11b8868 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -60,6 +60,10 @@
     return type == Type.FIELD;
   }
 
+  public boolean isNonNull() {
+    return isValueRange() && getValueRange().getMin() > 0;
+  }
+
   public boolean isNull() {
     return type == Type.NULL;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index a4c9785..3a6f0e4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Argument;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -63,7 +63,7 @@
       boolean npeCaught,
       BiConsumer<AppView<?>, IRCode> inspector)
       throws Exception {
-    AppView<? extends AppInfoWithClassHierarchy> appView = build(mainClass);
+    AppView<AppInfoWithLiveness> appView = build(mainClass);
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(mainClass.getName()).method(signature);
     IRCode irCode = fooSubject.buildIR();
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 daa21f4..f5431bb 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
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -49,7 +49,7 @@
       int expectedNumberOfNonNull,
       Consumer<IRCode> testAugmentedIRCode)
       throws Exception {
-    AppView<? extends AppInfoWithClassHierarchy> appView = build(testClass);
+    AppView<AppInfoWithLiveness> appView = build(testClass);
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(testClass.getName()).method(signature);
     IRCode code = fooSubject.buildIR();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 6b5f0cb..7d5e23e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -5,13 +5,12 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class NonNullTrackerTestBase extends TestBase {
 
-  protected AppView<? extends AppInfoWithClassHierarchy> build(Class<?> mainClass)
-      throws Exception {
-    return computeAppViewWithSubtyping(buildAndroidApp(ToolHelper.getClassAsBytes(mainClass)));
+  protected AppView<AppInfoWithLiveness> build(Class<?> mainClass) throws Exception {
+    return computeAppViewWithLiveness(buildAndroidApp(ToolHelper.getClassAsBytes(mainClass)));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
new file mode 100644
index 0000000..fda891e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AssumeNotNullTest extends TestBase {
+
+  private final String flavor;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, flavor: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of("assumenosideeffects", "assumevalues"),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AssumeNotNullTest(String flavor, TestParameters parameters) {
+    this.flavor = flavor;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-" + flavor + " class " + Factory.class.getTypeName() + " {",
+            "  java.lang.Object create() return 1;",
+            "}",
+            "-" + flavor + " class " + Singleton.class.getTypeName() + " {",
+            "  java.lang.Object INSTANCE return 1;",
+            "}")
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              if (flavor.equals("assumenosideeffects")) {
+                // With -assumenosideeffects, the method should become empty.
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .allMatch(InstructionSubject::isReturnVoid));
+              } else {
+                // With -assumevalues, the Singleton.INSTANCE access should remain along with the
+                // Factory.create() invoke.
+                ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+                assertThat(factoryClassSubject, isPresent());
+
+                ClassSubject singletonClassSubject = inspector.clazz(Singleton.class);
+                assertThat(singletonClassSubject, isPresent());
+
+                assertEquals(
+                    2,
+                    mainMethodSubject
+                        .streamInstructions()
+                        .filter(
+                            instruction -> instruction.isFieldAccess() || instruction.isInvoke())
+                        .count());
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .anyMatch(
+                            instruction ->
+                                instruction.isStaticGet()
+                                    && instruction.getField().getHolderType()
+                                        == singletonClassSubject.getDexProgramClass().getType()));
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .filter(InstructionSubject::isInvoke)
+                        .anyMatch(
+                            instruction ->
+                                instruction.getMethod().getHolderType()
+                                    == factoryClassSubject.getDexProgramClass().getType()));
+              }
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      if (Singleton.INSTANCE == null) {
+        System.out.println("Foo");
+      }
+      if (Factory.create() == null) {
+        System.out.println("Bar");
+      }
+    }
+  }
+
+  static class Factory {
+
+    @NeverInline
+    public static Object create() {
+      return System.currentTimeMillis() > 0 ? new Object() : null;
+    }
+  }
+
+  static class Singleton {
+
+    public static Object INSTANCE = System.currentTimeMillis() > 0 ? new Object() : null;
+  }
+}