Extend field/parameter propagation to include object state

Bug: b/296030319
Change-Id: I6a609972a0017fa220b7cff64239a5aeabee9dce
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index 1f2b960..d16e2b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.value.objectstate;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -29,7 +28,7 @@
   }
 
   @Override
-  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+  public AbstractValue getAbstractFieldValue(DexField field) {
     return UnknownValue.getInstance();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 28652ca..25a9566 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.value.objectstate;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -40,7 +39,7 @@
   public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {}
 
   @Override
-  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+  public AbstractValue getAbstractFieldValue(DexField field) {
     return UnknownValue.getInstance();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
index f05e962..a773fd2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.value.objectstate;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -27,7 +26,7 @@
   }
 
   @Override
-  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+  public AbstractValue getAbstractFieldValue(DexField field) {
     return UnknownValue.getInstance();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 3b73b9d..7acebdd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.value.objectstate;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -32,8 +31,8 @@
   }
 
   @Override
-  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
-    return state.getOrDefault(field.getReference(), UnknownValue.getInstance());
+  public AbstractValue getAbstractFieldValue(DexField field) {
+    return state.getOrDefault(field, UnknownValue.getInstance());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 3b02fc4..271cd94 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -47,7 +47,11 @@
     return predicate.test(singleValue);
   }
 
-  public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
+  public final AbstractValue getAbstractFieldValue(DexEncodedField field) {
+    return getAbstractFieldValue(field.getReference());
+  }
+
+  public abstract AbstractValue getAbstractFieldValue(DexField field);
 
   public abstract boolean isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
index dea5836..f53f615 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
@@ -20,13 +20,13 @@
 
   public static ObjectState computeObjectState(
       Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
-    assert !value.hasAliasedValue();
-    if (value.isDefinedByInstructionSatisfying(
+    Value valueRoot = value.getAliasedValue();
+    if (valueRoot.isDefinedByInstructionSatisfying(
         i -> i.isNewArrayEmpty() || i.isNewArrayFilledData() || i.isNewArrayFilled())) {
-      return computeNewArrayObjectState(value, appView, context);
+      return computeNewArrayObjectState(valueRoot, appView, context);
     }
-    if (value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
-      return computeNewInstanceObjectState(value, appView, context);
+    if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+      return computeNewInstanceObjectState(valueRoot, appView, context);
     }
     return ObjectState.empty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
index 6b7f62c..c1dee99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -19,6 +19,8 @@
 
   boolean isInstanceGet();
 
+  InstanceGet asInstanceGet();
+
   boolean isStaticGet();
 
   boolean hasUsedOutValue();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 5ec5473..8df07a0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
+import static com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow.asBaseInFlowOrNull;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -19,6 +21,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectStateAnalysis;
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -45,6 +48,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InstanceFieldReadAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
@@ -66,6 +70,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * Analyzes each {@link IRCode} during the primary optimization to collect information about the
@@ -268,8 +273,10 @@
 
     AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
     if (abstractValue.isUnknown()) {
-      // TODO(b/296030319): Add the current object state to the computed fallback abstract value.
-      abstractValue = getFallbackAbstractValueForField(field);
+      abstractValue =
+          getFallbackAbstractValueForField(
+              field,
+              () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
     }
     if (field.getType().isClassType()) {
       DynamicType dynamicType =
@@ -297,6 +304,14 @@
       if (field == null) {
         return null;
       }
+      if (fieldGet.isInstanceGet()) {
+        Value receiverValue = fieldGet.asInstanceGet().object();
+        BaseInFlow receiverInFlow = asBaseInFlowOrNull(computeInFlow(receiverValue, context));
+        if (receiverInFlow != null
+            && receiverInFlow.equals(widenBaseInFlow(receiverInFlow, context))) {
+          return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+        }
+      }
       return widenBaseInFlow(fieldValueFactory.create(field), context);
     }
     return null;
@@ -325,7 +340,7 @@
     if (inFlow.isUnknownAbstractFunction()) {
       return ValueState.unknown();
     }
-    assert inFlow.isBaseInFlow();
+    assert inFlow.isBaseInFlow() || inFlow.isInstanceFieldReadAbstractFunction();
     return ConcreteValueState.create(staticType, inFlow);
   }
 
@@ -334,7 +349,8 @@
   // will never have their value changed after the <clinit> finishes, so value in a static final
   // field can always be rematerialized by reading the field.
   private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
-    AbstractValue fallbackAbstractValue = getFallbackAbstractValueForField(field);
+    AbstractValue fallbackAbstractValue =
+        getFallbackAbstractValueForField(field, ObjectState::empty);
     if (!fallbackAbstractValue.isUnknown()) {
       AbstractValue abstractValue = fieldState.getAbstractValue(appView);
       if (!abstractValue.isUnknown()) {
@@ -359,11 +375,12 @@
   }
 
   // TODO(b/296030319): Also handle effectively final fields.
-  private AbstractValue getFallbackAbstractValueForField(ProgramField field) {
+  private AbstractValue getFallbackAbstractValueForField(
+      ProgramField field, Supplier<ObjectState> objectStateSupplier) {
     if (field.getAccessFlags().isFinal() && field.getAccessFlags().isStatic()) {
       return appView
           .abstractValueFactory()
-          .createSingleFieldValue(field.getReference(), ObjectState.empty());
+          .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
     }
     return AbstractValue.unknown();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
index 20ec5c7..ef35ee9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
@@ -5,6 +5,10 @@
 
 public interface BaseInFlow extends InFlow {
 
+  static BaseInFlow asBaseInFlowOrNull(InFlow inFlow) {
+    return inFlow != null ? inFlow.asBaseInFlow() : null;
+  }
+
   @Override
   default boolean isBaseInFlow() {
     return true;
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 bf3d1f9..a38f3bf 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
@@ -60,7 +60,7 @@
 
   @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
-    if (getDynamicType().getNullability().isDefinitelyNull()) {
+    if (getNullability().isDefinitelyNull()) {
       assert abstractValue.isNull() || abstractValue.isUnknown();
       return appView.abstractValueFactory().createUncheckedNullValue();
     }
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 5bc8659..6fd5ce4 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
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import java.util.Collections;
@@ -27,6 +29,17 @@
     this.inFlow = inFlow;
   }
 
+  public static NonEmptyValueState create(DexType staticType, AbstractValue abstractValue) {
+    if (staticType.isArrayType()) {
+      return unknown();
+    } else if (staticType.isClassType()) {
+      return ConcreteClassTypeValueState.create(abstractValue, DynamicType.unknown());
+    } else {
+      assert staticType.isPrimitiveType();
+      return ConcretePrimitiveTypeValueState.create(abstractValue);
+    }
+  }
+
   public static ConcreteValueState create(DexType staticType, InFlow inFlow) {
     if (staticType.isArrayType()) {
       return new ConcreteArrayTypeValueState(inFlow);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
index 3cb0550..a305998 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
@@ -31,6 +31,14 @@
     return null;
   }
 
+  default boolean isInstanceFieldReadAbstractFunction() {
+    return false;
+  }
+
+  default InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+    return null;
+  }
+
   default boolean isMethodParameter() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
new file mode 100644
index 0000000..9a8cf97
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
@@ -0,0 +1,59 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class InstanceFieldReadAbstractFunction implements AbstractFunction {
+
+  private final BaseInFlow receiver;
+  private final DexField field;
+
+  public InstanceFieldReadAbstractFunction(BaseInFlow receiver, DexField field) {
+    this.receiver = receiver;
+    this.field = field;
+  }
+
+  // TODO(b/296030319): Instead of returning unknown from here, we should fallback to the state of
+  //  the instance field node in the graph. A prerequisite for this is the ability to express
+  //  multiple inputs to abstract functions.
+  @Override
+  public NonEmptyValueState apply(ConcreteValueState state) {
+    if (!state.isClassState()) {
+      return ValueState.unknown();
+    }
+    ConcreteClassTypeValueState classState = state.asClassState();
+    if (classState.getNullability().isDefinitelyNull()) {
+      // TODO(b/296030319): This should be rare, but we should really return bottom here, since
+      //  reading a field from the the null value throws an exception, meaning no flow should be
+      //  propagated.
+      return ValueState.unknown();
+    }
+    AbstractValue abstractValue = state.getAbstractValue(null);
+    if (!abstractValue.hasObjectState()) {
+      return ValueState.unknown();
+    }
+    AbstractValue fieldValue = abstractValue.getObjectState().getAbstractFieldValue(field);
+    if (fieldValue.isUnknown()) {
+      return ValueState.unknown();
+    }
+    return ConcreteValueState.create(field.getType(), fieldValue);
+  }
+
+  @Override
+  public InFlow getBaseInFlow() {
+    return receiver;
+  }
+
+  @Override
+  public boolean isInstanceFieldReadAbstractFunction() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
index 728cd78..6272631 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
@@ -51,14 +51,12 @@
               MethodSubject printMethodSubject =
                   mainClassSubject.uniqueMethodWithOriginalName("print");
               assertThat(printMethodSubject, isPresent());
-              // TODO(b/296030319): Should be 0.
-              assertEquals(1, printMethodSubject.getProgramMethod().getArity());
+              assertEquals(0, printMethodSubject.getProgramMethod().getArity());
 
               MethodSubject printlnMethodSubject =
                   mainClassSubject.uniqueMethodWithOriginalName("println");
               assertThat(printlnMethodSubject, isPresent());
-              // TODO(b/296030319): Should be 0.
-              assertEquals(1, printlnMethodSubject.getProgramMethod().getArity());
+              assertEquals(0, printlnMethodSubject.getProgramMethod().getArity());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello, world!");