Remove original definitions in value propagation

This extends member value propagation to:
* remove static-get instructions that do not trigger class initialization
* replace static-get instructions that do trigger class initialization by init-class instructions
* remove invoke-static instructions that do not trigger class initialization and do not have side effects
* replace invoke-static instructions that trigger class initialization but do not have side effects by init-class instructions

Change-Id: Idcfabdca77c3bc12658f055b1a9dec592e87b61a
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 12ff943..da92bf3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -83,7 +83,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 8ab298a..589710f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -144,7 +144,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // In debug mode, ArrayPut has a side-effect on the locals.
     if (appView.options().debug) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index c28bc4a..a084a0f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -95,7 +95,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 1574275..a9089f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -135,7 +135,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
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 366f0cf..507ae25 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
@@ -28,20 +28,6 @@
 
 public abstract class FieldInstruction extends Instruction {
 
-  public enum Assumption {
-    NONE,
-    CLASS_ALREADY_INITIALIZED,
-    RECEIVER_NOT_NULL;
-
-    boolean canAssumeClassIsAlreadyInitialized() {
-      return this == CLASS_ALREADY_INITIALIZED;
-    }
-
-    boolean canAssumeReceiverIsNotNull() {
-      return this == RECEIVER_NOT_NULL;
-    }
-  }
-
   private final DexField field;
 
   protected FieldInstruction(DexField field, Value dest, Value value) {
@@ -76,11 +62,11 @@
 
   @Override
   public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
-    return instructionInstanceCanThrow(appView, context, Assumption.NONE);
+    return instructionInstanceCanThrow(appView, context, SideEffectAssumption.NONE);
   }
 
   public AbstractError instructionInstanceCanThrow(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     DexEncodedField resolvedField;
     if (appView.enableWholeProgramOptimizations()) {
       // TODO(b/123857022): Should be possible to use definitionFor().
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 667e733..a4d0704 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -108,7 +108,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
@@ -159,4 +160,9 @@
   public boolean hasInvariantOutType() {
     return true;
   }
+
+  @Override
+  public String toString() {
+    return super.toString() + "; " + clazz.toSourceString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
index 94bfdaf..e78d3a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 
 public interface InstanceFieldInstruction {
 
@@ -16,10 +16,13 @@
 
   Value object();
 
-  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+  boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption);
 
   FieldInstruction asFieldInstruction();
 
+  boolean isInstanceFieldInstruction();
+
   boolean isInstanceGet();
 
   InstanceGet asInstanceGet();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index b070660..6d9b8e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -115,13 +115,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
@@ -161,6 +156,16 @@
   }
 
   @Override
+  public boolean isInstanceFieldInstruction() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceGet() {
     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 00a5f30..5680260 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
@@ -114,13 +114,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
@@ -200,6 +195,16 @@
   }
 
   @Override
+  public boolean isInstanceFieldInstruction() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isInstancePut() {
     return true;
   }
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 d49e290..a56f0a2 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
@@ -571,6 +571,11 @@
   }
 
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE);
+  }
+
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow();
   }
 
@@ -888,6 +893,14 @@
     return null;
   }
 
+  public boolean isInstanceFieldInstruction() {
+    return false;
+  }
+
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return null;
+  }
+
   public boolean isInstanceGet() {
     return false;
   }
@@ -1460,4 +1473,18 @@
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
     return false;
   }
+
+  public enum SideEffectAssumption {
+    NONE,
+    CLASS_ALREADY_INITIALIZED,
+    RECEIVER_NOT_NULL;
+
+    boolean canAssumeClassIsAlreadyInitialized() {
+      return this == CLASS_ALREADY_INITIALIZED;
+    }
+
+    boolean canAssumeReceiverIsNotNull() {
+      return this == RECEIVER_NOT_NULL;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index ec0c68f..b4531c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -158,14 +158,15 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.options().debug) {
       return true;
     }
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 831c65f..02e45ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -171,7 +171,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Check if the instruction has a side effect on the locals environment.
     if (hasOutValue() && outValue().hasLocalInfo()) {
       assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 8378a5d..ce75c7c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -184,7 +184,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Check if the instruction has a side effect on the locals environment.
     if (hasOutValue() && outValue().hasLocalInfo()) {
       assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index da1c8fd..2d29972 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -156,7 +156,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (!appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -187,22 +188,24 @@
       }
 
       // Verify that the target method does not have side-effects.
-      boolean targetMayHaveSideEffects;
       if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
-        targetMayHaveSideEffects = false;
-      } else {
-        targetMayHaveSideEffects =
-            target.getOptimizationInfo().mayHaveSideEffects()
-                // Verify that calling the target method won't lead to class initialization.
-                || target.method.holder.classInitializationMayHaveSideEffects(
-                    appView,
-                    // Types that are a super type of `context` are guaranteed to be initialized
-                    // already.
-                    type -> appView.isSubtype(context, type).isTrue(),
-                    Sets.newIdentityHashSet());
+        return false;
       }
 
-      return targetMayHaveSideEffects;
+      if (target.getOptimizationInfo().mayHaveSideEffects()) {
+        return true;
+      }
+
+      if (assumption.canAssumeClassIsAlreadyInitialized()) {
+        return false;
+      }
+
+      return target.method.holder.classInitializationMayHaveSideEffects(
+          appView,
+          // Types that are a super type of `context` are guaranteed to be initialized
+          // already.
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     }
 
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 1ec8873..1218f08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -142,7 +142,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (!appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -153,7 +154,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 226b771..afd87ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -129,7 +129,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Treat the instruction as possibly having side-effects if it may throw or the array is used.
     if (instructionInstanceCanThrow(appView, context).isThrowing()
         || src().numberOfAllUsers() > 1) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 23ac981..384a27a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -141,7 +141,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (!appView.enableWholeProgramOptimizations()) {
       return !(dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
index c4b5f35..286b470 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 
 public interface StaticFieldInstruction {
 
@@ -16,7 +16,8 @@
 
   boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context);
 
-  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+  boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption);
 
   FieldInstruction asFieldInstruction();
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index e7c2dee..a3c202b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -137,12 +137,12 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+    return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE);
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
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 9d25691..e7d2bdc 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
@@ -94,13 +94,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
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 c6a84aa..7e25c8a 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
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.InitClass;
-import com.android.tools.r8.ir.code.InstanceFieldInstruction;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -32,7 +31,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.StaticFieldInstruction;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -212,7 +210,7 @@
       }
       if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
-        replaceStaticFieldInstructionByClinitAccessIfPossible(
+        replaceInstructionByInitClassIfPossible(
             staticGet, staticGet.getField().holder, code, iterator, code.method.holder());
       }
       replacement.setPosition(position);
@@ -227,7 +225,7 @@
 
   private void rewriteInvokeMethodWithConstantValues(
       IRCode code,
-      DexType callingContext,
+      DexType context,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
@@ -237,7 +235,7 @@
     if (!invokedHolder.isClassType()) {
       return;
     }
-    DexEncodedMethod target = current.lookupSingleTarget(appView, callingContext);
+    DexEncodedMethod target = current.lookupSingleTarget(appView, context);
     if (target != null && target.isInstanceInitializer()) {
       // Member value propagation does not apply to constructors. Removing a call to a constructor
       // that is marked as having no side effects could lead to verification errors, due to
@@ -292,15 +290,27 @@
     AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
-      if (singleReturnValue.isMaterializableInContext(appView, callingContext)) {
+      if (singleReturnValue.isMaterializableInContext(appView, context)) {
+        BasicBlock block = current.getBlock();
+        Position position = current.getPosition();
+
         Instruction replacement =
             singleReturnValue.createMaterializingInstruction(appView, code, current);
         affectedValues.addAll(current.outValue().affectedValues());
+        current.moveDebugValues(replacement);
         current.outValue().replaceUsers(replacement.outValue());
         current.setOutValue(null);
-        replacement.setPosition(current.getPosition());
-        current.moveDebugValues(replacement);
-        if (current.getBlock().hasCatchHandlers()) {
+
+        if (current.isInvokeMethodWithReceiver()) {
+          replaceInstructionByNullCheckIfPossible(current, iterator, context);
+        } else if (current.isInvokeStatic()) {
+          replaceInstructionByInitClassIfPossible(
+              current, target.holder(), code, iterator, context);
+        }
+
+        // Insert the definition of the replacement.
+        replacement.setPosition(position);
+        if (block.hasCatchHandlers()) {
           iterator.split(code, blocks).listIterator(code).add(replacement);
         } else {
           iterator.add(replacement);
@@ -355,47 +365,45 @@
     Instruction replacement =
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
+      BasicBlock block = current.getBlock();
+      DexType context = code.method.holder();
+      Position position = current.getPosition();
+
+      // All usages are replaced by the replacement value.
       affectedValues.addAll(current.outValue().affectedValues());
-      DexType context = code.method.method.holder;
-      if (current.instructionMayHaveSideEffects(appView, context)) {
-        BasicBlock block = current.getBlock();
-        Position position = current.getPosition();
+      current.outValue().replaceUsers(replacement.outValue());
 
-        // All usages are replaced by the replacement value.
-        current.outValue().replaceUsers(replacement.outValue());
-
-        // To preserve side effects, original field-get is replaced by an explicit null-check, if
-        // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
-        if (current.isInstanceGet()) {
-          replaceInstanceFieldInstructionByNullCheckIfPossible(
-              current.asInstanceGet(), iterator, context);
-        } else {
-          replaceStaticFieldInstructionByClinitAccessIfPossible(
-              current.asStaticGet(), target.holder(), code, iterator, context);
-        }
-
-        // Insert the definition of the replacement.
-        replacement.setPosition(position);
-        if (block.hasCatchHandlers()) {
-          iterator.split(code, blocks).listIterator(code).add(replacement);
-        } else {
-          iterator.add(replacement);
-        }
+      // To preserve side effects, original field-get is replaced by an explicit null-check, if
+      // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
+      if (current.isInstanceGet()) {
+        replaceInstructionByNullCheckIfPossible(current, iterator, context);
       } else {
-        iterator.replaceCurrentInstruction(replacement);
+        replaceInstructionByInitClassIfPossible(current, target.holder(), code, iterator, context);
+      }
+
+      // Insert the definition of the replacement.
+      replacement.setPosition(position);
+      if (block.hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator(code).add(replacement);
+      } else {
+        iterator.add(replacement);
       }
       feedback.markFieldAsPropagated(target);
     }
   }
 
-  private void replaceInstanceFieldInstructionByNullCheckIfPossible(
-      InstanceFieldInstruction instruction, InstructionListIterator iterator, DexType context) {
+  private void replaceInstructionByNullCheckIfPossible(
+      Instruction instruction, InstructionListIterator iterator, DexType context) {
+    assert instruction.isInstanceFieldInstruction() || instruction.isInvokeMethodWithReceiver();
     assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
     if (instruction.instructionMayHaveSideEffects(
-        appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
+        appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
       return;
     }
-    Value receiver = instruction.object();
+    Value receiver =
+        instruction.isInstanceFieldInstruction()
+            ? instruction.asInstanceFieldInstruction().object()
+            : instruction.asInvokeMethodWithReceiver().getReceiver();
     if (receiver.isNeverNull()) {
       iterator.removeOrReplaceByDebugLocalRead();
       return;
@@ -411,6 +419,38 @@
     iterator.replaceCurrentInstruction(replacement);
   }
 
+  private void replaceInstructionByInitClassIfPossible(
+      Instruction instruction,
+      DexType holder,
+      IRCode code,
+      InstructionListIterator iterator,
+      DexType context) {
+    assert instruction.isStaticFieldInstruction() || instruction.isInvokeStatic();
+    if (instruction.instructionMayHaveSideEffects(
+        appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
+      return;
+    }
+    boolean classInitializationMayHaveSideEffects =
+        holder.classInitializationMayHaveSideEffects(
+            appView,
+            // Types that are a super type of `context` are guaranteed to be initialized
+            // already.
+            type -> appView.isSubtype(context, type).isTrue(),
+            Sets.newIdentityHashSet());
+    if (!classInitializationMayHaveSideEffects) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
+    if (!appView.canUseInitClass()) {
+      return;
+    }
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
+    if (clazz != null) {
+      Value dest = code.createValue(TypeLatticeElement.getInt());
+      iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
+    }
+  }
+
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
     DexEncodedField target = appView.appInfo().resolveField(current.getField());
@@ -422,10 +462,10 @@
       return;
     }
 
-    replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
+    replaceInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
   }
 
-  private void replaceStaticPutByClinitAccessIfNeverRead(
+  private void replaceStaticPutByInitClassIfNeverRead(
       IRCode code, InstructionListIterator iterator, StaticPut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField());
     if (field == null || appView.appInfo().isFieldRead(field)) {
@@ -436,34 +476,10 @@
       return;
     }
 
-    replaceStaticFieldInstructionByClinitAccessIfPossible(
+    replaceInstructionByInitClassIfPossible(
         current, field.holder(), code, iterator, code.method.holder());
   }
 
-  private void replaceStaticFieldInstructionByClinitAccessIfPossible(
-      StaticFieldInstruction instruction,
-      DexType holder,
-      IRCode code,
-      InstructionListIterator iterator,
-      DexType context) {
-    if (!instruction.instructionMayHaveSideEffects(appView, context)) {
-      iterator.removeOrReplaceByDebugLocalRead();
-      return;
-    }
-    if (!appView.canUseInitClass()) {
-      return;
-    }
-    if (instruction.instructionMayHaveSideEffects(
-        appView, context, FieldInstruction.Assumption.CLASS_ALREADY_INITIALIZED)) {
-      return;
-    }
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
-    if (clazz != null) {
-      Value dest = code.createValue(TypeLatticeElement.getInt());
-      iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
-    }
-  }
-
   /**
    * Replace invoke targets and field accesses with constant values where possible.
    *
@@ -491,7 +507,7 @@
         } else if (current.isInstancePut()) {
           replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
         } else if (current.isStaticPut()) {
-          replaceStaticPutByClinitAccessIfNeverRead(code, iterator, current.asStaticPut());
+          replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
index d59f954..23c2d55 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,6 +36,7 @@
     // Cannot be inlined into TestClass.main() because the static initialization of this class could
     // have side-effects; in order for R8 to be conservative, library classes are treated as if
     // their static initialization could have side-effects.
+    @NeverPropagateValue
     public static String m() {
       return "StaticMergeCandidateA.m()";
     }
@@ -44,6 +46,7 @@
 
     // Can be inlined into TestClass.main() because the static initialization of this class has no
     // side-effects.
+    @NeverPropagateValue
     public static String m() {
       return "StaticMergeCandidateB.m()";
     }
@@ -65,7 +68,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
@@ -82,8 +85,9 @@
             .addKeepMainRule(TestClass.class)
             .addOptionsModification(
                 options -> options.libraryInterfacesMayHaveStaticInitialization = true)
+            .enableMemberValuePropagationAnnotations()
             .noMinification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
@@ -94,11 +98,13 @@
             .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
             .collect(Collectors.toList());
     assertEquals(1, classes.size());
-    assertEquals(StaticMergeCandidateA.class.getTypeName(), classes.get(0).getOriginalName());
+
+    FoundClassSubject clazz = classes.get(0);
+    assertEquals(StaticMergeCandidateA.class.getTypeName(), clazz.getOriginalName());
 
     // Check that StaticMergeCandidateB.m() has not been moved into StaticMergeCandidateA, because
     // that would disable inlining of it.
-    assertEquals(1, classes.get(0).allMethods().size());
+    assertEquals(1, clazz.allMethods().size());
 
     // Check that the test class only has a main method.
     ClassSubject classSubject = inspector.clazz(TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
index 3090720..c87d144 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -66,7 +66,7 @@
     assertThat(bClassSubject, isPresent());
 
     MethodSubject methodSubject = bClassSubject.uniqueMethodWithName("method");
-    assertThat(methodSubject, isPresent());
+    assertThat(methodSubject, not(isPresent()));
 
     // TestClass.missingFieldValuePropagation() and TestClass.missingMethodValuePropagation() are
     // absent.
@@ -85,11 +85,6 @@
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
             .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field));
-    assertTrue(
-        mainMethodSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeStatic)
-            .anyMatch(x -> x.getMethod() == methodSubject.getMethod().method));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index f9d9d21..f743bf2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -85,12 +85,12 @@
     ClassSubject mainClass = codeInspector.clazz(MAIN);
     MethodSubject mainMethod = mainClass.mainMethod();
     assertThat(mainMethod, isPresent());
-    int expectedCount = isR8 ? 3 : (isRelease ? 5 : 7);
+    int expectedCount = isR8 ? 4 : (isRelease ? 5 : 7);
     assertEquals(expectedCount, countCall(mainMethod, "String", "valueOf"));
     // Due to the different behavior regarding constant canonicalization.
-    expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : 1;
+    expectedCount = isR8 ? (parameters.isCfRuntime() ? 3 : 1) : 1;
     assertEquals(expectedCount, countConstNullNumber(mainMethod));
-    expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : (isRelease ? 1 : 0);
+    expectedCount = isR8 ? 1 : (isRelease ? 1 : 0);
     assertEquals(expectedCount, countNullStringNumber(mainMethod));
 
     MethodSubject hideNPE = mainClass.uniqueMethodWithName("hideNPE");
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index 809a521..3c80a6a 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -125,8 +125,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessInstanceMethod")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessInstanceMethod")
                             .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main")
                             .build())));
@@ -153,8 +151,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessInstanceField")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessInstanceField")
                             .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main")
                             .build())));
@@ -183,8 +179,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessStaticField")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessStaticField")
                             .addWithoutFileNameAndLineNumber(
                                 TestClassForInlineStaticField.class, "main")
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index 0212a16..e23b80b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -48,7 +49,8 @@
 
   @Parameters(name = "{1}, include WorldGreeter: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public ServiceLoaderTest(boolean includeWorldGreeter, TestParameters parameters) {
@@ -89,7 +91,8 @@
                   options.enableInliningOfInvokesWithNullableReceivers = false;
                 })
             .enableGraphInspector()
-            .setMinApi(parameters.getRuntime())
+            .enableMemberValuePropagationAnnotations()
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
@@ -226,6 +229,7 @@
 
   public static class HelloGreeter implements Greeter {
 
+    @NeverPropagateValue
     @Override
     public String greeting() {
       return "Hello";
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
index 8cb9074..e47222b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -9,18 +9,14 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.io.ByteStreams;
 import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
 import org.junit.Test;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -40,6 +36,8 @@
   }
 
   private static class Inlinee {
+
+    @NeverPropagateValue
     public static String foo() {
       return "Hello from Inlinee!";
     }
@@ -80,7 +78,13 @@
     assertEquals(OLD_VERSION, getBaseClassVersion(inputJar));
     ProcessResult runInput = run(inputJar);
     assertEquals(0, runInput.exitCode);
-    Path outputJar = runR8(inputJar);
+    Path outputJar =
+        testForR8(Backend.CF)
+            .addProgramFiles(inputJar)
+            .addKeepMainRule(Base.class)
+            .enableMemberValuePropagationAnnotations()
+            .compile()
+            .writeToZip();
     ProcessResult runOutput = run(outputJar);
     assertEquals(runInput.toString(), runOutput.toString());
     assertNotEquals(
@@ -141,19 +145,4 @@
   private ProcessResult run(Path jar) throws Exception {
     return ToolHelper.runJava(jar, Base.class.getName());
   }
-
-  private Path runR8(Path inputJar) throws Exception {
-    List<String> keepRule =
-        Collections.singletonList(
-            "-keep class " + Base.class.getName() + " { public static void main(...); }");
-    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
-    ToolHelper.runR8(
-        R8Command.builder()
-            .addProgramFiles(inputJar)
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-            .addProguardConfiguration(keepRule, Origin.unknown())
-            .setOutput(outputJar, OutputMode.ClassFile)
-            .build());
-    return outputJar;
-  }
 }