filled-new-array: Add support for InvokeNewArray to Enum Unboxing

Bug: 246971330
Change-Id: Ibeba3cdb397ef807a1087cb71599360066a58f2a
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 30c3b67..27227a0 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
@@ -37,6 +39,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
@@ -210,7 +213,7 @@
     if (value.isPhi()) {
       return null;
     }
-    if (value.definition.isNewArrayEmpty()) {
+    if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
     if (value.definition.isNewInstance()) {
@@ -220,7 +223,7 @@
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.definition.isNewArrayEmpty()) {
+    if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return null;
     }
     AbstractValue valuesValue = computedValues.get(value);
@@ -241,26 +244,47 @@
   }
 
   private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
-    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
-
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
-    if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+    InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
+    assert newArrayEmpty != null || invokeNewArray != null;
+
+    DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
+    if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
       return null;
     }
     if (value.hasDebugUsers() || value.hasPhiUsers()) {
       return null;
     }
-    if (!newArrayEmpty.size().isConstNumber()) {
-      return null;
+
+    int valuesSize;
+    if (newArrayEmpty != null) {
+      if (!newArrayEmpty.size().isConstNumber()) {
+        return null;
+      }
+      valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+    } else {
+      valuesSize = invokeNewArray.inValues().size();
     }
 
-    int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
     if (valuesSize == 0) {
       // No need to compute the state of an empty array.
       return null;
     }
 
     ObjectState[] valuesState = new ObjectState[valuesSize];
+
+    if (invokeNewArray != null) {
+      // Populate array values from filled-new-array values.
+      List<Value> inValues = invokeNewArray.inValues();
+      for (int i = 0; i < valuesSize; ++i) {
+        if (!updateEnumValueState(valuesState, i, inValues.get(i))) {
+          return null;
+        }
+      }
+    }
+
+    // Populate / update array values from aput-object instructions, and find the static-put
+    // instruction.
     DexEncodedField valuesField = null;
     for (Instruction user : value.aliasedUsers()) {
       switch (user.opcode()) {
@@ -276,18 +300,9 @@
           if (index < 0 || index >= valuesSize) {
             return null;
           }
-          ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
-          if (objectState == null || objectState.isEmpty()) {
-            // We need the state of all fields for the analysis to be valuable.
+          if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
             return null;
           }
-          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
-            return null;
-          }
-          if (valuesState[index] != null) {
-            return null;
-          }
-          valuesState[index] = objectState;
           break;
 
         case ASSUME:
@@ -328,24 +343,34 @@
         .createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
   }
 
-  private ObjectState computeEnumInstanceObjectState(Value value) {
+  private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
     Value root = value.getAliasedValue();
     if (root.isPhi()) {
-      return ObjectState.empty();
+      return false;
     }
     Instruction definition = root.getDefinition();
-    if (definition.isNewInstance()) {
-      return computeObjectState(definition.outValue());
-    }
     if (definition.isStaticGet()) {
       // Enums with many instance rely on staticGets to set the $VALUES data instead of directly
       // keeping the values in registers, due to the max capacity of the redundant field load
       // elimination. The capacity has already been increased, so that this case is extremely
       // uncommon (very large enums).
       // TODO(b/169050248): We could consider analysing these to answer the object state here.
-      return ObjectState.empty();
+      return false;
     }
-    return ObjectState.empty();
+    ObjectState objectState =
+        definition.isNewInstance() ? computeObjectState(definition.outValue()) : null;
+    if (objectState == null || objectState.isEmpty()) {
+      // We need the state of all fields for the analysis to be valuable.
+      return false;
+    }
+    if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+      return false;
+    }
+    if (valuesState[index] != null) {
+      return false;
+    }
+    valuesState[index] = objectState;
+    return true;
   }
 
   private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c99ee11..8892da0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1038,6 +1038,10 @@
     return false;
   }
 
+  public boolean isNewArrayEmptyOrInvokeNewArray() {
+    return isNewArrayEmpty() || isInvokeNewArray();
+  }
+
   public NewArrayEmpty asNewArrayEmpty() {
     return null;
   }
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 c6d527f..a5f71e5 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
@@ -49,10 +49,6 @@
     return super.toString() + " " + type.toString();
   }
 
-  public Value dest() {
-    return outValue;
-  }
-
   public Value size() {
     return inValues.get(0);
   }
@@ -60,7 +56,7 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int size = builder.allocatedRegister(size(), getNumber());
-    int dest = builder.allocatedRegister(dest(), getNumber());
+    int dest = builder.allocatedRegister(outValue, getNumber());
     builder.add(this, new DexNewArray(dest, size, type));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a649af6..6fb553c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,6 +18,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
@@ -68,6 +69,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
@@ -1060,6 +1062,10 @@
             instruction.asInstancePut(), code, context, enumClass, enumValue);
       case INVOKE_DIRECT:
       case INVOKE_INTERFACE:
+        return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+      case INVOKE_NEW_ARRAY:
+        return analyzeInvokeNewArrayUser(
+            instruction.asInvokeNewArray(), code, context, enumClass, enumValue);
       case INVOKE_STATIC:
       case INVOKE_SUPER:
       case INVOKE_VIRTUAL:
@@ -1130,6 +1136,40 @@
     return Reason.INVALID_ARRAY_PUT;
   }
 
+  private Reason analyzeInvokeNewArrayUser(
+      InvokeNewArray invokeNewArray,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid.
+    // We need to prove that the value to put in and the array have correct types.
+    TypeElement arrayType = invokeNewArray.getOutType();
+    assert arrayType.isArrayType();
+
+    ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+    if (arrayBaseType == null) {
+      assert false;
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+    if (arrayBaseType.getClassType() != enumClass.type) {
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+
+    for (Value value : invokeNewArray.inValues()) {
+      TypeElement valueBaseType = value.getType();
+      if (valueBaseType.isArrayType()) {
+        assert valueBaseType.asArrayType().getBaseType().isClassType();
+        assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+        valueBaseType = valueBaseType.asArrayType().getBaseType();
+      }
+      if (!arrayBaseType.equalUpToNullability(valueBaseType)) {
+        return Reason.INVALID_INVOKE_NEW_ARRAY;
+      }
+    }
+    return Reason.ELIGIBLE;
+  }
+
   private Reason analyzeCheckCastUser(
       CheckCast checkCast,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 3e17a0a..7cb0290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,6 +32,8 @@
       new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
   public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
   public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+  public static final Reason INVALID_INVOKE_NEW_ARRAY =
+      new StringReason("INVALID_INVOKE_NEW_ARRAY");
   public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
   public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
   public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");