Use field-never-read-before-write analysis in field assignment tracker

This refactors the current analysis from `FieldValueAnalysis` into its own class `FieldReadBeforeWriteAnalysis` to allow reusing the analysis in the `FieldAssignmentTracker`.

Fixes: b/274802355
Change-Id: I9debd6b0af9cf04fdffa1d9332743c504db59988
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index a664d9d..97ceb6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -747,7 +747,7 @@
                   dataResourceConsumer.accept(adapted, options.reporter);
                 } else {
                   options.reporter.warning(
-                      new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+                      new StringDiagnostic("Resource '" + adapted.getName() + "' already exists."));
                 }
                 options.reporter.failIfPendingErrors();
               }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index d871616..637026f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -69,6 +70,7 @@
       IRCode code,
       BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
       OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
       MethodProcessor methodProcessor) {
     if (!methodProcessor.isPrimaryMethodProcessor()) {
       return;
@@ -85,7 +87,8 @@
             appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
         if (field != null) {
           if (fieldAssignmentTracker != null) {
-            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
+            fieldAssignmentTracker.recordFieldAccess(
+                fieldInstruction, field, fieldReadBeforeWriteAnalysis);
           }
           if (fieldBitAccessAnalysis != null) {
             fieldBitAccessAnalysis.recordFieldAccess(
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 0db5825..dc90a85 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteArrayTypeFieldState;
 import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
 import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
@@ -34,6 +35,8 @@
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
@@ -179,16 +182,23 @@
         });
   }
 
-  void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
+  void recordFieldAccess(
+      FieldInstruction instruction,
+      ProgramField field,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
     if (instruction.isFieldPut()) {
-      recordFieldPut(field, instruction.value(), context);
+      recordFieldPut(instruction.asFieldPut(), field, fieldReadBeforeWriteAnalysis);
     }
   }
 
-  private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+  private void recordFieldPut(
+      FieldPut fieldPut,
+      ProgramField field,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
     // For now only attempt to prove that fields are definitely null. In order to prove a single
     // value for fields that are not definitely null, we need to prove that the given field is never
     // read before it is written.
+    Value value = fieldPut.value();
     AbstractValue abstractValue =
         value.isZero() ? abstractValueFactory.createZeroValue() : AbstractValue.unknown();
     fieldStates.compute(
@@ -203,12 +213,8 @@
               return ConcretePrimitiveTypeFieldState.create(abstractValue);
             }
             assert fieldType.isClassType();
-            DynamicType dynamicType =
-                WideningUtils.widenDynamicNonReceiverType(
-                    appView,
-                    value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
-                    field.getType());
-            return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
+            return ConcreteClassTypeFieldState.create(
+                abstractValue, getDynamicType(fieldPut, field, fieldReadBeforeWriteAnalysis));
           }
 
           if (fieldState.isUnknown()) {
@@ -231,10 +237,31 @@
 
           ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
           return classFieldState.mutableJoin(
-              appView, abstractValue, value.getDynamicType(appView), field);
+              appView,
+              abstractValue,
+              getDynamicType(fieldPut, field, fieldReadBeforeWriteAnalysis),
+              field);
         });
   }
 
+  private DynamicType getDynamicType(
+      FieldPut fieldPut,
+      ProgramField field,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
+    DynamicTypeWithUpperBound dynamicType = fieldPut.value().getDynamicType(appView);
+    if (fieldPut.isInstancePut()) {
+      InstancePut instancePut = fieldPut.asInstancePut();
+      if (fieldReadBeforeWriteAnalysis.isInstanceFieldMaybeReadBeforeInstruction(
+          instancePut.object(), field.getDefinition(), instancePut)) {
+        dynamicType = dynamicType.withNullability(Nullability.maybeNull());
+      }
+    } else if (fieldReadBeforeWriteAnalysis.isStaticFieldMaybeReadBeforeInstruction(
+        field.getDefinition(), fieldPut.asStaticPut())) {
+      dynamicType = dynamicType.withNullability(Nullability.maybeNull());
+    }
+    return WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType());
+  }
+
   void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
     Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
         abstractInstanceFieldValues.get(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java
new file mode 100644
index 0000000..fb6bae0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysis.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.readbeforewrite;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+public abstract class FieldReadBeforeWriteAnalysis {
+
+  public static FieldReadBeforeWriteAnalysis create(
+      AppView<?> appView, IRCode code, ProgramMethod context) {
+    if (appView.hasLiveness()) {
+      return new FieldReadBeforeWriteAnalysisImpl(appView.withLiveness(), code, context);
+    }
+    return trivial();
+  }
+
+  public static TrivialFieldReadBeforeWriteAnalysis trivial() {
+    return new TrivialFieldReadBeforeWriteAnalysis();
+  }
+
+  public abstract boolean isInstanceFieldMaybeReadBeforeInstruction(
+      Value receiver, DexEncodedField field, Instruction instruction);
+
+  public abstract boolean isStaticFieldMaybeReadBeforeInstruction(
+      DexEncodedField field, Instruction instruction);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java
new file mode 100644
index 0000000..7e7ac89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/FieldReadBeforeWriteAnalysisImpl.java
@@ -0,0 +1,209 @@
+// Copyright (c) 2023, 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.readbeforewrite;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.KnownFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DequeUtils;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+class FieldReadBeforeWriteAnalysisImpl extends FieldReadBeforeWriteAnalysis {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final IRCode code;
+  private final ProgramMethod context;
+
+  private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
+
+  public FieldReadBeforeWriteAnalysisImpl(
+      AppView<AppInfoWithLiveness> appView, IRCode code, ProgramMethod context) {
+    this.appView = appView;
+    this.code = code;
+    this.context = context;
+  }
+
+  @Override
+  public boolean isInstanceFieldMaybeReadBeforeInstruction(
+      Value receiver, DexEncodedField field, Instruction instruction) {
+    if (!code.context().getDefinition().isInstanceInitializer()
+        || receiver.getAliasedValue() != code.getThis()) {
+      return true;
+    }
+    return isFieldMaybeReadBeforeInstructionInInitializer(field, instruction);
+  }
+
+  @Override
+  public boolean isStaticFieldMaybeReadBeforeInstruction(
+      DexEncodedField field, Instruction instruction) {
+    if (!code.context().getDefinition().isClassInitializer()
+        || field.getHolderType() != code.context().getHolderType()) {
+      return true;
+    }
+    return isFieldMaybeReadBeforeInstructionInInitializer(field, instruction);
+  }
+
+  public boolean isFieldMaybeReadBeforeInstructionInInitializer(
+      DexEncodedField field, Instruction instruction) {
+    BasicBlock block = instruction.getBlock();
+
+    // First check if the field may be read in any of the (transitive) predecessor blocks.
+    if (fieldMaybeReadBeforeBlock(field, block)) {
+      return true;
+    }
+
+    // Then check if any of the instructions that precede the given instruction in the current block
+    // may read the field.
+    InstructionIterator instructionIterator = block.iterator();
+    while (instructionIterator.hasNext()) {
+      Instruction current = instructionIterator.next();
+      if (current == instruction) {
+        break;
+      }
+      if (current.readSet(appView, context).contains(field)) {
+        return true;
+      }
+    }
+
+    // Otherwise, the field is not read prior to the given instruction.
+    return false;
+  }
+
+  private boolean fieldMaybeReadBeforeBlock(DexEncodedField field, BasicBlock block) {
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      if (fieldMaybeReadBeforeBlockInclusive(field, predecessor)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean fieldMaybeReadBeforeBlockInclusive(DexEncodedField field, BasicBlock block) {
+    return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(field);
+  }
+
+  private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
+    if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
+      fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
+    }
+    return fieldsMaybeReadBeforeBlockInclusiveCache;
+  }
+
+  /**
+   * Eagerly creates a mapping from each block to the set of fields that may be read in that block
+   * and its transitive predecessors.
+   */
+  private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
+    Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
+    Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
+    while (!worklist.isEmpty()) {
+      BasicBlock block = worklist.removeFirst();
+      boolean seenBefore = result.containsKey(block);
+      AbstractFieldSet readSet =
+          result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance());
+      if (readSet.isTop()) {
+        // We already have unknown information for this block.
+        continue;
+      }
+
+      assert readSet.isKnownFieldSet();
+      KnownFieldSet knownReadSet = readSet.asKnownFieldSet();
+      int oldSize = seenBefore ? knownReadSet.size() : -1;
+
+      // Everything that is read in the predecessor blocks should also be included in the read set
+      // for the current block, so here we join the information from the predecessor blocks into the
+      // current read set.
+      boolean blockOrPredecessorMaybeReadAnyField = false;
+      for (BasicBlock predecessor : block.getPredecessors()) {
+        AbstractFieldSet predecessorReadSet =
+            result.getOrDefault(predecessor, EmptyFieldSet.getInstance());
+        if (predecessorReadSet.isBottom()) {
+          continue;
+        }
+        if (predecessorReadSet.isTop()) {
+          blockOrPredecessorMaybeReadAnyField = true;
+          break;
+        }
+        assert predecessorReadSet.isConcreteFieldSet();
+        if (!knownReadSet.isConcreteFieldSet()) {
+          knownReadSet = new ConcreteMutableFieldSet();
+        }
+        knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet());
+      }
+
+      if (!blockOrPredecessorMaybeReadAnyField) {
+        // Finally, we update the read set with the fields that are read by the instructions in the
+        // current block. This can be skipped if the block has already been processed.
+        if (seenBefore) {
+          assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
+        } else {
+          for (Instruction instruction : block.getInstructions()) {
+            AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+            if (instructionReadSet.isBottom()) {
+              continue;
+            }
+            if (instructionReadSet.isTop()) {
+              blockOrPredecessorMaybeReadAnyField = true;
+              break;
+            }
+            if (!knownReadSet.isConcreteFieldSet()) {
+              knownReadSet = new ConcreteMutableFieldSet();
+            }
+            knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
+          }
+        }
+      }
+
+      boolean changed = false;
+      if (blockOrPredecessorMaybeReadAnyField) {
+        // Record that this block reads all fields.
+        result.put(block, UnknownFieldSet.getInstance());
+        changed = true;
+      } else {
+        if (knownReadSet != readSet) {
+          result.put(block, knownReadSet.asConcreteFieldSet());
+        }
+        if (knownReadSet.size() != oldSize) {
+          assert knownReadSet.size() > oldSize;
+          changed = true;
+        }
+      }
+
+      if (changed) {
+        // Rerun the analysis for all successors because the state of the current block changed.
+        worklist.addAll(block.getSuccessors());
+      }
+    }
+    return result;
+  }
+
+  private boolean verifyFieldSetContainsAllFieldReadsInBlock(
+      KnownFieldSet readSet, BasicBlock block, ProgramMethod context) {
+    for (Instruction instruction : block.getInstructions()) {
+      AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+      assert !instructionReadSet.isTop();
+      if (instructionReadSet.isBottom()) {
+        continue;
+      }
+      for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
+        assert readSet.contains(field);
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java
new file mode 100644
index 0000000..9cd76dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/readbeforewrite/TrivialFieldReadBeforeWriteAnalysis.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, 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.readbeforewrite;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+class TrivialFieldReadBeforeWriteAnalysis extends FieldReadBeforeWriteAnalysis {
+
+  @Override
+  public boolean isInstanceFieldMaybeReadBeforeInstruction(
+      Value receiver, DexEncodedField field, Instruction instruction) {
+    return true;
+  }
+
+  @Override
+  public boolean isStaticFieldMaybeReadBeforeInstruction(
+      DexEncodedField field, Instruction instruction) {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index b18d02a..854d046 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -39,7 +39,7 @@
     return this;
   }
 
-  Set<DexEncodedField> getFields() {
+  public Set<DexEncodedField> getFields() {
     if (InternalOptions.assertionsEnabled()) {
       return Collections.unmodifiableSet(fields);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 9c09ef2..50c075e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -10,13 +10,14 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -24,14 +25,11 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.UnknownInstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.ArrayList;
-import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 public abstract class FieldValueAnalysis {
 
@@ -53,16 +51,20 @@
   final OptimizationFeedback feedback;
 
   private DominatorTree dominatorTree;
-  private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
+  private FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis;
 
   final Map<DexEncodedField, List<FieldInitializationInfo>> putsPerField = new IdentityHashMap<>();
 
   FieldValueAnalysis(
-      AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
     this.appView = appView;
     this.code = code;
-    this.feedback = feedback;
     this.context = code.context();
+    this.feedback = feedback;
+    this.fieldReadBeforeWriteAnalysis = fieldReadBeforeWriteAnalysis;
   }
 
   DominatorTree getOrCreateDominatorTree() {
@@ -72,13 +74,6 @@
     return dominatorTree;
   }
 
-  private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
-    if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
-      fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
-    }
-    return fieldsMaybeReadBeforeBlockInclusiveCache;
-  }
-
   boolean isInstanceFieldValueAnalysis() {
     return false;
   }
@@ -95,9 +90,9 @@
     return null;
   }
 
-  abstract boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field);
+  abstract boolean isSubjectToOptimizationIgnoringPinning(ProgramField field);
 
-  abstract boolean isSubjectToOptimization(DexEncodedField field);
+  abstract boolean isSubjectToOptimization(ProgramField field);
 
   void recordFieldPut(DexEncodedField field, Instruction instruction) {
     recordFieldPut(field, instruction, UnknownInstanceFieldInitializationInfo.getInstance());
@@ -127,13 +122,12 @@
           DexField field = fieldPut.getField();
           ProgramField programField = appInfo.resolveField(field).getProgramField();
           if (programField != null) {
-            DexEncodedField encodedField = programField.getDefinition();
-            if (isSubjectToOptimization(encodedField)) {
-              recordFieldPut(encodedField, fieldPut);
+            if (isSubjectToOptimization(programField)) {
+              recordFieldPut(programField.getDefinition(), fieldPut);
             } else if (isStaticFieldValueAnalysis()
                 && programField.getHolder().isEnum()
-                && isSubjectToOptimizationIgnoringPinning(encodedField)) {
-              recordFieldPut(encodedField, fieldPut);
+                && isSubjectToOptimizationIgnoringPinning(programField)) {
+              recordFieldPut(programField.getDefinition(), fieldPut);
             }
           }
         } else if (isInstanceFieldValueAnalysis()
@@ -144,186 +138,54 @@
       }
     }
 
+    boolean checkDominance = !isStraightLineCode;
     List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-    for (Entry<DexEncodedField, List<FieldInitializationInfo>> entry : putsPerField.entrySet()) {
-      DexEncodedField field = entry.getKey();
-      List<FieldInitializationInfo> fieldPuts = entry.getValue();
-      if (fieldPuts.size() > 1) {
-        continue;
-      }
-      FieldInitializationInfo info = ListUtils.first(fieldPuts);
-      Instruction instruction = info.instruction;
-      if (instruction.isInvokeDirect()) {
-        asInstanceFieldValueAnalysis()
-            .recordInstanceFieldIsInitializedWithInfo(field, info.instanceFieldInitializationInfo);
-        continue;
-      }
-      FieldInstruction fieldPut = instruction.asFieldInstruction();
-      if (!isStraightLineCode) {
-        if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
-          continue;
-        }
-      }
-      boolean priorReadsWillReadSameValue =
-          !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
-      if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
-        // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
-        if (isStaticFieldValueAnalysis()) {
-          // At this point the value read in the field can be only the default static value, if read
-          // prior to the put, or the value put, if read after the put. We still want to record it
-          // because the default static value is typically null/0, so code present after a null/0
-          // check can take advantage of the optimization.
-          DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
-          asStaticFieldValueAnalysis()
-              .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
-        }
-        continue;
-      }
-      updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
-    }
-  }
-
-  private boolean fieldMaybeReadBeforeInstruction(
-      DexEncodedField encodedField, Instruction instruction) {
-    BasicBlock block = instruction.getBlock();
-
-    // First check if the field may be read in any of the (transitive) predecessor blocks.
-    if (fieldMaybeReadBeforeBlock(encodedField, block)) {
-      return true;
-    }
-
-    // Then check if any of the instructions that precede the given instruction in the current block
-    // may read the field.
-    InstructionIterator instructionIterator = block.iterator();
-    while (instructionIterator.hasNext()) {
-      Instruction current = instructionIterator.next();
-      if (current == instruction) {
-        break;
-      }
-      if (current.readSet(appView, context).contains(encodedField)) {
-        return true;
-      }
-    }
-
-    // Otherwise, the field is not read prior to the given instruction.
-    return false;
-  }
-
-  private boolean fieldMaybeReadBeforeBlock(DexEncodedField encodedField, BasicBlock block) {
-    for (BasicBlock predecessor : block.getPredecessors()) {
-      if (fieldMaybeReadBeforeBlockInclusive(encodedField, predecessor)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private boolean fieldMaybeReadBeforeBlockInclusive(
-      DexEncodedField encodedField, BasicBlock block) {
-    return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(encodedField);
-  }
-
-  /**
-   * Eagerly creates a mapping from each block to the set of fields that may be read in that block
-   * and its transitive predecessors.
-   */
-  private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
-    Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
-    Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
-    while (!worklist.isEmpty()) {
-      BasicBlock block = worklist.removeFirst();
-      boolean seenBefore = result.containsKey(block);
-      AbstractFieldSet readSet =
-          result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance());
-      if (readSet.isTop()) {
-        // We already have unknown information for this block.
-        continue;
-      }
-
-      assert readSet.isKnownFieldSet();
-      KnownFieldSet knownReadSet = readSet.asKnownFieldSet();
-      int oldSize = seenBefore ? knownReadSet.size() : -1;
-
-      // Everything that is read in the predecessor blocks should also be included in the read set
-      // for the current block, so here we join the information from the predecessor blocks into the
-      // current read set.
-      boolean blockOrPredecessorMaybeReadAnyField = false;
-      for (BasicBlock predecessor : block.getPredecessors()) {
-        AbstractFieldSet predecessorReadSet =
-            result.getOrDefault(predecessor, EmptyFieldSet.getInstance());
-        if (predecessorReadSet.isBottom()) {
-          continue;
-        }
-        if (predecessorReadSet.isTop()) {
-          blockOrPredecessorMaybeReadAnyField = true;
-          break;
-        }
-        assert predecessorReadSet.isConcreteFieldSet();
-        if (!knownReadSet.isConcreteFieldSet()) {
-          knownReadSet = new ConcreteMutableFieldSet();
-        }
-        knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet());
-      }
-
-      if (!blockOrPredecessorMaybeReadAnyField) {
-        // Finally, we update the read set with the fields that are read by the instructions in the
-        // current block. This can be skipped if the block has already been processed.
-        if (seenBefore) {
-          assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
-        } else {
-          for (Instruction instruction : block.getInstructions()) {
-            AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
-            if (instructionReadSet.isBottom()) {
-              continue;
-            }
-            if (instructionReadSet.isTop()) {
-              blockOrPredecessorMaybeReadAnyField = true;
-              break;
-            }
-            if (!knownReadSet.isConcreteFieldSet()) {
-              knownReadSet = new ConcreteMutableFieldSet();
-            }
-            knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
+    putsPerField.forEach(
+        (field, fieldPuts) -> {
+          if (fieldPuts.size() > 1) {
+            return;
           }
-        }
-      }
-
-      boolean changed = false;
-      if (blockOrPredecessorMaybeReadAnyField) {
-        // Record that this block reads all fields.
-        result.put(block, UnknownFieldSet.getInstance());
-        changed = true;
-      } else {
-        if (knownReadSet != readSet) {
-          result.put(block, knownReadSet.asConcreteFieldSet());
-        }
-        if (knownReadSet.size() != oldSize) {
-          assert knownReadSet.size() > oldSize;
-          changed = true;
-        }
-      }
-
-      if (changed) {
-        // Rerun the analysis for all successors because the state of the current block changed.
-        worklist.addAll(block.getSuccessors());
-      }
-    }
-    return result;
-  }
-
-  private boolean verifyFieldSetContainsAllFieldReadsInBlock(
-      KnownFieldSet readSet, BasicBlock block, ProgramMethod context) {
-    for (Instruction instruction : block.getInstructions()) {
-      AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
-      assert !instructionReadSet.isTop();
-      if (instructionReadSet.isBottom()) {
-        continue;
-      }
-      for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
-        assert readSet.contains(field);
-      }
-    }
-    return true;
+          FieldInitializationInfo info = ListUtils.first(fieldPuts);
+          Instruction instruction = info.instruction;
+          if (instruction.isInvokeDirect()) {
+            asInstanceFieldValueAnalysis()
+                .recordInstanceFieldIsInitializedWithInfo(
+                    field, info.instanceFieldInitializationInfo);
+            return;
+          }
+          FieldInstruction fieldPut = instruction.asFieldInstruction();
+          if (checkDominance
+              && !getOrCreateDominatorTree()
+                  .dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+            return;
+          }
+          boolean priorReadsWillReadSameValue =
+              !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
+          if (!priorReadsWillReadSameValue) {
+            if (fieldPut.isInstancePut()) {
+              InstancePut instancePut = fieldPut.asInstancePut();
+              if (fieldReadBeforeWriteAnalysis.isInstanceFieldMaybeReadBeforeInstruction(
+                  instancePut.object(), field, instancePut)) {
+                return;
+              }
+            } else {
+              if (fieldReadBeforeWriteAnalysis.isStaticFieldMaybeReadBeforeInstruction(
+                  field, fieldPut)) {
+                // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
+                // At this point the value read in the field can be only the default static value,
+                // if read prior to the put, or the value put, if read after the put. We still want
+                // to record it because the default static value is typically null/0, so code
+                // present after a null/0 check can take advantage of the optimization.
+                DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
+                asStaticFieldValueAnalysis()
+                    .updateFieldOptimizationInfoWith2Values(
+                        field, fieldPut.value(), valueBeforePut);
+                return;
+              }
+            }
+          }
+          updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
+        });
   }
 
   abstract void updateFieldOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index c4061ec..9ae014f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -50,9 +52,10 @@
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
       OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
       DexClassAndMethod parentConstructor,
       InvokeDirect parentConstructorCall) {
-    super(appView, code, feedback);
+    super(appView, code, feedback, fieldReadBeforeWriteAnalysis);
     this.factory = appView.instanceFieldInitializationInfoFactory();
     this.parentConstructor = parentConstructor;
     this.parentConstructorCall = parentConstructorCall;
@@ -67,10 +70,11 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
       Timing timing) {
     timing.begin("Analyze instance initializer");
     InstanceFieldInitializationInfoCollection result =
-        run(appView, code, classInitializerDefaultsResult, feedback);
+        run(appView, code, classInitializerDefaultsResult, feedback, fieldReadBeforeWriteAnalysis);
     timing.end();
     return result;
   }
@@ -79,7 +83,8 @@
       AppView<?> appView,
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
-      OptimizationFeedback feedback) {
+      OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
     assert code.context().getDefinition().isInstanceInitializer();
@@ -101,6 +106,7 @@
             appView.withLiveness(),
             code,
             feedback,
+            fieldReadBeforeWriteAnalysis,
             parentConstructor,
             parentConstructorCall);
     analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -119,12 +125,12 @@
   }
 
   @Override
-  boolean isSubjectToOptimization(DexEncodedField field) {
-    return !field.isStatic() && field.getHolderType() == context.getHolderType();
+  boolean isSubjectToOptimization(ProgramField field) {
+    return !field.getAccessFlags().isStatic() && field.getHolderType() == context.getHolderType();
   }
 
   @Override
-  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+  boolean isSubjectToOptimizationIgnoringPinning(ProgramField field) {
     throw new Unreachable("Used by static analysis only.");
   }
 
@@ -203,7 +209,7 @@
     if (abstractValue.isSingleValue()) {
       return abstractValue.asSingleValue();
     }
-    DexType fieldType = field.type();
+    DexType fieldType = field.getType();
     if (fieldType.isClassType()) {
       ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
       TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
@@ -229,7 +235,7 @@
 
   private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
       DexEncodedField field, InstancePut instancePut) {
-    if (field.isFinal()) {
+    if (field.getAccessFlags().isFinal()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 4df6b75..5f8381d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -49,8 +51,11 @@
   private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
 
   private StaticFieldValueAnalysis(
-      AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
-    super(appView, code, feedback);
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis) {
+    super(appView, code, feedback, fieldReadBeforeWriteAnalysis);
     builder = StaticFieldValues.builder(code.context().getHolder());
   }
 
@@ -59,13 +64,15 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
+      FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis,
       Timing timing) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
     assert code.context().getDefinition().isClassInitializer();
     timing.begin("Analyze class initializer");
     StaticFieldValues result =
-        new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
+        new StaticFieldValueAnalysis(
+                appView.withLiveness(), code, feedback, fieldReadBeforeWriteAnalysis)
             .analyze(classInitializerDefaultsResult);
     timing.end();
     return result;
@@ -117,19 +124,22 @@
   }
 
   @Override
-  boolean isSubjectToOptimization(DexEncodedField field) {
-    return field.isStatic()
-        && field.getHolderType() == context.getHolderType()
-        && appView.appInfo().isFieldOnlyWrittenInMethod(field, context.getDefinition());
-  }
-
-  @Override
-  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
-    return field.isStatic()
+  boolean isSubjectToOptimization(ProgramField field) {
+    return field.getAccessFlags().isStatic()
         && field.getHolderType() == context.getHolderType()
         && appView
             .appInfo()
-            .isFieldOnlyWrittenInMethodIgnoringPinning(field, context.getDefinition());
+            .isFieldOnlyWrittenInMethod(field.getDefinition(), context.getDefinition());
+  }
+
+  @Override
+  boolean isSubjectToOptimizationIgnoringPinning(ProgramField field) {
+    return field.getAccessFlags().isStatic()
+        && field.getHolderType() == context.getHolderType()
+        && appView
+            .appInfo()
+            .isFieldOnlyWrittenInMethodIgnoringPinning(
+                field.getDefinition(), context.getDefinition());
   }
 
   @Override
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 fc8d1f2..bc02268 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
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 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.readbeforewrite.FieldReadBeforeWriteAnalysis;
 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.fieldvalueanalysis.StaticFieldValues;
@@ -979,10 +980,16 @@
       timing.end();
     }
 
+    FieldReadBeforeWriteAnalysis fieldReadBeforeWriteAnalysis =
+        FieldReadBeforeWriteAnalysis.create(appView, code, method);
     if (fieldAccessAnalysis != null) {
       timing.begin("Analyze field accesses");
       fieldAccessAnalysis.recordFieldAccesses(
-          code, bytecodeMetadataProviderBuilder, feedback, methodProcessor);
+          code,
+          bytecodeMetadataProviderBuilder,
+          feedback,
+          fieldReadBeforeWriteAnalysis,
+          methodProcessor);
       if (classInitializerDefaultsResult != null) {
         fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
       }
@@ -999,11 +1006,21 @@
       if (method.getDefinition().isClassInitializer()) {
         staticFieldValues =
             StaticFieldValueAnalysis.run(
-                appView, code, classInitializerDefaultsResult, feedback, timing);
+                appView,
+                code,
+                classInitializerDefaultsResult,
+                feedback,
+                fieldReadBeforeWriteAnalysis,
+                timing);
       } else {
         instanceFieldInitializationInfos =
             InstanceFieldValueAnalysis.run(
-                appView, code, classInitializerDefaultsResult, feedback, timing);
+                appView,
+                code,
+                classInitializerDefaultsResult,
+                feedback,
+                fieldReadBeforeWriteAnalysis,
+                timing);
       }
     }
     enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index f8e784e..c92342f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -420,9 +420,10 @@
                 }
                 builder.setParent(invokedMethod);
               } else {
-                builder
-                    .markAllFieldsAsRead()
-                    .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+                builder.markAllFieldsAsRead();
+                if (invoke.instructionMayHaveSideEffects(appView, context)) {
+                  builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+                }
                 for (Value inValue : invoke.inValues()) {
                   if (couldBeReceiverValue(inValue, receiver, aliasesThroughAssumeAndCheckCasts)) {
                     builder.setReceiverMayEscapeOutsideConstructorChain();
@@ -453,9 +454,10 @@
           case INVOKE_VIRTUAL:
             {
               InvokeMethod invoke = instruction.asInvokeMethod();
-              builder
-                  .markAllFieldsAsRead()
-                  .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+              builder.markAllFieldsAsRead();
+              if (invoke.instructionMayHaveSideEffects(appView, context)) {
+                builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+              }
               for (Value argument : invoke.arguments()) {
                 if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
                   builder.setReceiverMayEscapeOutsideConstructorChain();
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 39d8035..07a5bb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -954,14 +954,6 @@
     return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
   }
 
-  public boolean mayPropagateArgumentsTo(ProgramMethod method) {
-    DexMethod reference = method.getReference();
-    return method.getDefinition().hasCode()
-        && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
-        && !neverReprocess.contains(reference)
-        && !keepInfo.getMethodInfo(method).isPinned(options());
-  }
-
   public boolean mayPropagateValueFor(
       AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
     assert checkIfObsolete();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 2cdeffd..bb382f8 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.fieldaccess.readbeforewrite.FieldReadBeforeWriteAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
@@ -96,7 +97,11 @@
         method -> {
           IRCode code = method.buildIR(appView);
           fieldAccessAnalysis.recordFieldAccesses(
-              code, BytecodeMetadataProvider.builder(), feedback, new PrimaryMethodProcessorMock());
+              code,
+              BytecodeMetadataProvider.builder(),
+              feedback,
+              FieldReadBeforeWriteAnalysis.trivial(),
+              new PrimaryMethodProcessorMock());
         });
 
     int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java
new file mode 100644
index 0000000..972cc79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullInstanceFieldPropagationTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonNullInstanceFieldPropagationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              assertEquals(1, inspector.allClasses().size());
+              assertEquals(1, mainClassSubject.allMethods().size());
+              assertEquals(0, mainClassSubject.allFields().size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Main p = new Main();
+      p.mThreadChecker.assertOnValidThread();
+    }
+
+    public final ThreadChecker mThreadChecker = new ThreadChecker();
+  }
+
+  static class ThreadChecker {
+    public void assertOnValidThread() {
+      if (System.currentTimeMillis() == 0) {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 92404f0..8078ed6 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -101,6 +101,7 @@
         "  void <init>();",
         "}",
         "-neverclassinline class *",
+        "-nohorizontalclassmerging class adaptresourcefilenames.B",
         "-nohorizontalclassmerging class adaptresourcefilenames.pkg.C",
         "-nohorizontalclassmerging class adaptresourcefilenames.pkg.innerpkg.D");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java b/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
index 31d557e..babd7a7 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepClassMembersFieldTest.java
@@ -59,7 +59,10 @@
     Bar value = new Bar();
 
     public static void main(String[] args) {
-      new Foo();
+      Foo foo = new Foo();
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(foo);
+      }
     }
   }
 }