Reland #2: "Use new-filled-array for more things."

This reverts commit 533d03bb9749b7c72f75f6248a2099010ccb3245.

Reason for Reland: Made GetMembersTest and
IdentifierNameStringMarkerTest work with & without FilledNewArray.

Bug: b/246971330
Change-Id: I0aface999836bd776d38001c9b4850aefceacf95
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 369e60c..4b70a1c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -275,8 +275,8 @@
   public boolean isSubtype(DexType subtype, DexType supertype) {
     assert subtype != null;
     assert supertype != null;
-    assert subtype.isClassType();
-    assert supertype.isClassType();
+    assert subtype.isClassType() : "subtype not a class: " + subtype;
+    assert supertype.isClassType() : "supertype not a class: " + supertype;
     return subtype == supertype || isStrictSubtypeOf(subtype, supertype);
   }
 
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..2a543a8 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,38 @@
   }
 
   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 = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
-    if (valuesSize == 0) {
-      // No need to compute the state of an empty array.
+    int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
+    if (valuesSize < 1) {
+      // Array is empty or non-const size.
       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 +291,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 +334,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/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 74a3fb5..b4b2359 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -316,17 +318,37 @@
     Value sizeValue =
         instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
     Value newObjectsValue = code.createValue(objectArrayType);
-    instructionIterator.add(
-        new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
 
     // Populate the `objects` array.
-    for (int i = 0; i < objects.size(); i++) {
-      Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
-      Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
-      instructionIterator.add(materializingInstruction);
+    var rewriteOptions = appView.options().rewriteArrayOptions();
+    if (rewriteOptions.canUseFilledNewArrayOfObjects()
+        && objects.size() < rewriteOptions.maxRangeInputs) {
+      List<Value> arrayValues = new ArrayList<>(objects.size());
+      for (int i = 0; i < objects.size(); i++) {
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        arrayValues.add(materializingInstruction.outValue());
+      }
       instructionIterator.add(
-          new ArrayPut(
-              MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
+          new InvokeNewArray(
+              appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
+    } else {
+      instructionIterator.add(
+          new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
+
+      // Populate the `objects` array.
+      for (int i = 0; i < objects.size(); i++) {
+        Value indexValue =
+            instructionIterator.insertConstIntInstruction(code, appView.options(), i);
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        instructionIterator.add(
+            new ArrayPut(
+                MemberType.OBJECT,
+                newObjectsValue,
+                indexValue,
+                materializingInstruction.outValue()));
+      }
     }
 
     // Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index fbe3ff5..c25c456 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 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.NewArrayEmpty;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -302,18 +303,30 @@
    */
   private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
       Value objectsValue) throws InvalidRawMessageInfoException {
-    if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
+    if (objectsValue.isPhi()) {
       throw new InvalidRawMessageInfoException();
     }
 
     NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
-    int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
+    InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
 
-    // Verify that the size is correct.
+    if (newArrayEmpty == null && invokeNewArray == null) {
+      throw new InvalidRawMessageInfoException();
+    }
+    // Verify that the array is used in only one spot.
+    if (invokeNewArray != null) {
+      if (objectsValue.uniqueUsers().size() != 1) {
+        throw new InvalidRawMessageInfoException();
+      }
+      return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
+    }
+
     Value sizeValue = newArrayEmpty.size().getAliasedValue();
-    if (sizeValue.isPhi()
-        || !sizeValue.definition.isConstNumber()
-        || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
+    if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
+      throw new InvalidRawMessageInfoException();
+    }
+    int arraySize = sizeValue.definition.asConstNumber().getIntValue();
+    if (arraySize != objectsValue.uniqueUsers().size() - 1) {
       throw new InvalidRawMessageInfoException();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index e06cdbee..99f92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,20 +73,25 @@
    * <p>Use with caution!
    */
   public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) {
-    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
     if (definition.isConstNumber()) {
       // No need to explicitly remove `null`, it will be removed by ordinary dead code elimination
       // anyway.
       assert definition.asConstNumber().isZero();
       return;
     }
-
-    if (definition.isNewArrayEmpty()) {
-      Value arrayValue = definition.outValue();
-      if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
-        return;
-      }
-
+    Value arrayValue = definition.outValue();
+    if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+      return;
+    }
+    if (!definition.isNewArrayEmptyOrInvokeNewArray()) {
+      assert false;
+      return;
+    }
+    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
+    InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+    if (invokeNewArray != null) {
+      worklist.add(definition);
+    } else if (definition.isNewArrayEmpty()) {
       for (Instruction user : arrayValue.uniqueUsers()) {
         // If we encounter an Assume instruction here, we also need to consider indirect users.
         assert !user.isAssume();
@@ -95,11 +100,10 @@
         }
         worklist.add(user);
       }
-      internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
-      return;
+    } else {
+      assert false;
     }
-
-    assert false;
+    internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
   }
 
   /**
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/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2e815f..f0715b5 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
@@ -221,4 +221,9 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the number of elements in the array.
+  public int size() {
+    return inValues.size();
+  }
 }
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..a829247 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));
   }
 
@@ -171,4 +167,10 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the size of the array if it is known, -1 otherwise.
+  public int sizeIfConst() {
+    Value size = size();
+    return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1;
+  }
 }
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 6314197..a6da9b7 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
@@ -16,7 +16,6 @@
 
 import com.android.tools.r8.algorithms.scc.SCC;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
@@ -55,7 +54,6 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
@@ -84,6 +82,7 @@
 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.LinearFlowInstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -138,6 +137,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
@@ -162,7 +162,6 @@
     FALSE
   }
 
-  private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
 
@@ -2100,16 +2099,21 @@
     }
   }
 
-  private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
-    if (values == null) {
-      return null;
+  private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+    for (Value v : values) {
+      if (!v.isConstant()) {
+        return null;
+      }
     }
     if (elementSize == 1) {
       short[] result = new short[(size + 1) / 2];
       for (int i = 0; i < size; i += 2) {
-        short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
+        short value =
+            (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
         if (i + 1 < size) {
-          value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
+          value |=
+              (short)
+                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
         }
         result[i / 2] = value;
       }
@@ -2119,7 +2123,7 @@
     int shortsPerConstant = elementSize / 2;
     short[] result = new short[size * shortsPerConstant];
     for (int i = 0; i < size; i++) {
-      long value = values[i].asConstNumber().getRawValue();
+      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
       for (int part = 0; part < shortsPerConstant; part++) {
         result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
       }
@@ -2127,40 +2131,45 @@
     return result;
   }
 
-  private ConstInstruction[] computeConstantArrayValues(
-      NewArrayEmpty newArray, BasicBlock block, int size) {
-    if (size > MAX_FILL_ARRAY_SIZE) {
-      return null;
-    }
-    ConstInstruction[] values = new ConstInstruction[size];
+  private Value[] computeArrayValues(LinearFlowInstructionListIterator it, int size) {
+    NewArrayEmpty newArrayEmpty = it.next().asNewArrayEmpty();
+    assert newArrayEmpty != null;
+    Value arrayValue = newArrayEmpty.outValue();
+
+    // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+    // if types require a cast.
+    // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+    // if aput-object throws a ClassCastException if given an object that does not implement the
+    // desired interface, then we could add check-cast instructions for arguments we're not sure
+    // about.
+    DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+    boolean needsTypeCheck =
+        !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+    Value[] values = new Value[size];
     int remaining = size;
-    Set<Instruction> users = newArray.outValue().uniqueUsers();
-    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-    // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
-    // an instruction instance that can throw an exception.
-    InstructionIterator it = block.iterator();
-    it.nextUntil(i -> i == newArray);
-    do {
-      visitedBlocks.add(block);
+    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
       while (it.hasNext()) {
         Instruction instruction = it.next();
-        // If we encounter an instruction that can throw an exception we need to bail out of the
-        // optimization so that we do not transform half-initialized arrays into fully initialized
-        // arrays on exceptional edges. If the block has no handlers it is not observable so
-        // we perform the rewriting.
-        if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+      // If we encounter an instruction that can throw an exception we need to bail out of the
+      // optimization so that we do not transform half-initialized arrays into fully initialized
+      // arrays on exceptional edges. If the block has no handlers it is not observable so
+      // we perform the rewriting.
+      // TODO(b/246971330): Allow simplification when all users of the array are in the same
+      // try/catch.
+      if (instruction.getBlock().hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
           return null;
         }
         if (!users.contains(instruction)) {
           continue;
         }
-        // If the initialization sequence is broken by another use we cannot use a
-        // fill-array-data instruction.
-        if (!instruction.isArrayPut()) {
+      ArrayPut arrayPut = instruction.asArrayPut();
+      // If the initialization sequence is broken by another use we cannot use a fill-array-data
+      // instruction.
+      if (arrayPut == null || arrayPut.array() != arrayValue) {
           return null;
         }
-        ArrayPut arrayPut = instruction.asArrayPut();
-        if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
+      if (!arrayPut.index().isConstNumber()) {
           return null;
         }
         int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
@@ -2170,40 +2179,38 @@
         if (values[index] != null) {
           return null;
         }
-        ConstInstruction value = arrayPut.value().getConstInstruction();
+      Value value = arrayPut.value();
+      if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+        DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+        if (elementType.isArrayType()) {
+          if (elementType != valueDexType) {
+            return null;
+          }
+        } else if (valueDexType.isArrayType()) {
+          // isSubtype asserts for this case.
+          return null;
+        } else if (valueDexType.isNullValueType()) {
+          // Assume instructions can cause value.isAlwaysNull() == false while the DexType is
+          // null.
+          // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+          // that hits this case.
+        } else {
+          // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+          // library types (which this helper does not do).
+          if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+            return null;
+          }
+        }
+      }
         values[index] = value;
         --remaining;
         if (remaining == 0) {
           return values;
         }
       }
-      BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
-      block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-      it = block != null ? block.iterator() : null;
-    } while (it != null);
     return null;
   }
 
-  private boolean allowNewFilledArrayConstruction(Instruction instruction) {
-    if (!(instruction instanceof NewArrayEmpty)) {
-      return false;
-    }
-    NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-    if (!newArray.size().isConstant()) {
-      return false;
-    }
-    assert newArray.size().isConstNumber();
-    int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-    if (size < 1) {
-      return false;
-    }
-    if (newArray.type.isPrimitiveArrayType()) {
-      return true;
-    }
-    return newArray.type == dexItemFactory.stringArrayType
-        && options.canUseFilledNewArrayOfObjects();
-  }
-
   /**
    * Replace new-array followed by stores of constants to all entries with new-array
    * and fill-array-data / filled-new-array.
@@ -2212,6 +2219,10 @@
     if (options.isGeneratingClassFiles()) {
       return;
     }
+    InternalOptions.RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+    boolean canUseForObjects = rewriteOptions.canUseFilledNewArrayOfObjects();
+    boolean canUseForArrays = rewriteOptions.canUseFilledNewArrayOfArrays();
+
     for (BasicBlock block : code.blocks) {
       // Map from the array value to the number of array put instruction to remove for that value.
       Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
@@ -2220,31 +2231,51 @@
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
-        if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
+        NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+        if (newArrayEmpty == null || !newArrayEmpty.size().isConstant()) {
           continue;
         }
-        NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-        int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-        ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
+        if (instruction.getLocalInfo() != null) {
+          continue;
+        }
+        int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+        if (size < 1 || size > rewriteOptions.maxFillArrayDataInputs) {
+          continue;
+        }
+        if (!newArrayEmpty.type.isPrimitiveArrayType() && !canUseForObjects) {
+          continue;
+        }
+        if (newArrayEmpty.type.getNumberOfLeadingSquareBrackets() > 1 && !canUseForArrays) {
+          continue;
+        }
+
+        Value[] values =
+            computeArrayValues(
+                new LinearFlowInstructionListIterator(code, block, it.previousIndex()), size);
         if (values == null) {
           continue;
         }
-        if (newArray.type == dexItemFactory.stringArrayType) {
+        // filled-new-array is implemented only for int[] and Object[].
+        // For int[], using filled-new-array is usually smaller than filled-array-data.
+        // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+        if (!newArrayEmpty.type.isPrimitiveArrayType()
+            || (newArrayEmpty.type == dexItemFactory.intArrayType && size <= 5)) {
           // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
-          if (size > 200) {
+          if (size > rewriteOptions.maxRangeInputs) {
             continue;
           }
-          List<Value> stringValues = new ArrayList<>(size);
-          for (ConstInstruction value : values) {
-            stringValues.add(value.outValue());
-          }
-          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
+          // block.hasCatchHandlers() is fine here since new-array-filled replaces new-array-empty
+          // and computeArrayValues already checks that no throwing instructions exist between the
+          // original new-array-empty and the final aput-object (where the new-array-filled will be
+          // positioned).
+          Value invokeValue =
+              code.createValue(newArrayEmpty.getOutType(), newArrayEmpty.getLocalInfo());
           InvokeNewArray invoke =
-              new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
-          for (Value value : newArray.inValues()) {
-            value.removeUser(newArray);
+              new InvokeNewArray(newArrayEmpty.type, invokeValue, Arrays.asList(values));
+          for (Value value : newArrayEmpty.inValues()) {
+            value.removeUser(newArrayEmpty);
           }
-          newArray.outValue().replaceUsers(invokeValue);
+          newArrayEmpty.outValue().replaceUsers(invokeValue);
           it.removeOrReplaceByDebugLocalRead();
           instructionToInsertForArray.put(invokeValue, invoke);
           storesToRemoveForArray.put(invokeValue, size);
@@ -2254,26 +2285,33 @@
           if (size == 1) {
             continue;
           }
-          int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
+          // TODO(b/246971330): Allow simplification when all users of the array are in the same
+          // try/catch.
+          if (block.hasCatchHandlers()) {
+            // NewArrayFilledData can throw, so creating one as done below would add a second
+            // throwing instruction to the same block (the first one being NewArrayEmpty).
+            continue;
+          }
+          int elementSize = newArrayEmpty.type.elementSizeForPrimitiveArrayType();
           short[] contents = computeArrayFilledData(values, size, elementSize);
           if (contents == null) {
             continue;
           }
-          if (block.hasCatchHandlers()) {
-            continue;
-          }
-          int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
+          int arraySize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+          // fill-array-data requires the new-array-empty instruction to remain, as it does not
+          // itself create an array.
           NewArrayFilledData fillArray =
-              new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
-          fillArray.setPosition(newArray.getPosition());
+              new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, arraySize, contents);
+          fillArray.setPosition(newArrayEmpty.getPosition());
           it.add(fillArray);
-          storesToRemoveForArray.put(newArray.outValue(), size);
+          storesToRemoveForArray.put(newArrayEmpty.outValue(), size);
         }
       }
       // Second pass: remove all the array put instructions for the array for which we have
       // inserted a fill array data instruction instead.
       if (!storesToRemoveForArray.isEmpty()) {
         Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+        int numInstructionsInserted = 0;
         do {
           visitedBlocks.add(block);
           it = block.listIterator(code);
@@ -2295,6 +2333,7 @@
                     // last removed put at which point we are now adding the construction.
                     construction.setPosition(instruction.getPosition());
                     it.add(construction);
+                    numInstructionsInserted += 1;
                   }
                 }
               }
@@ -2303,6 +2342,7 @@
           BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
           block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
         } while (block != null);
+        assert numInstructionsInserted == instructionToInsertForArray.size();
       }
     }
     assert code.isConsistentSSA(appView);
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");
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 3e4078b..6e14907 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 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.NewArrayEmpty;
@@ -424,12 +425,16 @@
 
   // Perform a conservative evaluation of an array content of dex type values from its construction
   // until its use at a given instruction.
-  private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
-      NewArrayEmpty newArray,
-      List<CheckCast> aliases,
-      int size,
-      Instruction user,
-      DexItemFactory factory) {
+  private static DexTypeList evaluateTypeArrayContentFromConstructionToUse(
+      NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) {
+    int size = newArray.sizeIfConst();
+    if (size < 0) {
+      return null;
+    } else if (size == 0) {
+      // TODO: We should likely still scan to ensure no ArrayPut instructions exist.
+      return DexTypeList.empty();
+    }
+
     DexType[] values = new DexType[size];
     int remaining = size;
     Set<Instruction> users = Sets.newIdentityHashSet();
@@ -453,7 +458,7 @@
         if (instruction == user) {
           // Return the array content if all elements are known when hitting the user for which
           // the content was requested.
-          return remaining == 0 ? values : null;
+          return remaining == 0 ? new DexTypeList(values) : null;
         }
         // Any other kinds of use besides array-put mean that the array escapes and its content
         // could be altered.
@@ -498,6 +503,21 @@
     return null;
   }
 
+  private static DexTypeList evaluateTypeArrayContent(
+      InvokeNewArray newArray, DexItemFactory factory) {
+    List<Value> arrayValues = newArray.inValues();
+    int size = arrayValues.size();
+    DexType[] values = new DexType[size];
+    for (int i = 0; i < size; ++i) {
+      DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory);
+      if (type == null) {
+        return null;
+      }
+      values[i] = type;
+    }
+    return new DexTypeList(values);
+  }
+
   /**
    * Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
    * as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -551,30 +571,13 @@
     }
 
     // Make sure this Value refers to a new array.
-    if (!classListValue.definition.isNewArrayEmpty()
-        || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
+    if (classListValue.definition.isNewArrayEmpty()) {
+      return evaluateTypeArrayContentFromConstructionToUse(
+          classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
+    } else if (classListValue.definition.isInvokeNewArray()) {
+      return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
+    } else {
       return null;
     }
-
-    int size =
-        classListValue
-            .definition
-            .asNewArrayEmpty()
-            .size()
-            .getConstInstruction()
-            .asConstNumber()
-            .getIntValue();
-    if (size == 0) {
-      return DexTypeList.empty();
-    }
-
-    DexType[] arrayContent =
-        evaluateTypeArrayContentFromConstructionToUse(
-            classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
-
-    if (arrayContent == null) {
-      return null;
-    }
-    return new DexTypeList(arrayContent);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 213819d..93d387e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -104,7 +104,9 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -5079,25 +5081,43 @@
       return;
     }
     Value parametersValue = constructorDefinition.inValues().get(1);
-    if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+    if (parametersValue.isPhi()) {
       // Give up, we can't tell which constructor is being invoked.
       return;
     }
-
-    Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
-    if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
-      // Give up, we can't tell which constructor is being invoked.
+    NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
+    InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
+    int parametersSize =
+        newArrayEmpty != null
+            ? newArrayEmpty.sizeIfConst()
+            : invokeNewArray != null ? invokeNewArray.size() : -1;
+    if (parametersSize < 0) {
       return;
     }
 
     ProgramMethod initializer = null;
 
-    int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
     if (parametersSize == 0) {
       initializer = clazz.getProgramDefaultInitializer();
     } else {
       DexType[] parameterTypes = new DexType[parametersSize];
       int missingIndices = parametersSize;
+
+      if (newArrayEmpty != null) {
+        missingIndices = parametersSize;
+      } else {
+        missingIndices = 0;
+        List<Value> values = invokeNewArray.inValues();
+        for (int i = 0; i < parametersSize; ++i) {
+          DexType type =
+              ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView);
+          if (type == null) {
+            return;
+          }
+          parameterTypes[i] = type;
+        }
+      }
+
       for (Instruction user : parametersValue.uniqueUsers()) {
         if (user.isArrayPut()) {
           ArrayPut arrayPutInstruction = user.asArrayPut();
@@ -5159,20 +5179,31 @@
     }
 
     Value interfacesValue = invoke.arguments().get(1);
-    if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
+    if (interfacesValue.isPhi()) {
       // Give up, we can't tell which interfaces the proxy implements.
       return;
     }
 
-    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
-    for (Instruction user : interfacesValue.uniqueUsers()) {
-      if (!user.isArrayPut()) {
-        continue;
+    InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray();
+    NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty();
+    List<Value> values;
+    if (invokeNewArray != null) {
+      values = invokeNewArray.inValues();
+    } else if (newArrayEmpty != null) {
+      values = new ArrayList<>(interfacesValue.uniqueUsers().size());
+      for (Instruction user : interfacesValue.uniqueUsers()) {
+        ArrayPut arrayPut = user.asArrayPut();
+        if (arrayPut != null) {
+          values.add(arrayPut.value());
+        }
       }
+    } else {
+      return;
+    }
 
-      ArrayPut arrayPut = user.asArrayPut();
-      DexType type =
-          ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView);
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+    for (Value value : values) {
+      DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
       if (type == null || !type.isClassType()) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e653a7a..5cb7a74 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,6 +816,7 @@
 
   public boolean debug = false;
 
+  private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
   private final CallSiteOptimizationOptions callSiteOptimizationOptions =
       new CallSiteOptimizationOptions();
   private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -850,6 +851,10 @@
 
   public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
 
+  public RewriteArrayOptions rewriteArrayOptions() {
+    return rewriteArrayOptions;
+  }
+
   public CallSiteOptimizationOptions callSiteOptimizationOptions() {
     return callSiteOptimizationOptions;
   }
@@ -1383,6 +1388,34 @@
         System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
   }
 
+  public class RewriteArrayOptions {
+    // Arbitrary limit of number of inputs to new-filled-array/range.
+    // The technical limit is 255 (Constants.U8BIT_MAX).
+    public int maxRangeInputs = 200;
+    // Arbitrary limit of number of inputs to fill-array-data.
+    public int maxFillArrayDataInputs = 8 * 1024;
+
+    // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
+    // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
+    // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
+    // removed during the jelly-bean release cycle and is not there from kitkat.
+    //
+    // Buggy code that accidentally call code that only works on primitives arrays.
+    //
+    // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
+    public boolean canUseFilledNewArrayOfObjects() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.K);
+    }
+
+    // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
+    // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer;  (dalvikvm)
+    public boolean canUseFilledNewArrayOfArrays() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.L);
+    }
+  }
+
   public class CallSiteOptimizationOptions {
 
     private boolean enabled = true;
@@ -2387,19 +2420,6 @@
     return hasFeaturePresentFrom(AndroidApiLevel.J);
   }
 
-  // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
-  // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
-  // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
-  // removed during the jelly-bean release cycle and is not there from kitkat.
-  //
-  // Buggy code that accidentally call code that only works on primitives arrays.
-  //
-  // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
-  public boolean canUseFilledNewArrayOfObjects() {
-    assert isGeneratingDex();
-    return hasFeaturePresentFrom(AndroidApiLevel.K);
-  }
-
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
   // where an aget-wide instruction using the same register for the array
   // and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
index 50d23c9..7d41ae1 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
@@ -32,4 +33,18 @@
     }
     return result;
   }
+
+  public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) {
+    return new ThrowingIterator<>() {
+      @Override
+      public boolean hasNext() {
+        return it.hasNext();
+      }
+
+      @Override
+      public T next() throws E {
+        return it.next();
+      }
+    };
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 812eb63..b00d78b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.graph.DexCode;
@@ -93,16 +95,29 @@
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof DexConst4);
-    assertTrue(code.instructions[1] instanceof DexNewArray);
-    assertTrue(code.instructions[2] instanceof DexConst4);
-    assertTrue(code.instructions[3] instanceof DexConstClass);
-    assertTrue(code.instructions[4] instanceof DexAputObject);
-    assertTrue(code.instructions[5] instanceof DexConstClass);
-    assertTrue(code.instructions[6] instanceof DexConstString);
-    assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
-    assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
-    assertTrue(code.instructions[8] instanceof DexReturnVoid);
+
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[1] instanceof DexFilledNewArray) {
+      assertTrue(code.instructions[0] instanceof DexConstClass);
+      assertTrue(code.instructions[1] instanceof DexFilledNewArray);
+      assertTrue(code.instructions[2] instanceof DexMoveResultObject);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[4].asConstString().getString().toString());
+      assertTrue(code.instructions[5] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[6] instanceof DexReturnVoid);
+    } else {
+      assertTrue(code.instructions[0] instanceof DexConst4);
+      assertTrue(code.instructions[1] instanceof DexNewArray);
+      assertTrue(code.instructions[2] instanceof DexConst4);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexAputObject);
+      assertTrue(code.instructions[5] instanceof DexConstClass);
+      assertTrue(code.instructions[6] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
+      assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[8] instanceof DexReturnVoid);
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1e43777..7141ad08 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -53,8 +53,7 @@
         .addProgramClasses(I.class, A.class)
         .addProgramClassFileData(getTransformedMain())
         .addKeepMainRule(Main.class)
-        // Keep get() to prevent that we optimize it into having static return type A.
-        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
         .addOptionsModification(
             options ->
                 options
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 83ea780..c544c9e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,10 +16,12 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeDirect;
 import com.android.tools.r8.dex.code.DexInvokeStatic;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
 import com.android.tools.r8.dex.code.DexIputObject;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSgetObject;
@@ -639,7 +641,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -647,19 +650,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertEquals("foo", constString.getString().toString());
   }
 
@@ -700,7 +717,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -708,19 +726,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertNotEquals("foo", constString.getString().toString());
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index 4a76a6a..513dc73 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.AppView;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,7 +35,7 @@
     this.parameters = parameters;
   }
 
-  private static final String[] expectedOutput = {"3"};
+  private static final String[] expectedOutput = {"3", "2"};
 
   @Test
   public void d8() throws Exception {
@@ -44,7 +46,7 @@
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, true));
   }
 
   @Test
@@ -54,32 +56,52 @@
         .addProgramClasses(Main.class)
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, false));
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-    if (irCode.context().getReference().getName().toString().contains("main")) {
       new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+    String name = irCode.context().getReference().getName().toString();
+    if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+    } else if (name.contains("filledNewArray")) {
+      assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray));
     }
   }
 
-  private void assertNoArrayLength(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean d8) {
     ClassSubject mainClass = inspector.clazz(Main.class);
     assertTrue(mainClass.isPresent());
-    assertTrue(
-        mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
+    assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    if (!d8) {
+      MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
+      assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    }
   }
 
   public static final class Main {
+    @NeverInline
+    public static void filledArrayData() {
+      short[] values = new short[3];
+      values[0] = 5;
+      values[1] = 6;
+      values[2] = 1;
+      System.out.println(values.length);
+    }
+
+    @NeverInline
+    public static void filledNewArray() {
+      int[] values = new int[] {7, 8};
+      System.out.println(values.length);
+    }
+
     public static void main(String[] args) {
-      int[] ints = new int[3];
-      ints[0] = 5;
-      ints[1] = 6;
-      ints[2] = 1;
-      System.out.println(ints.length);
+      filledArrayData();
+      filledNewArray();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
new file mode 100644
index 0000000..aad69a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,765 @@
+// Copyright (c) 2022, 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.beust.jcommander.internal.Lists;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+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 SimplifyArrayConstructionTest extends TestBase {
+  @Parameters(name = "{0}, mode = {1}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+        CompilationMode.values());
+  }
+
+  private final TestParameters parameters;
+  private final CompilationMode compilationMode;
+
+  public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) {
+    this.parameters = parameters;
+    this.compilationMode = compilationMode;
+  }
+
+  private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = {
+    DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class
+  };
+
+  private static final String[] EXPECTED_OUTPUT = {
+    "[a]",
+    "[a, 1, null]",
+    "[1, null]",
+    "[1, null, 2]",
+    "[1, null, 2]",
+    "[1]",
+    "[a, b]",
+    "[1, 2, 3, 4, 5]",
+    "[1]",
+    "[a, 1, null, d, e, f]",
+    "[a, b, c, d, e, f]",
+    "[1, null, 3, null, null, 6]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[true, false]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1.0, 2.0]",
+    "[1.0, 2.0]",
+    "[]",
+    "[]",
+    "[true]",
+    "[1]",
+    "[1]",
+    "[1]",
+    "[1.0]",
+    "[1.0]",
+    "[0, 1]",
+    "[1, null]",
+    "[a]",
+    "[0, 1]",
+    "200",
+    "[0, 1, 2, 3, 4]",
+    "[4, 0, 0, 0, 0]",
+    "[4, 1, 2, 3, 4]",
+    "[0, 1, 2]",
+    "[0]",
+    "[0, 1, 2]",
+    "[1, 2, 3]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[0]",
+    "[null, null]",
+  };
+
+  private static final byte[] TRANSFORMED_MAIN = transformedMain();
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(compilationMode == CompilationMode.DEBUG);
+    testForRuntime(
+            parameters.getRuntime(),
+            d8TestBuilder ->
+                d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode))
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class)))
+        .setMode(compilationMode)
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .addKeepAnnotation()
+        .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }")
+        .addDontObfuscate()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  private static byte[] transformedMain() {
+    try {
+      return transformer(Main.class)
+          .transformMethodInsnInMethod(
+              "interfaceArrayWithRawObject",
+              (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                if (name.equals("getObjectThatImplementsSerializable")) {
+                  visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+                } else {
+                  visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                }
+              })
+          .setReturnType(
+              ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"),
+              Object.class.getTypeName())
+          .transform();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) {
+    if (parameters.isCfRuntime()) {
+      return;
+    }
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+
+    MethodSubject referenceArraysNoCasts =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
+    MethodSubject referenceArraysWithSubclasses =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+    MethodSubject interfaceArrayWithRawObject =
+        mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
+
+    MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+    MethodSubject intsThatUseFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
+    MethodSubject twoDimensionalArrays =
+        mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays");
+    MethodSubject objectArraysFilledNewArrayRange =
+        mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange");
+    MethodSubject arraysThatUseFilledData =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData");
+    MethodSubject arraysThatUseNewArrayEmpty =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty");
+    MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray");
+    MethodSubject arrayWithCorrectCountButIncompleteCoverage =
+        mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
+    MethodSubject arrayWithExtraInitialPuts =
+        mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
+    MethodSubject catchHandlerThrowing =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
+    MethodSubject catchHandlerNonThrowingFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
+    MethodSubject catchHandlerNonThrowingFillArrayData =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+    MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+
+    assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
+    assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
+    assertFilledArrayData(arraysThatUseFilledData);
+
+    if (compilationMode == CompilationMode.DEBUG) {
+      // The explicit assignments can't be collapsed without breaking the debugger's ability to
+      // visit each line.
+      assertArrayTypes(reversedArray, DexNewArray.class);
+    } else {
+      assertArrayTypes(reversedArray, DexFilledNewArray.class);
+    }
+
+    // Cannot use filled-new-array of Object before K.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+      assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
+      assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      assertArrayTypes(phiFilledNewArray, DexNewArray.class);
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
+      assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
+      assertArrayTypes(assumedValues, DexNewArray.class);
+    } else {
+      assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
+      // TODO(b/246971330): Add support for arrays with subtypes.
+      if (isR8) {
+        assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
+      } else {
+        assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      }
+
+      // TODO(b/246971330): Add support for arrays whose values have conditionals.
+      // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
+
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class);
+
+      if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+        assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class);
+      } else {
+        // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures.
+      }
+
+      assertArrayTypes(assumedValues, DexFilledNewArray.class);
+    }
+    // filled-new-array fails verification when types of parameters are not subclasses (aput-object
+    // does not).
+    assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class);
+
+    // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we
+    // haven't bothered.
+    assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
+    assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
+
+    assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
+    assertArrayTypes(catchHandlerNonThrowingFillArrayData, DexNewArray.class);
+    assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
+    return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass());
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) {
+    return isInstruction(Arrays.asList(clazz));
+  }
+
+  private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(allowedArrayInst);
+
+    assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst)));
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+  }
+
+  private static void assertFilledArrayData(MethodSubject method) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(DexFillArrayData.class);
+    disallowedClasses.remove(DexNewArray.class);
+
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+    assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut));
+    long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count();
+    long numFillArray =
+        method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count();
+    assertEquals(numNewArray, numFillArray);
+  }
+
+  public static final class Main {
+    static final String assumedNonNullField = null;
+    static final String assumedNullField = null;
+
+    public static void main(String[] args) {
+      referenceArraysNoCasts();
+      referenceArraysWithSubclasses();
+      interfaceArrayWithRawObject();
+      phiFilledNewArray();
+      intsThatUseFilledNewArray();
+      twoDimensionalArrays();
+      objectArraysFilledNewArrayRange();
+      arraysThatUseFilledData();
+      arraysThatUseNewArrayEmpty();
+      reversedArray();
+      arrayWithCorrectCountButIncompleteCoverage();
+      arrayWithExtraInitialPuts();
+      catchHandlerThrowing();
+      catchHandlerNonThrowingFilledNewArray();
+      catchHandlerNonThrowingFillArrayData();
+      arrayIntoAnotherArray();
+      assumedValues();
+    }
+
+    @NeverInline
+    private static void referenceArraysNoCasts() {
+      // Test exact class, no null.
+      String[] stringArr = {"a"};
+      System.out.println(Arrays.toString(stringArr));
+      // Tests that no type info is needed when array type is Object[].
+      Object[] objectArr = {"a", 1, null};
+      System.out.println(Arrays.toString(objectArr));
+      // Test that interface arrays work when we have the exact interface already.
+      Serializable[] interfaceArr = {getSerializable(1), null};
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @Keep
+    private static Serializable getSerializable(Integer value) {
+      return value;
+    }
+
+    @NeverInline
+    private static void referenceArraysWithSubclasses() {
+      Serializable[] interfaceArr = {1, null, 2};
+      System.out.println(Arrays.toString(interfaceArr));
+      Number[] objArray = {1, null, 2};
+      System.out.println(Arrays.toString(objArray));
+    }
+
+    @NeverInline
+    private static void interfaceArrayWithRawObject() {
+      // Interfaces can use filled-new-array only when we know types implement the interface.
+      Serializable[] arr = new Serializable[1];
+      // Transformed from `I get()` to `Object get()`.
+      arr[0] = getObjectThatImplementsSerializable();
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @Keep
+    private static /*Object*/ Serializable getObjectThatImplementsSerializable() {
+      return 1;
+    }
+
+    @NeverInline
+    private static void reversedArray() {
+      int[] arr = new int[5];
+      arr[4] = 4;
+      arr[3] = 3;
+      arr[2] = 2;
+      arr[1] = 1;
+      arr[0] = 0;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithCorrectCountButIncompleteCoverage() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithExtraInitialPuts() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      arr[1] = 1;
+      arr[2] = 2;
+      arr[3] = 3;
+      arr[4] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFilledNewArray() {
+      try {
+        int[] arr1 = {1, 2, 3};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr1));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFillArrayData() {
+      try {
+        int[] arr = {1, 2, 3, 4, 5, 6};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerThrowing() {
+      int[] arr1 = new int[3];
+      arr1[0] = 0;
+      arr1[1] = 1;
+      // Since the array is used in only one spot, and that spot is not within the try/catch, it
+      // should be safe to use filled-new-array, but we don't.
+      try {
+        System.currentTimeMillis();
+        arr1[2] = 2;
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+      System.out.println(Arrays.toString(arr1));
+
+      try {
+        // Test filled-new-array with a throwing instruction before the last array-put.
+        int[] arr2 = new int[1];
+        System.currentTimeMillis();
+        arr2[0] = 0;
+        System.out.println(Arrays.toString(arr2));
+
+        // Test filled-array-data with a throwing instruction before the last array-put.
+        short[] arr3 = new short[3];
+        arr3[0] = 0;
+        arr3[1] = 1;
+        System.currentTimeMillis();
+        arr3[2] = 2;
+        System.out.println(Arrays.toString(arr3));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void arrayIntoAnotherArray() {
+      // Tests that our computeValues() method does not assume all array-put-object users have
+      // the array as the target.
+      int[] intArr = new int[1];
+      Object[] objArr = new Object[2];
+      objArr[0] = intArr;
+      System.out.println(Arrays.toString((int[]) objArr[0]));
+    }
+
+    @NeverInline
+    private static void assumedValues() {
+      String[] arr = {assumedNonNullField, assumedNullField};
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void phiFilledNewArray() {
+      // The presence of ? should not affect use of filled-new-array.
+      String[] phiArray = {"a", System.nanoTime() > 0 ? "b" : "c"};
+      System.out.println(Arrays.toString(phiArray));
+    }
+
+    @NeverInline
+    private static void intsThatUseFilledNewArray() {
+      // Up to 5 ints uses filled-new-array rather than filled-array-data.
+      int[] intArr = {1, 2, 3, 4, 5};
+      System.out.println(Arrays.toString(intArr));
+    }
+
+    @NeverInline
+    private static void twoDimensionalArrays() {
+      Integer[][] twoDimensions = {new Integer[] {1}, null};
+      System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0)));
+    }
+
+    @NeverInline
+    private static void objectArraysFilledNewArrayRange() {
+      // 6 or more elements use /range variant.
+      Object[] objectArr = {"a", 1, null, "d", "e", "f"};
+      System.out.println(Arrays.toString(objectArr));
+      String[] stringArr = {"a", "b", "c", "d", "e", "f"};
+      System.out.println(Arrays.toString(stringArr));
+      Serializable[] interfaceArr = {
+        getSerializable(1), null, getSerializable(3), null, null, getSerializable(6)
+      };
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseFilledData() {
+      // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used).
+      int[] intArr = {1, 2, 3, 4, 5, 6};
+      // For other primitives, > 1 element leads to fill-array-data.
+      System.out.println(Arrays.toString(intArr));
+      boolean[] boolArr = {true, false};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1, 2};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1', '2'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1, 2};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1, 2};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1, 2};
+      System.out.println(Arrays.toString(doubleArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseNewArrayEmpty() {
+      // int/object of size zero should not use filled-new-array.
+      int[] intArr = {};
+      System.out.println(Arrays.toString(intArr));
+      String[] strArr = {};
+      System.out.println(Arrays.toString(strArr));
+
+      // Other primitives with size <= 1 should not use filled-array-data.
+      boolean[] boolArr = {true};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1};
+      System.out.println(Arrays.toString(doubleArr));
+
+      // Array used before all members are set.
+      int[] readArray = new int[2];
+      readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime());
+      readArray[1] = readArray[0] + 1;
+      System.out.println(Arrays.toString(readArray));
+
+      // Array does not have all elements set (we could make this work, but likely this is rare).
+      Integer[] partialArray = new Integer[2];
+      partialArray[0] = 1;
+      System.out.println(Arrays.toString(partialArray));
+
+      // Non-constant array size.
+      int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime());
+      Object[] nonConstSize = new Object[trickyZero + 1];
+      nonConstSize[0] = "a";
+      System.out.println(Arrays.toString(nonConstSize));
+
+      // Non-constant index.
+      Object[] nonConstIndex = new Object[2];
+      nonConstIndex[trickyZero] = 0;
+      nonConstIndex[trickyZero + 1] = 1;
+      System.out.println(Arrays.toString(nonConstIndex));
+
+      // Exceeds our (arbitrary) size limit for /range.
+      String[] bigArr = new String[201];
+      bigArr[0] = "0";
+      bigArr[1] = "1";
+      bigArr[2] = "2";
+      bigArr[3] = "3";
+      bigArr[4] = "4";
+      bigArr[5] = "5";
+      bigArr[6] = "6";
+      bigArr[7] = "7";
+      bigArr[8] = "8";
+      bigArr[9] = "9";
+      bigArr[10] = "10";
+      bigArr[11] = "11";
+      bigArr[12] = "12";
+      bigArr[13] = "13";
+      bigArr[14] = "14";
+      bigArr[15] = "15";
+      bigArr[16] = "16";
+      bigArr[17] = "17";
+      bigArr[18] = "18";
+      bigArr[19] = "19";
+      bigArr[20] = "20";
+      bigArr[21] = "21";
+      bigArr[22] = "22";
+      bigArr[23] = "23";
+      bigArr[24] = "24";
+      bigArr[25] = "25";
+      bigArr[26] = "26";
+      bigArr[27] = "27";
+      bigArr[28] = "28";
+      bigArr[29] = "29";
+      bigArr[30] = "30";
+      bigArr[31] = "31";
+      bigArr[32] = "32";
+      bigArr[33] = "33";
+      bigArr[34] = "34";
+      bigArr[35] = "35";
+      bigArr[36] = "36";
+      bigArr[37] = "37";
+      bigArr[38] = "38";
+      bigArr[39] = "39";
+      bigArr[40] = "40";
+      bigArr[41] = "41";
+      bigArr[42] = "42";
+      bigArr[43] = "43";
+      bigArr[44] = "44";
+      bigArr[45] = "45";
+      bigArr[46] = "46";
+      bigArr[47] = "47";
+      bigArr[48] = "48";
+      bigArr[49] = "49";
+      bigArr[50] = "50";
+      bigArr[51] = "51";
+      bigArr[52] = "52";
+      bigArr[53] = "53";
+      bigArr[54] = "54";
+      bigArr[55] = "55";
+      bigArr[56] = "56";
+      bigArr[57] = "57";
+      bigArr[58] = "58";
+      bigArr[59] = "59";
+      bigArr[60] = "60";
+      bigArr[61] = "61";
+      bigArr[62] = "62";
+      bigArr[63] = "63";
+      bigArr[64] = "64";
+      bigArr[65] = "65";
+      bigArr[66] = "66";
+      bigArr[67] = "67";
+      bigArr[68] = "68";
+      bigArr[69] = "69";
+      bigArr[70] = "70";
+      bigArr[71] = "71";
+      bigArr[72] = "72";
+      bigArr[73] = "73";
+      bigArr[74] = "74";
+      bigArr[75] = "75";
+      bigArr[76] = "76";
+      bigArr[77] = "77";
+      bigArr[78] = "78";
+      bigArr[79] = "79";
+      bigArr[80] = "80";
+      bigArr[81] = "81";
+      bigArr[82] = "82";
+      bigArr[83] = "83";
+      bigArr[84] = "84";
+      bigArr[85] = "85";
+      bigArr[86] = "86";
+      bigArr[87] = "87";
+      bigArr[88] = "88";
+      bigArr[89] = "89";
+      bigArr[90] = "90";
+      bigArr[91] = "91";
+      bigArr[92] = "92";
+      bigArr[93] = "93";
+      bigArr[94] = "94";
+      bigArr[95] = "95";
+      bigArr[96] = "96";
+      bigArr[97] = "97";
+      bigArr[98] = "98";
+      bigArr[99] = "99";
+      bigArr[100] = "100";
+      bigArr[101] = "101";
+      bigArr[102] = "102";
+      bigArr[103] = "103";
+      bigArr[104] = "104";
+      bigArr[105] = "105";
+      bigArr[106] = "106";
+      bigArr[107] = "107";
+      bigArr[108] = "108";
+      bigArr[109] = "109";
+      bigArr[110] = "110";
+      bigArr[111] = "111";
+      bigArr[112] = "112";
+      bigArr[113] = "113";
+      bigArr[114] = "114";
+      bigArr[115] = "115";
+      bigArr[116] = "116";
+      bigArr[117] = "117";
+      bigArr[118] = "118";
+      bigArr[119] = "119";
+      bigArr[120] = "120";
+      bigArr[121] = "121";
+      bigArr[122] = "122";
+      bigArr[123] = "123";
+      bigArr[124] = "124";
+      bigArr[125] = "125";
+      bigArr[126] = "126";
+      bigArr[127] = "127";
+      bigArr[128] = "128";
+      bigArr[129] = "129";
+      bigArr[130] = "130";
+      bigArr[131] = "131";
+      bigArr[132] = "132";
+      bigArr[133] = "133";
+      bigArr[134] = "134";
+      bigArr[135] = "135";
+      bigArr[136] = "136";
+      bigArr[137] = "137";
+      bigArr[138] = "138";
+      bigArr[139] = "139";
+      bigArr[140] = "140";
+      bigArr[141] = "141";
+      bigArr[142] = "142";
+      bigArr[143] = "143";
+      bigArr[144] = "144";
+      bigArr[145] = "145";
+      bigArr[146] = "146";
+      bigArr[147] = "147";
+      bigArr[148] = "148";
+      bigArr[149] = "149";
+      bigArr[150] = "150";
+      bigArr[151] = "151";
+      bigArr[152] = "152";
+      bigArr[153] = "153";
+      bigArr[154] = "154";
+      bigArr[155] = "155";
+      bigArr[156] = "156";
+      bigArr[157] = "157";
+      bigArr[158] = "158";
+      bigArr[159] = "159";
+      bigArr[160] = "160";
+      bigArr[161] = "161";
+      bigArr[162] = "162";
+      bigArr[163] = "163";
+      bigArr[164] = "164";
+      bigArr[165] = "165";
+      bigArr[166] = "166";
+      bigArr[167] = "167";
+      bigArr[168] = "168";
+      bigArr[169] = "169";
+      bigArr[170] = "170";
+      bigArr[171] = "171";
+      bigArr[172] = "172";
+      bigArr[173] = "173";
+      bigArr[174] = "174";
+      bigArr[175] = "175";
+      bigArr[176] = "176";
+      bigArr[177] = "177";
+      bigArr[178] = "178";
+      bigArr[179] = "179";
+      bigArr[180] = "180";
+      bigArr[181] = "181";
+      bigArr[182] = "182";
+      bigArr[183] = "183";
+      bigArr[184] = "184";
+      bigArr[185] = "185";
+      bigArr[186] = "186";
+      bigArr[187] = "187";
+      bigArr[188] = "188";
+      bigArr[189] = "189";
+      bigArr[190] = "190";
+      bigArr[191] = "191";
+      bigArr[192] = "192";
+      bigArr[193] = "193";
+      bigArr[194] = "194";
+      bigArr[195] = "195";
+      bigArr[196] = "196";
+      bigArr[197] = "197";
+      bigArr[198] = "198";
+      bigArr[199] = "199";
+      bigArr[200] = "200";
+      System.out.println(Arrays.asList(bigArr).get(200));
+    }
+  }
+}