Eliminate dead field accesses and their dominated code

Fixes: b/330833514
Change-Id: Iad96367c89d80b84f1a0a5748bed3b423a166662
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 058be03..b73ba99 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -194,7 +194,7 @@
 
     // Find all the code objects that need reprocessing.
     new ArgumentPropagatorMethodReprocessingEnqueuer(appView, reprocessingCriteriaCollection)
-        .enqueueMethodForReprocessing(
+        .enqueueAndPrepareMethodsForReprocessing(
             graphLens, postMethodProcessorBuilder, executorService, timing);
     reprocessingCriteriaCollection = null;
 
@@ -245,7 +245,7 @@
         .propagateOptimizationInfo(executorService, timing);
     // TODO(b/296030319): Also publish the computed optimization information for fields.
     new ArgumentPropagatorOptimizationInfoPopulator(
-            appView, converter, methodStates, postMethodProcessorBuilder)
+            appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
         .populateOptimizationInfo(executorService, timing);
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index b857d7d..89fc2b0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -14,17 +15,31 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirConstant;
 import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -45,7 +60,7 @@
    * Called indirectly from {@link IRConverter} to add all methods that require reprocessing to
    * {@param postMethodProcessorBuilder}.
    */
-  public void enqueueMethodForReprocessing(
+  public void enqueueAndPrepareMethodsForReprocessing(
       ArgumentPropagatorGraphLens graphLens,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       ExecutorService executorService,
@@ -69,6 +84,10 @@
     }
     timing.end();
 
+    timing.begin("Eliminate dead field accesses");
+    eliminateDeadFieldAccesses(executorService);
+    timing.end();
+
     timing.end();
   }
 
@@ -144,6 +163,77 @@
             postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
   }
 
+  private void eliminateDeadFieldAccesses(ExecutorService executorService)
+      throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          clazz.forEachProgramMethodMatching(
+              m -> m.hasCode() && !m.getCode().isSharedCodeObject(),
+              this::eliminateDeadFieldAccesses);
+        },
+        appView.options().getThreadingModule(),
+        executorService);
+  }
+
+  private void eliminateDeadFieldAccesses(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
+    if (!code.isLirCode()) {
+      assert appView.isCfByteCodePassThrough(method.getDefinition());
+      return;
+    }
+    LirCode<Integer> lirCode = code.asLirCode();
+    LirCode<Integer> rewrittenLirCode = eliminateDeadFieldAccesses(method, lirCode);
+    if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
+      method.setCode(rewrittenLirCode, appView);
+    }
+  }
+
+  private LirCode<Integer> eliminateDeadFieldAccesses(
+      ProgramMethod method, LirCode<Integer> lirCode) {
+    if (ArrayUtils.none(
+        lirCode.getConstantPool(), constant -> isDeadFieldAccess(constant, method))) {
+      return lirCode;
+    }
+    IRCode irCode = lirCode.buildIR(method, appView);
+    AffectedValues affectedValues = new AffectedValues();
+    BasicBlockIterator blocks = irCode.listIterator();
+    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      if (blocksToRemove.contains(block)) {
+        continue;
+      }
+      InstructionListIterator instructionIterator = block.listIterator(irCode);
+      while (instructionIterator.hasNext()) {
+        FieldInstruction fieldInstruction = instructionIterator.next().asFieldInstruction();
+        if (fieldInstruction == null || !isDeadFieldAccess(fieldInstruction.getField(), method)) {
+          continue;
+        }
+        instructionIterator.replaceCurrentInstructionWithThrowNull(
+            appView, irCode, blocks, blocksToRemove, affectedValues);
+      }
+    }
+    irCode.removeBlocks(blocksToRemove);
+    affectedValues.narrowingWithAssumeRemoval(appView, irCode);
+    irCode.removeRedundantBlocks();
+    return new IRToLirFinalizer(appView)
+        .finalizeCode(irCode, BytecodeMetadataProvider.empty(), Timing.empty());
+  }
+
+  private boolean isDeadFieldAccess(LirConstant constant, ProgramMethod context) {
+    if (!(constant instanceof DexField)) {
+      return false;
+    }
+    DexField fieldReference = (DexField) constant;
+    return isDeadFieldAccess(fieldReference, context);
+  }
+
+  private boolean isDeadFieldAccess(DexField fieldReference, ProgramMethod context) {
+    ProgramField field = appView.appInfo().resolveField(fieldReference, context).getProgramField();
+    return field != null && field.getOptimizationInfo().isDead();
+  }
+
   static class AffectedMethodUseRegistry
       extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index e4181f2..2b14cf3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 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.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -17,11 +18,11 @@
 import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
@@ -47,6 +48,7 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final PrimaryR8IRConverter converter;
+  private final FieldStateCollection fieldStates;
   private final MethodStateCollectionByReference methodStates;
   private final InternalOptions options;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
@@ -54,10 +56,12 @@
   public ArgumentPropagatorOptimizationInfoPopulator(
       AppView<AppInfoWithLiveness> appView,
       PrimaryR8IRConverter converter,
+      FieldStateCollection fieldStates,
       MethodStateCollectionByReference methodStates,
       PostMethodProcessor.Builder postMethodProcessorBuilder) {
     this.appView = appView;
     this.converter = converter;
+    this.fieldStates = fieldStates;
     this.methodStates = methodStates;
     this.options = appView.options();
     this.postMethodProcessorBuilder = postMethodProcessorBuilder;
@@ -94,11 +98,21 @@
 
   private ProgramMethodSet setOptimizationInfo(DexProgramClass clazz) {
     ProgramMethodSet prunedMethods = ProgramMethodSet.create();
+    clazz.forEachProgramField(this::setOptimizationInfo);
     clazz.forEachProgramMethod(method -> setOptimizationInfo(method, prunedMethods));
     clazz.getMethodCollection().removeMethods(prunedMethods.toDefinitionSet());
     return prunedMethods;
   }
 
+  public void setOptimizationInfo(ProgramField field) {
+    ValueState state = fieldStates.remove(field);
+    // TODO(b/296030319): Also publish non-bottom field states.
+    if (state.isBottom()) {
+      getSimpleFeedback()
+          .recordFieldHasAbstractValue(field.getDefinition(), appView, AbstractValue.bottom());
+    }
+  }
+
   public void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
     setOptimizationInfo(method, prunedMethods, methodStates.remove(method));
   }
@@ -184,7 +198,7 @@
     if (optimizationInfo.returnsArgument()) {
       ValueState returnedArgumentState =
           monomorphicMethodState.getParameterState(optimizationInfo.getReturnedArgument());
-      OptimizationFeedback.getSimple()
+      getSimpleFeedback()
           .methodReturnsAbstractValue(
               method.getDefinition(), appView, returnedArgumentState.getAbstractValue(appView));
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index f325f4a..567a559 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -27,7 +27,7 @@
       StateCloner cloner,
       Action onChangedAction) {
     if (state.isBottom()) {
-      assert state == bottomPrimitiveTypeParameter();
+      assert state == bottomPrimitiveTypeState();
       return this;
     }
     if (state.isUnknown()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 46a26d7..d973ed8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -40,18 +40,6 @@
   }
 
   @Override
-  public ValueState clearInFlow() {
-    if (hasInFlow()) {
-      if (nullability.isBottom()) {
-        return bottomArrayTypeParameter();
-      }
-      internalClearInFlow();
-    }
-    assert !isEffectivelyBottom();
-    return this;
-  }
-
-  @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     if (getNullability().isDefinitelyNull()) {
       return appView.abstractValueFactory().createUncheckedNullValue();
@@ -65,6 +53,11 @@
   }
 
   @Override
+  public BottomValueState getCorrespondingBottom() {
+    return bottomArrayTypeState();
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.ARRAY;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index a38f3bf..4653731 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -46,19 +46,6 @@
   }
 
   @Override
-  public ValueState clearInFlow() {
-    if (hasInFlow()) {
-      if (abstractValue.isBottom()) {
-        assert dynamicType.isBottom();
-        return bottomClassTypeParameter();
-      }
-      internalClearInFlow();
-    }
-    assert !isEffectivelyBottom();
-    return this;
-  }
-
-  @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     if (getNullability().isDefinitelyNull()) {
       assert abstractValue.isNull() || abstractValue.isUnknown();
@@ -68,6 +55,11 @@
   }
 
   @Override
+  public BottomValueState getCorrespondingBottom() {
+    return bottomClassTypeState();
+  }
+
+  @Override
   public DynamicType getDynamicType() {
     return dynamicType;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index fbd733b..816e303 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -44,18 +44,6 @@
   }
 
   @Override
-  public ValueState clearInFlow() {
-    if (hasInFlow()) {
-      if (abstractValue.isBottom()) {
-        return bottomPrimitiveTypeParameter();
-      }
-      internalClearInFlow();
-    }
-    assert !isEffectivelyBottom();
-    return this;
-  }
-
-  @Override
   public ConcretePrimitiveTypeValueState mutableCopy() {
     return new ConcretePrimitiveTypeValueState(abstractValue, copyInFlow());
   }
@@ -110,6 +98,11 @@
   }
 
   @Override
+  public BottomValueState getCorrespondingBottom() {
+    return bottomPrimitiveTypeState();
+  }
+
+  @Override
   public ConcreteParameterStateKind getKind() {
     return ConcreteParameterStateKind.PRIMITIVE;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index 186dae0..6169d4a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -31,20 +31,13 @@
   }
 
   @Override
-  public ValueState clearInFlow() {
-    if (hasInFlow()) {
-      if (dynamicType.isBottom()) {
-        return bottomReceiverParameter();
-      }
-      internalClearInFlow();
-    }
-    assert !isEffectivelyBottom();
-    return this;
+  public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
+    return AbstractValue.unknown();
   }
 
   @Override
-  public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
-    return AbstractValue.unknown();
+  public BottomValueState getCorrespondingBottom() {
+    return bottomReceiverParameter();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index 6fd5ce4..e32324a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -51,7 +51,16 @@
     }
   }
 
-  public abstract ValueState clearInFlow();
+  public ValueState clearInFlow() {
+    if (hasInFlow()) {
+      internalClearInFlow();
+      if (isEffectivelyBottom()) {
+        return getCorrespondingBottom();
+      }
+    }
+    assert !isEffectivelyBottom();
+    return this;
+  }
 
   void internalClearInFlow() {
     inFlow = Collections.emptySet();
@@ -74,6 +83,8 @@
     return inFlow;
   }
 
+  public abstract BottomValueState getCorrespondingBottom();
+
   public abstract ConcreteParameterStateKind getKind();
 
   public abstract boolean isEffectivelyBottom();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
index d323112..544c365 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
@@ -88,7 +88,20 @@
     return fieldState != null ? fieldState : ValueState.bottom(field);
   }
 
-  public void set(ProgramField field, NonEmptyValueState state) {
-    fieldStates.put(field, state);
+  public ValueState remove(ProgramField field) {
+    ValueState removed = fieldStates.remove(field);
+    return removed != null ? removed : ValueState.bottom(field);
+  }
+
+  public ValueState set(ProgramField field, ValueState state) {
+    if (state.isNonEmpty()) {
+      return set(field, state.asNonEmpty());
+    }
+    return remove(field);
+  }
+
+  public ValueState set(ProgramField field, NonEmptyValueState state) {
+    NonEmptyValueState previous = fieldStates.put(field, state);
+    return previous != null ? previous : ValueState.bottom(field);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index 7e91edf..96adb2c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -21,28 +21,28 @@
   public static BottomValueState bottom(DexField field) {
     DexType fieldType = field.getType();
     if (fieldType.isArrayType()) {
-      return bottomArrayTypeParameter();
+      return bottomArrayTypeState();
     } else if (fieldType.isClassType()) {
-      return bottomClassTypeParameter();
+      return bottomClassTypeState();
     } else {
       assert fieldType.isPrimitiveType();
-      return bottomPrimitiveTypeParameter();
+      return bottomPrimitiveTypeState();
     }
   }
 
-  public static BottomValueState bottomArrayTypeParameter() {
+  public static BottomArrayTypeValueState bottomArrayTypeState() {
     return BottomArrayTypeValueState.get();
   }
 
-  public static BottomValueState bottomClassTypeParameter() {
+  public static BottomClassTypeValueState bottomClassTypeState() {
     return BottomClassTypeValueState.get();
   }
 
-  public static BottomValueState bottomPrimitiveTypeParameter() {
+  public static BottomPrimitiveTypeValueState bottomPrimitiveTypeState() {
     return BottomPrimitiveTypeValueState.get();
   }
 
-  public static BottomValueState bottomReceiverParameter() {
+  public static BottomReceiverValueState bottomReceiverParameter() {
     return BottomReceiverValueState.get();
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 949bc53..68381bf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -5,18 +5,32 @@
 
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.google.common.collect.Lists;
 import java.util.ArrayDeque;
@@ -34,10 +48,15 @@
 public class DefaultFieldValueJoiner {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final FieldStateCollection fieldStates;
   private final List<FlowGraph> flowGraphs;
 
-  public DefaultFieldValueJoiner(AppView<AppInfoWithLiveness> appView, List<FlowGraph> flowGraphs) {
+  public DefaultFieldValueJoiner(
+      AppView<AppInfoWithLiveness> appView,
+      FieldStateCollection fieldStates,
+      List<FlowGraph> flowGraphs) {
     this.appView = appView;
+    this.fieldStates = fieldStates;
     this.flowGraphs = flowGraphs;
   }
 
@@ -53,7 +72,7 @@
     Map<DexProgramClass, List<ProgramField>> nonFinalInstanceFields =
         removeFieldsNotSubjectToInitializerAnalysis(fieldsOfInterest);
     ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
-    analyzeInstanceInitializers(fieldsOfInterest, fieldsWithLiveDefaultValue::add, executorService);
+    analyzeInitializers(fieldsOfInterest, fieldsWithLiveDefaultValue::add, executorService);
 
     // For non-final fields where writes in instance initializers may have been subject to
     // constructor inlining, we find all new-instance instructions (including subtype allocations)
@@ -65,13 +84,22 @@
 
   private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
-    for (FlowGraph flowGraph : flowGraphs) {
-      // TODO(b/296030319): We only need to include the fields where including the default value
-      //  would make a difference. Then we can assert below in updateFlowGraphs() that adding the
-      //  default value changes the field state.
-      flowGraph.forEachFieldNode(
-          node -> {
-            ProgramField field = node.getField();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramField(
+          field -> {
+            // We only need to include the fields where including the default value would make a
+            // difference. Then we can assert below in updateFlowGraphs() that adding the default
+            // value
+            // changes the field state.
+            // TODO(b/296030319): Implement this for primitive fields.
+            ValueState state = fieldStates.get(field);
+            if (state.isUnknown()) {
+              return;
+            }
+            if (state.isReferenceState()
+                && state.asReferenceState().getNullability().isNullable()) {
+              return;
+            }
             fieldsOfInterest
                 .computeIfAbsent(field.getHolder(), ignoreKey(ArrayList::new))
                 .add(field);
@@ -108,7 +136,7 @@
     return nonFinalInstanceFields;
   }
 
-  private void analyzeInstanceInitializers(
+  private void analyzeInitializers(
       Map<DexProgramClass, List<ProgramField>> fieldsOfInterest,
       Consumer<ProgramField> concurrentLiveDefaultValueConsumer,
       ExecutorService executorService)
@@ -201,9 +229,10 @@
               flowGraph.forEachFieldNode(
                   node -> {
                     ProgramField field = node.getField();
-                    if (fieldsWithLiveDefaultValue.contains(field)) {
-                      node.addDefaultValue(
+                    if (fieldsWithLiveDefaultValue.remove(field)) {
+                      node.addState(
                           appView,
+                          getDefaultValueState(field),
                           () -> {
                             if (node.isUnknown()) {
                               node.clearPredecessors();
@@ -217,7 +246,48 @@
             pair -> !pair.getSecond().isEmpty(),
             appView.options().getThreadingModule(),
             executorService);
+    // Unseen fields are not added to any flow graphs, since they are not needed for flow
+    // propagation. Update these fields directly in the field state collection.
+    for (ProgramField field : fieldsWithLiveDefaultValue) {
+      fieldStates.addTemporaryFieldState(
+          appView, field, () -> getDefaultValueState(field), Timing.empty());
+    }
     return MapUtils.newIdentityHashMap(
         builder -> worklists.forEach(pair -> builder.put(pair.getFirst(), pair.getSecond())));
   }
+
+  private ConcreteValueState getDefaultValueState(ProgramField field) {
+    AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+    AbstractValue defaultValue;
+    if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
+      defaultValue = field.getDefinition().getStaticValue().toAbstractValue(abstractValueFactory);
+    } else if (field.getType().isPrimitiveType()) {
+      defaultValue = abstractValueFactory.createZeroValue();
+    } else {
+      defaultValue = abstractValueFactory.createUncheckedNullValue();
+    }
+    NonEmptyValueState fieldStateToAdd;
+    if (field.getType().isArrayType()) {
+      Nullability defaultNullability = Nullability.definitelyNull();
+      fieldStateToAdd = ConcreteArrayTypeValueState.create(defaultNullability);
+    } else if (field.getType().isClassType()) {
+      assert defaultValue.isNull()
+          || defaultValue.isSingleStringValue()
+          || defaultValue.isSingleDexItemBasedStringValue();
+      DynamicType dynamicType =
+          defaultValue.isNull()
+              ? DynamicType.definitelyNull()
+              : DynamicType.createExact(
+                  TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+      fieldStateToAdd = ConcreteClassTypeValueState.create(defaultValue, dynamicType);
+    } else {
+      assert field.getType().isPrimitiveType();
+      fieldStateToAdd = ConcretePrimitiveTypeValueState.create(defaultValue);
+    }
+    // We should always be able to map static field values to an unknown abstract value.
+    if (fieldStateToAdd.isUnknown()) {
+      throw new Unreachable();
+    }
+    return fieldStateToAdd.asConcrete();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
index a998a15..00d36e6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -98,9 +98,13 @@
       }
     }
 
-    if (!node.getState().isUnknown()) {
-      assert node.getState() == concreteFieldState;
-      node.setState(concreteFieldState.clearInFlow());
+    ValueState concreteFieldStateOrBottom = concreteFieldState.clearInFlow();
+    if (concreteFieldStateOrBottom.isBottom()) {
+      fieldStates.remove(field);
+      if (!node.getState().isUnknown()) {
+        assert node.getState() == concreteFieldState;
+        node.setState(concreteFieldStateOrBottom);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 952b3a2..3cd35c2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayDeque;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
@@ -57,8 +59,9 @@
     // perform this analysis after having computed the initial fixpoint(s). The hypothesis is that
     // many fields will have reached the unknown state after the initial fixpoint, meaning there is
     // fewer fields to analyze.
+    updateFieldStates(fieldStates, flowGraphs);
     Map<FlowGraph, Deque<FlowGraphNode>> worklists =
-        includeDefaultValuesInFieldStates(flowGraphs, executorService);
+        includeDefaultValuesInFieldStates(fieldStates, flowGraphs, executorService);
 
     // Since the inclusion of default values changes the flow graphs, we need to repeat the
     // fixpoint.
@@ -68,6 +71,9 @@
     // of these method states have effectively become unknown, we replace them by the canonicalized
     // unknown method state.
     postProcessMethodStates(executorService);
+
+    // Copy the result of the flow graph propagation back to the field state collection.
+    updateFieldStates(fieldStates, flowGraphs);
   }
 
   private List<FlowGraph> computeStronglyConnectedFlowGraphs() {
@@ -83,8 +89,9 @@
   }
 
   private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
-      List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException {
-    DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, flowGraphs);
+      FieldStateCollection fieldStates, List<FlowGraph> flowGraphs, ExecutorService executorService)
+      throws ExecutionException {
+    DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, fieldStates, flowGraphs);
     return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
   }
 
@@ -199,4 +206,20 @@
       methodStates.set(method, MethodState.unknown());
     }
   }
+
+  private void updateFieldStates(
+      FieldStateCollection fieldStates, Collection<FlowGraph> flowGraphs) {
+    for (FlowGraph flowGraph : flowGraphs) {
+      flowGraph.forEachFieldNode(
+          node -> {
+            ProgramField field = node.getField();
+            ValueState state = node.getState();
+            ValueState previousState = fieldStates.set(field, state);
+            assert state.isUnknown()
+                    || state == previousState
+                    || (state.isConcrete() && previousState.isBottom())
+                : "Expected current state to be >= previous state";
+          });
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 6b63d9c..1aca973 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -93,7 +93,7 @@
     inFlowPropagator.run(executorService);
 
     ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
-        new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null);
+        new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null, null);
     Set<ComposableCallGraphNode> optimizedComposableFunctions = Sets.newIdentityHashSet();
     wave.forEach(
         node ->
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
index 2952b30..c778df3 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -88,6 +88,10 @@
     return remove(field.getReference());
   }
 
+  public boolean remove(ProgramField field) {
+    return remove(field.getReference());
+  }
+
   public boolean removeIf(Predicate<? super ProgramField> predicate) {
     return backing.values().removeIf(predicate);
   }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
new file mode 100644
index 0000000..d484c87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 BottomFieldStatePropagationTest 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)
+        .enableConstantArgumentAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Builder builder = alwaysFalse() ? new Builder(args.length) : null;
+      test(builder);
+    }
+
+    static boolean alwaysFalse() {
+      return false;
+    }
+
+    @KeepConstantArguments
+    @NeverInline
+    static void test(Builder builder) {
+      // Argument propagation should prove that the field access is unreachable due to the field
+      // value being bottom.
+      unreachable(builder.f);
+    }
+
+    @KeepConstantArguments
+    @NeverInline
+    static void unreachable(int i) {
+      System.out.println(i);
+    }
+  }
+
+  static class Builder {
+
+    int f;
+
+    Builder(int f) {
+      this.f = f;
+    }
+  }
+}