Reland "Move Array length to ObjectState"

This reverts commit 17687b2579af5bc68199a815183a66efdc31a6d6.

Bug: 202365761
Change-Id: Iff5816b55a38b1ab0c824cbc9e024b2f05a42edd
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 30060d9..0303ba7 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
@@ -11,7 +11,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
@@ -25,6 +24,7 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.analysis.value.objectstate.EnumValuesObjectState;
 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.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -35,8 +35,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.IdentityHashMap;
@@ -240,7 +238,7 @@
       // This implicitely answers null if the value could not get computed.
       if (valuesValue.isSingleFieldValue()) {
         SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
-        if (fieldValue.getState().isEnumValuesObjectState()) {
+        if (fieldValue.getObjectState().isEnumValuesObjectState()) {
           return fieldValue;
         }
       }
@@ -439,62 +437,12 @@
   }
 
   private ObjectState computeObjectState(Value value) {
-    assert !value.hasAliasedValue();
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
-      return ObjectState.empty();
-    }
-
-    NewInstance newInstance = value.definition.asNewInstance();
-    InvokeDirect uniqueConstructorInvoke =
-        newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
-    if (uniqueConstructorInvoke == null) {
-      return ObjectState.empty();
-    }
-
-    DexClassAndMethod singleTarget = uniqueConstructorInvoke.lookupSingleTarget(appView, context);
-    if (singleTarget == null) {
-      return ObjectState.empty();
-    }
-
-    InstanceFieldInitializationInfoCollection initializationInfos =
-        singleTarget
-            .getDefinition()
-            .getOptimizationInfo()
-            .getInstanceInitializerInfo(uniqueConstructorInvoke)
-            .fieldInitializationInfos();
-    if (initializationInfos.isEmpty()) {
-      return ObjectState.empty();
-    }
-
-    ObjectState.Builder builder = ObjectState.builder();
-    initializationInfos.forEach(
-        appView,
-        (field, initializationInfo) -> {
-          // If the instance field is not written only in the instance initializer, then we can't
-          // conclude that this field will have a constant value.
-          //
-          // We have special handling for library fields that satisfy the property that they are
-          // only written in their corresponding instance initializers. This is needed since we
-          // don't analyze these instance initializers in the Enqueuer, as they are in the library.
-          if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)
-              && !appView.dexItemFactory().enumMembers.isNameOrOrdinalField(field.getReference())) {
-            return;
-          }
-          if (initializationInfo.isArgumentInitializationInfo()) {
-            InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
-                initializationInfo.asArgumentInitializationInfo();
-            Value argument =
-                uniqueConstructorInvoke.getArgument(argumentInitializationInfo.getArgumentIndex());
-            builder.recordFieldHasValue(field, argument.getAbstractValue(appView, context));
-          } else if (initializationInfo.isSingleValue()) {
-            builder.recordFieldHasValue(field, initializationInfo.asSingleValue());
-          }
-        });
-    return builder.build();
+    // TODO(b/204159267): Move this logic into Instruction#getAbstractValue in NewInstance.
+    return ObjectStateAnalysis.computeObjectState(value, appView, context);
   }
 
   private boolean isEnumValuesArray(Value value) {
     SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
-    return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
+    return singleFieldValue != null && singleFieldValue.getObjectState().isEnumValuesObjectState();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index 0b9a6cd..e57052f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -59,17 +59,18 @@
           DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
         if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
           if (value.isSingleFieldValue()
-              && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
+              && value.asSingleFieldValue().getObjectState().isEnumValuesObjectState()) {
             assert valuesCandidateAbstractValue == null
                 || valuesCandidateAbstractValue.equals(value);
             valuesCandidateAbstractValue = value;
             enumObjectStateBuilder.put(
-                staticField.getReference(), value.asSingleFieldValue().getState());
+                staticField.getReference(), value.asSingleFieldValue().getObjectState());
           }
         } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
-          if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
+          if (value.isSingleFieldValue()
+              && !value.asSingleFieldValue().getObjectState().isEmpty()) {
             enumObjectStateBuilder.put(
-                staticField.getReference(), value.asSingleFieldValue().getState());
+                staticField.getReference(), value.asSingleFieldValue().getObjectState());
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index d69ca26..854ea6d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class AbstractValue {
@@ -57,14 +58,32 @@
     return null;
   }
 
-  public boolean isKnownLengthArrayValue() {
+  public boolean hasObjectState() {
     return false;
   }
 
-  public KnownLengthArrayValue asKnownLengthArrayValue() {
+  public ObjectState getObjectState() {
+    throw new UnsupportedOperationException(
+        "Abstract value " + this + " does not have any object state.");
+  }
+
+  public boolean isStatefulObjectValue() {
+    return false;
+  }
+
+  public StatefulObjectValue asStatefulObjectValue() {
     return null;
   }
 
+  public boolean hasKnownArrayLength() {
+    return false;
+  }
+
+  public int getKnownArrayLength() {
+    throw new UnsupportedOperationException(
+        "Abstract value " + this + " does not have a known array length.");
+  }
+
   public boolean isSingleConstValue() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index e1cdb6c..bc1a554 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.objectstate.KnownLengthArrayState;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import java.util.concurrent.ConcurrentHashMap;
@@ -19,15 +20,15 @@
   private ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues = new ConcurrentHashMap<>();
   private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
       new ConcurrentHashMap<>();
-  private ConcurrentHashMap<Integer, KnownLengthArrayValue> knownArrayLengthValues =
+  private ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
       new ConcurrentHashMap<>();
 
   public SingleConstClassValue createSingleConstClassValue(DexType type) {
     return singleConstClassValues.computeIfAbsent(type, SingleConstClassValue::new);
   }
 
-  public AbstractValue createKnownLengthArrayValue(int length) {
-    return knownArrayLengthValues.computeIfAbsent(length, KnownLengthArrayValue::new);
+  public KnownLengthArrayState createKnownLengthArrayState(int length) {
+    return knownArrayLengthStates.computeIfAbsent(length, KnownLengthArrayState::new);
   }
 
   public SingleFieldValue createSingleFieldValue(DexField field, ObjectState state) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/KnownLengthArrayValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/KnownLengthArrayValue.java
deleted file mode 100644
index 9721031..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/KnownLengthArrayValue.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2021, 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.value;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-/** A KnownLengthArrayValue implicitly implies the value is non null. */
-public class KnownLengthArrayValue extends AbstractValue {
-
-  private final int length;
-
-  public KnownLengthArrayValue(int length) {
-    this.length = length;
-  }
-
-  public int getLength() {
-    return length;
-  }
-
-  @Override
-  public boolean isKnownLengthArrayValue() {
-    return true;
-  }
-
-  @Override
-  public KnownLengthArrayValue asKnownLengthArrayValue() {
-    return this;
-  }
-
-  @Override
-  public boolean isNonTrivial() {
-    return true;
-  }
-
-  @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    return this;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    return this == o;
-  }
-
-  @Override
-  public int hashCode() {
-    return System.identityHashCode(this);
-  }
-
-  @Override
-  public String toString() {
-    return "KnownLengthArrayValue(len=" + length + ")";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 71a6dbf..b499b81 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -48,7 +48,11 @@
     return field.lookupOnClass(holder);
   }
 
-  public abstract ObjectState getState();
+  @Override
+  public abstract ObjectState getObjectState();
+
+  @Override
+  public abstract boolean hasObjectState();
 
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(AppView<AppInfoWithLiveness> appView) {
     DexType fieldType = field.type;
@@ -140,6 +144,6 @@
       }
     }
     return factory.createSingleFieldValue(
-        lens.lookupField(field), getState().rewrittenWithLens(appView, lens));
+        lens.lookupField(field), getObjectState().rewrittenWithLens(appView, lens));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
index d14f5a9..4191a9c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
@@ -20,7 +20,22 @@
   }
 
   @Override
-  public ObjectState getState() {
+  public boolean hasKnownArrayLength() {
+    return getObjectState().hasKnownArrayLength();
+  }
+
+  @Override
+  public int getKnownArrayLength() {
+    return getObjectState().getKnownArrayLength();
+  }
+
+  @Override
+  public boolean hasObjectState() {
+    return true;
+  }
+
+  @Override
+  public ObjectState getObjectState() {
     return state;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
index 809b365..3845594 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
@@ -15,11 +15,16 @@
   }
 
   @Override
-  public ObjectState getState() {
+  public ObjectState getObjectState() {
     return ObjectState.empty();
   }
 
   @Override
+  public boolean hasObjectState() {
+    return false;
+  }
+
+  @Override
   public String toString() {
     return "SingleStatelessFieldValue(" + field.toSourceString() + ")";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
new file mode 100644
index 0000000..39174da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2021, 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.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/** A KnownLengthArrayValue implicitly implies the value is non null. */
+public class StatefulObjectValue extends AbstractValue {
+
+  private final ObjectState state;
+
+  StatefulObjectValue(ObjectState state) {
+    assert !state.isEmpty();
+    this.state = state;
+  }
+
+  public static AbstractValue create(ObjectState objectState) {
+    return objectState.isEmpty()
+        ? UnknownValue.getInstance()
+        : new StatefulObjectValue(objectState);
+  }
+
+  @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
+  public boolean isStatefulObjectValue() {
+    return true;
+  }
+
+  @Override
+  public StatefulObjectValue asStatefulObjectValue() {
+    return this;
+  }
+
+  @Override
+  public boolean hasKnownArrayLength() {
+    return getObjectState().hasKnownArrayLength();
+  }
+
+  @Override
+  public int getKnownArrayLength() {
+    return getObjectState().getKnownArrayLength();
+  }
+
+  @Override
+  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+    return create(getObjectState().rewrittenWithLens(appView, lens));
+  }
+
+  @Override
+  public boolean hasObjectState() {
+    return true;
+  }
+
+  @Override
+  public ObjectState getObjectState() {
+    return state;
+  }
+
+  @Override
+  public String toString() {
+    return "StatefulValue";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+    StatefulObjectValue statefulObjectValue = (StatefulObjectValue) o;
+    return state.equals(statefulObjectValue.state);
+  }
+
+  @Override
+  public int hashCode() {
+    return state.hashCode();
+  }
+}
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 397e79f..817b166 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
@@ -45,6 +45,16 @@
   }
 
   @Override
+  public boolean hasKnownArrayLength() {
+    return true;
+  }
+
+  @Override
+  public int getKnownArrayLength() {
+    return state.length;
+  }
+
+  @Override
   public boolean isEnumValuesObjectState() {
     return true;
   }
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
new file mode 100644
index 0000000..a379776
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2021, 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.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.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.BiConsumer;
+
+public class KnownLengthArrayState extends ObjectState {
+
+  private final int length;
+
+  public KnownLengthArrayState(int length) {
+    this.length = length;
+  }
+
+  @Override
+  public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+    return UnknownValue.getInstance();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return false;
+  }
+
+  @Override
+  public boolean hasKnownArrayLength() {
+    return true;
+  }
+
+  @Override
+  public int getKnownArrayLength() {
+    return length;
+  }
+
+  @Override
+  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
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 f709c59..b81fb32 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.value.objectstate;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -58,6 +59,15 @@
   @Override
   public abstract int hashCode();
 
+  public boolean hasKnownArrayLength() {
+    return false;
+  }
+
+  public int getKnownArrayLength() {
+    // Override this method if hasKnownArrayLength answers true.
+    throw new Unreachable();
+  }
+
   public boolean isEnumValuesObjectState() {
     return false;
   }
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
new file mode 100644
index 0000000..125acfa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2021, 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.value.objectstate;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ObjectStateAnalysis {
+
+  public static ObjectState computeObjectState(
+      Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    assert !value.hasAliasedValue();
+    if (value.isDefinedByInstructionSatisfying(
+        i -> i.isNewArrayEmpty() || i.isNewArrayFilledData() || i.isInvokeNewArray())) {
+      return computeNewArrayObjectState(value, appView, context);
+    }
+    if (value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+      return computeNewInstanceObjectState(value, appView, context);
+    }
+    return ObjectState.empty();
+  }
+
+  private static ObjectState computeNewArrayObjectState(
+      Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    AbstractValue abstractValue = value.definition.getAbstractValue(appView, context);
+    if (abstractValue.isStatefulObjectValue()) {
+      // TODO(b/204272377): Avoid wrapping and unwrapping the object state.
+      return abstractValue.asStatefulObjectValue().getObjectState();
+    }
+    return ObjectState.empty();
+  }
+
+  private static ObjectState computeNewInstanceObjectState(
+      Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    NewInstance newInstance = value.definition.asNewInstance();
+    InvokeDirect uniqueConstructorInvoke =
+        newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+    if (uniqueConstructorInvoke == null) {
+      return ObjectState.empty();
+    }
+
+    DexClassAndMethod singleTarget = uniqueConstructorInvoke.lookupSingleTarget(appView, context);
+    if (singleTarget == null) {
+      return ObjectState.empty();
+    }
+
+    InstanceFieldInitializationInfoCollection initializationInfos =
+        singleTarget
+            .getDefinition()
+            .getOptimizationInfo()
+            .getInstanceInitializerInfo(uniqueConstructorInvoke)
+            .fieldInitializationInfos();
+    if (initializationInfos.isEmpty()) {
+      return ObjectState.empty();
+    }
+
+    ObjectState.Builder builder = ObjectState.builder();
+    initializationInfos.forEach(
+        appView,
+        (field, initializationInfo) -> {
+          // If the instance field is not written only in the instance initializer, then we can't
+          // conclude that this field will have a constant value.
+          //
+          // We have special handling for library fields that satisfy the property that they are
+          // only written in their corresponding instance initializers. This is needed since we
+          // don't analyze these instance initializers in the Enqueuer, as they are in the library.
+          if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)
+              && !appView.dexItemFactory().enumMembers.isNameOrOrdinalField(field.getReference())) {
+            return;
+          }
+          if (initializationInfo.isArgumentInitializationInfo()) {
+            InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+                initializationInfo.asArgumentInitializationInfo();
+            Value argument =
+                uniqueConstructorInvoke.getArgument(argumentInitializationInfo.getArgumentIndex());
+            builder.recordFieldHasValue(field, argument.getAbstractValue(appView, context));
+          } else if (initializationInfo.isSingleValue()) {
+            builder.recordFieldHasValue(field, initializationInfo.asSingleValue());
+          }
+        });
+    return builder.build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 9aff428..e1bb5bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -276,10 +276,10 @@
       return true;
     }
     AbstractValue abstractValue = array().getAliasedValue().getAbstractValue(appView, context);
-    if (!abstractValue.isKnownLengthArrayValue()) {
+    if (!abstractValue.hasKnownArrayLength()) {
       return true;
     }
-    int newArraySize = abstractValue.asKnownLengthArrayValue().getLength();
+    int newArraySize = abstractValue.getKnownArrayLength();
     int index = index().getConstInstruction().asConstNumber().getIntValue();
     return newArraySize <= 0 || index < 0 || newArraySize <= index;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index cad5e1d..636468c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -15,6 +15,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 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.StatefulObjectValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -140,6 +143,17 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    if (!instructionMayHaveSideEffects(appView, context)) {
+      int size = inValues.size();
+      return StatefulObjectValue.create(
+          appView.abstractValueFactory().createKnownLengthArrayState(size));
+    }
+    return UnknownValue.getInstance();
+  }
+
+  @Override
   public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
     if (baseType.isPrimitiveType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 3ddbcbd..3be3d3d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -14,6 +14,7 @@
 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.StatefulObjectValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -90,9 +91,10 @@
       AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context) && size().getType().isInt()) {
       assert !instructionInstanceCanThrow();
-      return appView
-          .abstractValueFactory()
-          .createKnownLengthArrayValue(size().definition.asConstNumber().getIntValue());
+      return StatefulObjectValue.create(
+          appView
+              .abstractValueFactory()
+              .createKnownLengthArrayState(size().definition.asConstNumber().getIntValue()));
     }
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ce235d4..fb04f03 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.StatefulObjectValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -127,7 +128,8 @@
       AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context) && size <= Integer.MAX_VALUE) {
       assert !instructionInstanceCanThrow();
-      return appView.abstractValueFactory().createKnownLengthArrayValue((int) size);
+      return StatefulObjectValue.create(
+          appView.abstractValueFactory().createKnownLengthArrayState((int) size));
     }
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index bcd27e6..dae6f04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -3451,7 +3451,7 @@
       }
 
       AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
-      if (!abstractValue.isKnownLengthArrayValue() && !array.isNeverNull()) {
+      if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
         continue;
       }
       Instruction arrayDefinition = array.getDefinition();
@@ -3468,15 +3468,13 @@
           continue;
         }
         iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
-      } else if (abstractValue.isKnownLengthArrayValue()) {
-        iterator.replaceCurrentInstructionWithConstInt(
-            code, abstractValue.asKnownLengthArrayValue().getLength());
+      } else if (abstractValue.hasKnownArrayLength()) {
+        iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
       } else {
         continue;
       }
 
       phiUsers.forEach(Phi::removeTrivialPhi);
-      // TODO(139489070): static-get of constant array
     }
     assert code.isConsistentSSA();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 9e8d052..206c455 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -401,12 +401,8 @@
       if (abstractValue.isUnknown() && !definition.isStatic()) {
         AbstractValue abstractReceiverValue =
             current.asInstanceGet().object().getAbstractValue(appView, code.context());
-        if (abstractReceiverValue.isSingleFieldValue()) {
-          abstractValue =
-              abstractReceiverValue
-                  .asSingleFieldValue()
-                  .getState()
-                  .getAbstractFieldValue(definition);
+        if (abstractReceiverValue.hasObjectState()) {
+          abstractValue = abstractReceiverValue.getObjectState().getAbstractFieldValue(definition);
         }
       }
     } else if (definition.isStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index e42266b..290f25b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -421,7 +421,7 @@
       SingleFieldValue singleFieldValue =
           field.getDefinition().getOptimizationInfo().getAbstractValue().asSingleFieldValue();
       if (singleFieldValue != null) {
-        applyObjectState(staticGet.outValue(), singleFieldValue.getState());
+        applyObjectState(staticGet.outValue(), singleFieldValue.getObjectState());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 487d677..82322c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -191,7 +191,7 @@
     AbstractValue abstractValue = optimizationInfo.getAbstractValue();
     objectState =
         abstractValue.isSingleFieldValue()
-            ? abstractValue.asSingleFieldValue().getState()
+            ? abstractValue.asSingleFieldValue().getObjectState()
             : ObjectState.empty();
     return EligibilityStatus.ELIGIBLE;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index f51dc69..ff79200 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -356,7 +356,7 @@
     if (encodedField == null) {
       return null;
     }
-    return abstractValue.asSingleFieldValue().getState().getAbstractFieldValue(encodedField);
+    return abstractValue.asSingleFieldValue().getObjectState().getAbstractFieldValue(encodedField);
   }
 
   private static final class EnumSwitchInfo {
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 15819c7..b5ced8c 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
@@ -65,6 +65,9 @@
 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.StatefulObjectValue;
+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.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -209,6 +212,13 @@
             checkCastAndInstanceOfMethodSpecialization.addCandidateForOptimization(
                 context, abstractReturnValue, methodProcessor);
           }
+        } else if (returnValue.getType().isReferenceType()) {
+          // TODO(b/204159267): Move this logic into Instruction#getAbstractValue in NewInstance.
+          ObjectState objectState =
+              ObjectStateAnalysis.computeObjectState(aliasedValue, appView, context);
+          // TODO(b/204272377): Avoid wrapping and unwrapping the object state.
+          feedback.methodReturnsAbstractValue(
+              method, appView, StatefulObjectValue.create(objectState));
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
index c9cc17b..b171525 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
@@ -90,7 +90,7 @@
       } else {
         dead();
       }
-      if (Config.alwaysEmpty.length == 0) {
+      if (Config.alwaysEmpty.clone().length == 0) {
         System.out.print(" world");
       }
       for (String str : Config.alwaysNonEmpty) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StatePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StatePropagationTest.java
new file mode 100644
index 0000000..f4b90ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StatePropagationTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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 org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.ir.optimize.membervaluepropagation.StatePropagationTest.TestClass.Data;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StatePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StatePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StatePropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("1", "1.0", "2", "2.0");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject data = inspector.clazz(Data.class);
+    assertEquals(0, data.allInstanceFields().size());
+  }
+
+  static class TestClass {
+
+    @NeverClassInline
+    static class Data {
+      final int i;
+      final float j;
+
+      private Data(int i, float j) {
+        this.i = i;
+        this.j = j;
+      }
+    }
+
+    @NeverInline
+    public static Data getData1() {
+      return new Data(1, 1.0f);
+    }
+
+    @NeverInline
+    public static Data getData2() {
+      return new Data(2, 2.0f);
+    }
+
+    public static void main(String[] args) {
+      Data data1 = getData1();
+      Data data2 = getData2();
+      System.out.println(data1.i);
+      System.out.println(data1.j);
+      System.out.println(data2.i);
+      System.out.println(data2.j);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentSubtypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentSubtypeTest.java
index c460e16..97b57f4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentSubtypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentSubtypeTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), isAbsent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
index 5a7be26..c38c66f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
@@ -68,7 +68,7 @@
         .addProgramClasses(Main.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::inspect);
+        .inspect(i -> inspect(i, true));
   }
 
   @Test public void r8() throws Exception {
@@ -80,10 +80,10 @@
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::inspect);
+        .inspect(i -> inspect(i, false));
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean d8) {
     ClassSubject mainClass = inspector.clazz(Main.class);
     assertTrue(mainClass.isPresent());
 
@@ -104,10 +104,10 @@
 
     // TODO(139489070): these should be rewritten and result in 0 array-length bytecodes
     MethodSubject staticConstants = mainClass.uniqueMethodWithName("staticConstants");
-    assertArrayLengthCallCount(staticConstants, 3);
+    assertArrayLengthCallCount(staticConstants, (d8 || debugMode) ? 3 : 0);
 
     MethodSubject staticNonConstants = mainClass.uniqueMethodWithName("staticNonConstants");
-    assertArrayLengthCallCount(staticNonConstants, 2);
+    assertArrayLengthCallCount(staticNonConstants, (d8 || debugMode) ? 2 : 0);
   }
 
   private static void assertArrayLengthCallCount(MethodSubject subject, int expected) {
@@ -200,7 +200,7 @@
     }
 
     private static String[] mutable = { "one" };
-    private static final String[] runtimeInit = { System.lineSeparator() };
+    private static final String[] runtimeInit = {"two"};
 
     @NeverInline
     private static void staticNonConstants() {