Reprocess trivial field accesses

This CL enqueues all methods that access a constant field for reprocessing with the purpose of:
- removing all field writes to constant fields and
- replacing all constant field reads by the constant.

Change-Id: Ib53b32252ebb91e368b4a6963c2c7f84f1023c7f
Bug: 150349055, 125282093, 147799448
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 96eb447..c036f5f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -646,6 +646,7 @@
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
             application = pruner.run(application);
+
             if (options.usageInformationConsumer != null) {
               ExceptionUtils.withFinishedResourceHandler(
                   options.reporter, options.usageInformationConsumer);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index a791cfc..640b7a1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -98,7 +98,7 @@
     assert previous == null || previous == clazz;
   }
 
-  public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+  public Collection<DexProgramClass> synthesizedClasses() {
     assert checkIfObsolete();
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 6b507da..d1e70ed 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -481,9 +481,8 @@
   }
 
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
-    Set<DexType> interfaces = type.getInterfaces();
-    if (!interfaces.isEmpty()) {
-      for (DexType interfaceType : interfaces) {
+    if (type.getClassType() == dexItemFactory().objectType && !type.getInterfaces().isEmpty()) {
+      for (DexType interfaceType : type.getInterfaces()) {
         if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index ddab792..9d19406 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -12,6 +12,8 @@
 
   void flattenAccessContexts();
 
+  boolean contains(DexField field);
+
   T get(DexField field);
 
   void forEach(Consumer<T> consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 7ef10ac..5634d5b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public boolean contains(DexField field) {
+    return infos.containsKey(field);
+  }
+
+  @Override
   public FieldAccessInfoImpl get(DexField field) {
     return infos.get(field);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index ef36b5b..d5bd10a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -75,6 +76,8 @@
    * is interpreted as if we known nothing about the value of the field.
    */
   private void initializeAbstractInstanceFieldValues() {
+    FieldAccessInfoCollection<?> fieldAccessInfos =
+        appView.appInfo().getFieldAccessInfoCollection();
     ObjectAllocationInfoCollection objectAllocationInfos =
         appView.appInfo().getObjectAllocationInfoCollection();
     objectAllocationInfos.forEachClassWithKnownAllocationSites(
@@ -91,7 +94,10 @@
           Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
               new IdentityHashMap<>();
           for (DexEncodedField field : clazz.instanceFields()) {
-            abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+            FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.field);
+            if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
+              abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+            }
           }
           abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
         });
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
new file mode 100644
index 0000000..31ead7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -0,0 +1,242 @@
+// 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.ir.analysis.fieldaccess;
+
+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.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class TrivialFieldAccessReprocessor {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final PostMethodProcessor.Builder postMethodProcessorBuilder;
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedField> fieldsOfInterest = Sets.newConcurrentHashSet();
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedMethod> methodsToReprocess = Sets.newConcurrentHashSet();
+
+  public TrivialFieldAccessReprocessor(
+      AppView<AppInfoWithLiveness> appView,
+      PostMethodProcessor.Builder postMethodProcessorBuilder) {
+    this.appView = appView;
+    this.postMethodProcessorBuilder = postMethodProcessorBuilder;
+  }
+
+  public void run(
+      ExecutorService executorService, OptimizationFeedbackDelayed feedback, Timing timing)
+      throws ExecutionException {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+
+    timing.begin("Trivial field accesses analysis");
+    assert feedback.noUpdatesLeft();
+
+    timing.begin("Compute fields of interest");
+    computeFieldsOfInterest(appInfo);
+    timing.end(); // Compute fields of interest
+
+    if (fieldsOfInterest.isEmpty()) {
+      timing.end(); // Trivial field accesses analysis
+      return;
+    }
+
+    timing.begin("Clear reads from fields of interest");
+    clearReadsFromFieldsOfInterest(appInfo);
+    timing.end(); // Clear reads from fields of interest
+
+    timing.begin("Enqueue methods for reprocessing");
+    enqueueMethodsForReprocessing(appInfo, executorService);
+    timing.end(); // Enqueue methods for reprocessing
+    timing.end(); // Trivial field accesses analysis
+
+    fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+  }
+
+  private void computeFieldsOfInterest(AppInfoWithLiveness appInfo) {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      Iterable<DexEncodedField> fields =
+          clazz.classInitializationMayHaveSideEffects(appView)
+              ? clazz.instanceFields()
+              : clazz.fields();
+      for (DexEncodedField field : fields) {
+        if (canOptimizeField(field, appView) && appInfo.mayPropagateValueFor(field.field)) {
+          fieldsOfInterest.add(field);
+        }
+      }
+    }
+    assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
+  }
+
+  private void clearReadsFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    for (DexEncodedField field : fieldsOfInterest) {
+      fieldAccessInfoCollection.get(field.field).asMutable().clearReads();
+    }
+  }
+
+  private void enqueueMethodsForReprocessing(
+      AppInfoWithLiveness appInfo, ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appInfo.classes(), this::processClass, executorService);
+    ThreadUtils.processItems(appInfo.synthesizedClasses(), this::processClass, executorService);
+    postMethodProcessorBuilder.put(methodsToReprocess);
+  }
+
+  private void processClass(DexProgramClass clazz) {
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.hasCode()) {
+        method.getCode().registerCodeReferences(method, new TrivialFieldAccessUseRegistry(method));
+      }
+    }
+  }
+
+  private static boolean canOptimizeField(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
+    FieldAccessInfo fieldAccessInfo =
+        appView.appInfo().getFieldAccessInfoCollection().get(field.field);
+    if (fieldAccessInfo == null || fieldAccessInfo.isAccessedFromMethodHandle()) {
+      return false;
+    }
+    AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
+    if (abstractValue.isSingleValue()) {
+      SingleValue singleValue = abstractValue.asSingleValue();
+      if (!singleValue.isMaterializableInAllContexts(appView)) {
+        return false;
+      }
+      if (singleValue.isSingleConstValue()) {
+        return true;
+      }
+      if (singleValue.isSingleFieldValue()) {
+        SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
+        DexField singleField = singleFieldValue.getField();
+        if (singleField == field.field) {
+          return false;
+        }
+        if (singleField.type.isClassType()) {
+          ClassTypeLatticeElement fieldType =
+              TypeLatticeElement.fromDexType(singleFieldValue.getField().type, maybeNull(), appView)
+                  .asClassTypeLatticeElement();
+          return !appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(fieldType);
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean verifyNoConstantFieldsOnSynthesizedClasses(
+      AppView<AppInfoWithLiveness> appView) {
+    for (DexProgramClass clazz : appView.appInfo().synthesizedClasses()) {
+      for (DexEncodedField field : clazz.fields()) {
+        assert field.getOptimizationInfo().getAbstractValue().isUnknown();
+      }
+    }
+    return true;
+  }
+
+  class TrivialFieldAccessUseRegistry extends UseRegistry {
+
+    private final DexEncodedMethod method;
+
+    TrivialFieldAccessUseRegistry(DexEncodedMethod method) {
+      super(appView.dexItemFactory());
+      this.method = method;
+    }
+
+    private boolean registerFieldAccess(DexField field, boolean isStatic) {
+      DexEncodedField encodedField = appView.appInfo().resolveField(field);
+      if (encodedField != null) {
+        if (encodedField.isStatic() == isStatic) {
+          if (fieldsOfInterest.contains(encodedField)) {
+            methodsToReprocess.add(method);
+          }
+        } else {
+          // Should generally not happen.
+          fieldsOfInterest.remove(encodedField);
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public boolean registerInstanceFieldWrite(DexField field) {
+      return registerFieldAccess(field, false);
+    }
+
+    @Override
+    public boolean registerInstanceFieldRead(DexField field) {
+      return registerFieldAccess(field, false);
+    }
+
+    @Override
+    public boolean registerStaticFieldRead(DexField field) {
+      return registerFieldAccess(field, true);
+    }
+
+    @Override
+    public boolean registerStaticFieldWrite(DexField field) {
+      return registerFieldAccess(field, true);
+    }
+
+    @Override
+    public boolean registerInvokeVirtual(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeDirect(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeStatic(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeInterface(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeSuper(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerNewInstance(DexType type) {
+      return false;
+    }
+
+    @Override
+    public boolean registerTypeReference(DexType type) {
+      return false;
+    }
+  }
+}
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 a8e99cf..d00ff3c 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
@@ -192,34 +192,38 @@
    * default finalize() method in a field. In that case, it is not safe to remove this instruction,
    * since that could change the lifetime of the value.
    */
-  boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
+  boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo, DexEncodedField field) {
     assert isFieldPut();
+
     TypeLatticeElement type = value().getTypeLattice();
     TypeLatticeElement baseType =
         type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
-    if (baseType.isClassType()) {
-      Value root = value().getAliasedValue();
-      if (!root.isPhi() && root.definition.isNewInstance()) {
-        DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
-        if (clazz == null) {
-          return true;
-        }
-        if (clazz.superType == null) {
-          return false;
-        }
-        DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-        DexEncodedMethod resolutionResult =
-            appInfo
-                .resolveMethod(clazz.type, dexItemFactory.objectMembers.finalize)
-                .getSingleTarget();
-        return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
-      }
-
-      return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
-          baseType.asClassTypeLatticeElement());
+    if (!baseType.isClassType()) {
+      return false;
     }
 
-    return false;
+    if (field.getOptimizationInfo().getAbstractValue().isZero()) {
+      return false;
+    }
+
+    Value root = value().getAliasedValue();
+    if (!root.isPhi() && root.definition.isNewInstance()) {
+      DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
+      if (clazz == null) {
+        return true;
+      }
+      if (clazz.superType == null) {
+        return false;
+      }
+      DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+      DexEncodedMethod resolutionResult =
+          appInfo
+              .resolveMethod(clazz.type, dexItemFactory.objectMembers.finalize)
+              .getSingleTarget();
+      return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
+    }
+
+    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassTypeLatticeElement());
   }
 
   @Override
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
new file mode 100644
index 0000000..94bfdaf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -0,0 +1,30 @@
+// 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.ir.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+
+public interface InstanceFieldInstruction {
+
+  boolean hasOutValue();
+
+  Value outValue();
+
+  Value object();
+
+  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+
+  FieldInstruction asFieldInstruction();
+
+  boolean isInstanceGet();
+
+  InstanceGet asInstanceGet();
+
+  boolean isInstancePut();
+
+  InstancePut asInstancePut();
+}
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 2ca1b2a..2a7f621 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
@@ -31,7 +31,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.Set;
 
-public class InstanceGet extends FieldInstruction {
+public class InstanceGet extends FieldInstruction implements InstanceFieldInstruction {
 
   public InstanceGet(Value dest, Value object, DexField field) {
     super(field, dest, object);
@@ -56,6 +56,7 @@
     return outValue;
   }
 
+  @Override
   public Value object() {
     assert inValues.size() == 1;
     return inValues.get(0);
@@ -119,6 +120,7 @@
     return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
   }
 
+  @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, DexType context, Assumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
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 cd71750..12f3d93 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
@@ -31,7 +31,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 
-public class InstancePut extends FieldInstruction {
+public class InstancePut extends FieldInstruction implements InstanceFieldInstruction {
 
   public InstancePut(DexField field, Value object, Value value) {
     this(field, object, value, false);
@@ -63,6 +63,7 @@
     return visitor.visit(this);
   }
 
+  @Override
   public Value object() {
     return inValues.get(0);
   }
@@ -115,17 +116,23 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
 
-      if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
         return true;
       }
 
       DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
       return appInfoWithLiveness.isFieldRead(encodedField)
-          || isStoringObjectWithFinalizer(appInfoWithLiveness);
+          || isStoringObjectWithFinalizer(appInfoWithLiveness, encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
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 c9bab8b..ff1dd42 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
@@ -122,7 +122,7 @@
       }
 
       return appInfoWithLiveness.isFieldRead(encodedField)
-          || isStoringObjectWithFinalizer(appInfoWithLiveness);
+          || isStoringObjectWithFinalizer(appInfoWithLiveness, encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
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 038bc33..5b3e1ed 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.fieldaccess.FieldAccessAnalysis;
+import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -702,6 +703,9 @@
       enumUnboxer.finishAnalysis();
       enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     }
+    new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+        .run(executorService, feedback, timing);
+
     timing.begin("IR conversion phase 2");
     graphLenseForIR = appView.graphLense();
     PostMethodProcessor postMethodProcessor =
@@ -825,13 +829,8 @@
 
     // Check if what we've added to the application builder as synthesized classes are same as
     // what we've added and used through AppInfo.
-    assert appView
-            .appInfo()
-            .getSynthesizedClassesForSanityCheck()
-            .containsAll(builder.getSynthesizedClasses())
-        && builder
-            .getSynthesizedClasses()
-            .containsAll(appView.appInfo().getSynthesizedClassesForSanityCheck());
+    assert appView.appInfo().synthesizedClasses().containsAll(builder.getSynthesizedClasses())
+        && builder.getSynthesizedClasses().containsAll(appView.appInfo().synthesizedClasses());
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 6052427..e742528 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -83,7 +83,7 @@
       }
     }
 
-    void put(Set<DexEncodedMethod> methodsToRevisit) {
+    public void put(Set<DexEncodedMethod> methodsToRevisit) {
       put(methodsToRevisit, defaultCodeOptimizations);
     }
 
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 48b8637..16619d2 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
@@ -20,12 +20,14 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InstanceGet;
+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;
 import com.android.tools.r8.ir.code.InvokeMethod;
 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.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -342,22 +344,22 @@
       affectedValues.addAll(current.outValue().affectedValues());
       DexType context = code.method.method.holder;
       if (current.instructionMayHaveSideEffects(appView, context)) {
+        BasicBlock block = current.getBlock();
+        Position position = current.getPosition();
+
         // 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.
-        Instruction currentOrNullCheck;
         if (current.isInstanceGet()) {
-          currentOrNullCheck =
-              replaceInstanceGetByNullCheckIfPossible(current.asInstanceGet(), iterator, context);
-        } else {
-          currentOrNullCheck = current;
+          replaceInstanceFieldInstructionByNullCheckIfPossible(
+              current.asInstanceGet(), iterator, context);
         }
 
         // Insert the definition of the replacement.
-        replacement.setPosition(currentOrNullCheck.getPosition());
-        if (currentOrNullCheck.getBlock().hasCatchHandlers()) {
+        replacement.setPosition(position);
+        if (block.hasCatchHandlers()) {
           iterator.split(code, blocks).listIterator(code).add(replacement);
         } else {
           iterator.add(replacement);
@@ -369,14 +371,18 @@
     }
   }
 
-  private Instruction replaceInstanceGetByNullCheckIfPossible(
-      InstanceGet instruction, InstructionListIterator iterator, DexType context) {
-    assert !instruction.outValue().hasAnyUsers();
+  private void replaceInstanceFieldInstructionByNullCheckIfPossible(
+      InstanceFieldInstruction instruction, InstructionListIterator iterator, DexType context) {
+    assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
     if (instruction.instructionMayHaveSideEffects(
         appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
-      return instruction;
+      return;
     }
     Value receiver = instruction.object();
+    if (receiver.isNeverNull()) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
     InvokeMethod replacement;
     if (appView.options().canUseRequireNonNull()) {
       DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
@@ -386,7 +392,20 @@
       replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
     }
     iterator.replaceCurrentInstruction(replacement);
-    return replacement;
+  }
+
+  private void replaceInstancePutByNullCheckIfNeverRead(
+      IRCode code, InstructionListIterator iterator, InstancePut current) {
+    DexEncodedField target = appView.appInfo().resolveField(current.getField());
+    if (target == null || appView.appInfo().isFieldRead(target)) {
+      return;
+    }
+
+    if (target.isStatic() != current.isStaticGet()) {
+      return;
+    }
+
+    replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
   }
 
   /**
@@ -396,7 +415,7 @@
    */
   public void rewriteWithConstantValues(IRCode code, DexType callingContext) {
     IRMetadata metadata = code.metadata();
-    if (!metadata.mayHaveFieldGet() && !metadata.mayHaveInvokeMethod()) {
+    if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
       return;
     }
 
@@ -413,6 +432,8 @@
         } else if (current.isFieldGet()) {
           rewriteFieldGetWithConstantValues(
               code, affectedValues, blocks, iterator, current.asFieldInstruction());
+        } else if (current.isInstancePut()) {
+          replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
         }
       }
     }
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 e33b617..81d573b 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
@@ -144,6 +144,8 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+    assert appView.appInfo().getFieldAccessInfoCollection().contains(field.field);
+    assert !appView.appInfo().getFieldAccessInfoCollection().get(field.field).hasReflectiveAccess();
     if (appView.appInfo().mayPropagateValueFor(field.field)) {
       getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 5cbfa16..61ed827 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -853,7 +853,9 @@
 
   public boolean mayPropagateValueFor(DexReference reference) {
     assert checkIfObsolete();
-    return !isPinned(reference) && !neverPropagateValue.contains(reference);
+    return options().enableValuePropagation
+        && !isPinned(reference)
+        && !neverPropagateValue.contains(reference);
   }
 
   private boolean isLibraryOrClasspathField(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 05d281b..da42de6 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -18,11 +18,13 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -175,6 +177,9 @@
     clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
     usagePrinter.visited();
+    assert Streams.stream(clazz.fields())
+        .map(DexEncodedField::getOptimizationInfo)
+        .noneMatch(FieldOptimizationInfo::isDead);
   }
 
   private void rewriteNestAttributes(DexProgramClass clazz) {
diff --git a/src/test/examples/shaking1/print-mapping.ref b/src/test/examples/shaking1/print-mapping-cf.ref
similarity index 64%
rename from src/test/examples/shaking1/print-mapping.ref
rename to src/test/examples/shaking1/print-mapping-cf.ref
index d346c04..87a7bc0 100644
--- a/src/test/examples/shaking1/print-mapping.ref
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -1,6 +1,5 @@
 shaking1.Shaking -> shaking1.Shaking:
 shaking1.Used -> a.a:
-    java.lang.String name -> a
     1:1:java.lang.String method():17:17 -> a
     1:1:void main(java.lang.String[]):8:8 -> main
-    1:2:void <init>(java.lang.String):12:13 -> <init>
+    1:1:void <init>(java.lang.String):12:12 -> <init>
diff --git a/src/test/examples/shaking1/print-mapping-dex.ref b/src/test/examples/shaking1/print-mapping-dex.ref
new file mode 100644
index 0000000..602e5f8
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-dex.ref
@@ -0,0 +1,5 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+    java.lang.String method() -> a
+    1:1:void main(java.lang.String[]):8:8 -> main
+    1:1:void <init>(java.lang.String):12:12 -> <init>
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
index b9883ef..2cb3e95 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -32,7 +33,8 @@
 
 @NeverMerge
 class MySerializable implements Serializable {
-  transient int value;
+
+  @NeverPropagateValue transient int value;
 
   MySerializable(int value) {
     this.value = value;
@@ -169,6 +171,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .addKeepRuleFiles(configuration)
             .setMinApi(parameters.getApiLevel())
             .compile();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
index 7813a2d..9461c6b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -37,6 +38,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(SingletonClassInitializerPatternCanBePostponedTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableMemberValuePropagationAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -65,7 +67,7 @@
 
     static A INSTANCE = new A(" world!");
 
-    final String message;
+    @NeverPropagateValue final String message;
 
     A(String message) {
       this.message = message;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 2c8f4b3..0745620 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
@@ -88,9 +87,7 @@
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
-    //  be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
-    assertEquals(3, aClassSubject.allInstanceFields().size());
+    assertTrue(aClassSubject.allInstanceFields().isEmpty());
   }
 
   static class TestClass {
@@ -125,7 +122,7 @@
     String s = "Hello world!";
   }
 
-  enum MyEnum {
+  public enum MyEnum {
     A,
     B
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
index f68a131..433ee13 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -61,8 +61,7 @@
 
     MethodSubject configConstructorSubject = configClassSubject.init();
     assertThat(configConstructorSubject, isPresent());
-    // TODO(b/147799637): Should be true.
-    assertFalse(
+    assertTrue(
         configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
index 69b4b03..180541a 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -29,6 +30,7 @@
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 "-keep class " + B.class.getTypeName() + " { public java.lang.String f2; }")
+            .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .enableMergeAnnotations()
@@ -55,7 +57,7 @@
   @NeverMerge
   static class A {
 
-    public String f1;
+    @NeverPropagateValue public String f1;
 
     public A(String f1) {
       this.f1 = f1;
@@ -65,7 +67,7 @@
   @NeverMerge
   static class B extends A {
 
-    public String f2;
+    @NeverPropagateValue public String f2;
 
     public B(String f1, String f2) {
       super(f1);
@@ -76,7 +78,7 @@
   @NeverClassInline
   static class C extends B {
 
-    public String f3;
+    @NeverPropagateValue public String f3;
 
     public C(String f1, String f2, String f3) {
       super(f1, f2);
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
index 77190ce..943e4bf 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -34,7 +35,8 @@
 
   @Parameterized.Parameters(name = "{0}, reserve name: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public ReservedFieldNameInSubClassTest(TestParameters parameters, boolean reserveName) {
@@ -49,6 +51,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(
                 TestClass.class, A.class, B.class, C.class, I.class, J.class, K.class)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -57,7 +60,7 @@
                         + C.class.getTypeName()
                         + "{ java.lang.String a; }"
                     : "")
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -140,13 +143,13 @@
   @NeverMerge
   static class A {
 
-    String f1 = "He";
+    @NeverPropagateValue String f1 = "He";
   }
 
   @NeverMerge
   static class B extends A {
 
-    String f2 = "l";
+    @NeverPropagateValue String f2 = "l";
   }
 
   @NeverMerge
@@ -169,7 +172,7 @@
 
   static class C extends B implements J, K {
 
-    String a = "!";
+    @NeverPropagateValue String a = "!";
 
     @Override
     public String toString() {
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
index 057ba0c..309c156 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -46,6 +47,7 @@
     R8TestRunResult result =
         testForR8(Backend.DEX)
             .addProgramClasses(TestClass.class, A.class, B.class, I.class, J.class)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -102,6 +104,7 @@
         .addProgramClasses(TestClass.class, A.class, B.class)
         .addLibraryClasses(I.class, J.class)
         .addLibraryFiles(runtimeJar(Backend.DEX))
+        .enableMemberValuePropagationAnnotations()
         .enableMergeAnnotations()
         .addKeepMainRule(TestClass.class)
         .compile()
@@ -138,7 +141,7 @@
   @NeverMerge
   static class A {
 
-    String f2 = " ";
+    @NeverPropagateValue String f2 = " ";
   }
 
   static class B extends A implements J {
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
index f7551b7..4af8655 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,6 +51,7 @@
                 reserveName
                     ? "-keepclassmembernames class " + I.class.getTypeName() + "{ <fields>; }"
                     : "")
+            .enableMemberValuePropagationAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
@@ -79,6 +81,7 @@
         .addLibraryClasses(I.class)
         .addLibraryFiles(runtimeJar(Backend.DEX))
         .addKeepMainRule(TestClass.class)
+        .enableMemberValuePropagationAnnotations()
         .compile()
         .addRunClasspathFiles(testForD8().addProgramClasses(I.class).compile().writeToZip())
         .run(TestClass.class)
@@ -116,7 +119,7 @@
 
   static class A implements I {
 
-    String f2 = "world!";
+    @NeverPropagateValue String f2 = "world!";
 
     @Override
     public String toString() {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
index da47d4c..3d4b859 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +39,7 @@
   @NeverClassInline
   public static class B { // Should be kept with all members without renaming.
 
-    int foo = 4;
+    @NeverPropagateValue int foo = 4;
 
     @NeverInline
     int bar() {
@@ -63,7 +64,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingKeepPrecedenceTest(TestParameters parameters) {
@@ -90,7 +91,8 @@
         .addKeepClassRules(A.class)
         .addKeepRules("-keepclassmembernames class " + B.class.getTypeName() + " { *; }")
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getRuntime())
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
             "What is the answer to life the universe and everything?", "42")
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
index d6382aa..f527ead 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -32,8 +33,10 @@
 
   @NeverClassInline
   public static class A {
-    public int fieldA = 1;
-    public int fieldB = 2;
+
+    @NeverPropagateValue public int fieldA = 1;
+
+    @NeverPropagateValue public int fieldB = 2;
 
     @NeverInline
     public void methodA() {
@@ -75,7 +78,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingMinificationTest(TestParameters parameters) {
@@ -96,9 +99,10 @@
             .addKeepRules(
                 "-keepclassmembers class " + A.class.getTypeName() + " { void methodC(); }")
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .addApplyMapping(StringUtils.lines(pgMap))
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), C.class)
             .assertSuccessWithOutputLines("1", "2", "A.methodA", "A.methodB", "A.methodC", "B.foo")
             .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index 30a8126..3b683fa 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -29,7 +30,7 @@
 @NeverMerge
 abstract class AbstractChecker {
   // String tag -> p
-  private String tag = "PrivateInitialTag_AbstractChecker";
+  @NeverPropagateValue private String tag = "PrivateInitialTag_AbstractChecker";
 
   // check() -> x
   private void check() {
@@ -80,7 +81,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public MemberResolutionTest(TestParameters parameters) {
@@ -121,9 +122,10 @@
                 AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class)
             .addKeepMainRule(MemberResolutionTestMain.class)
             .addKeepRules("-applymapping " + mapPath)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addOptionsModification(options -> options.enableInlining = false)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MemberResolutionTestMain.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 327e1b5..9c88b7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -158,7 +158,9 @@
     Optional<ClassSubject> thing = inspector.clazz("shaking8.Thing");
     assertTrue(thing.isPresent());
     assertTrue(thing.get().field("int", "aField"));
-    assertFalse(inspector.clazz("shaking8.OtherThing").isPresent());
+    Optional<ClassSubject> otherThing = inspector.clazz("shaking8.OtherThing");
+    assertTrue(otherThing.isPresent());
+    assertTrue(otherThing.get().field("int", "otherField"));
     assertTrue(inspector.clazz("shaking8.YetAnotherThing").isPresent());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index fbe40d6..d246ba7 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -140,7 +140,11 @@
             .collect(Collectors.joining("\n"));
     String refMapping =
         new String(
-            Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")),
+            Files.readAllBytes(
+                Paths.get(
+                    EXAMPLES_DIR,
+                    "shaking1",
+                    "print-mapping-" + backend.name().toLowerCase() + ".ref")),
             StandardCharsets.UTF_8);
     Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 707a415..366a97e 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -51,7 +52,7 @@
   public enum T {
     A(A::new);
 
-    private final Supplier<Object> factory;
+    @NeverPropagateValue private final Supplier<Object> factory;
 
     T(Supplier<Object> factory) {
       this.factory = factory;
@@ -111,6 +112,7 @@
             .enableGraphInspector(consumer)
             .addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
             .addKeepMethodRules(mainMethod)
+            .enableMemberValuePropagationAnnotations()
             .setMinApi(AndroidApiLevel.N)
             .apply(
                 b -> {