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

This reverts commit b21566f8b8a0308e7c89da9bfb436d416e33ec38.

Bug: b/246971330
Change-Id: I8fd974407b5cf21976bd5af9448e086b42f23fd3
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..104b854 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,36 @@
     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.canUseFilledNewArrayOfNonStringObjects()
+        && objects.size() < rewriteOptions.maxSizeForFilledNewArrayOfReferences) {
+      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));
+
+      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..00182d3 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.hasSingleUniqueUser()) {
+        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 23f0de9..d7e5fc1 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;
@@ -107,9 +106,12 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ArrayListMultimap;
@@ -138,10 +140,10 @@
 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;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -162,7 +164,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;
 
@@ -2101,16 +2102,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;
       }
@@ -2120,7 +2126,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);
       }
@@ -2128,185 +2134,354 @@
     return result;
   }
 
-  private ConstInstruction[] computeConstantArrayValues(
-      NewArrayEmpty newArray, BasicBlock block, int size) {
-    if (size > MAX_FILL_ARRAY_SIZE) {
-      return null;
+  private static class FilledArrayConversionInfo {
+
+    Value[] values;
+    List<ArrayPut> arrayPutsToRemove;
+    LinearFlowInstructionListIterator lastArrayPutIterator;
+
+    public FilledArrayConversionInfo(int size) {
+      values = new Value[size];
+      arrayPutsToRemove = new ArrayList<>(size);
     }
-    ConstInstruction[] values = new ConstInstruction[size];
+  }
+
+  private FilledArrayConversionInfo computeConversionInfo(
+      FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
+    NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+    assert it.peekPrevious() == newArrayEmpty;
+    Value arrayValue = newArrayEmpty.outValue();
+    int size = candidate.size;
+
+    // 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;
+
+    FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
+    Value[] values = info.values;
     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);
-      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()) {
+    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      BasicBlock block = instruction.getBlock();
+      // 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 (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+        return null;
+      }
+      if (!users.contains(instruction)) {
+        // If any instruction can transfer control between the new-array and the last array put
+        // then it is not safe to move the new array to the point of the last put.
+        if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
           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()) {
+        continue;
+      }
+      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;
+      }
+      if (!arrayPut.index().isConstNumber()) {
+        return null;
+      }
+      if (arrayPut.instructionInstanceCanThrow()) {
+        assert false;
+        return null;
+      }
+      int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+      if (index < 0 || index >= values.length) {
+        return null;
+      }
+      if (values[index] != null) {
+        return null;
+      }
+      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;
-        }
-        ArrayPut arrayPut = instruction.asArrayPut();
-        if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
-          return null;
-        }
-        int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
-        if (index < 0 || index >= values.length) {
-          return null;
-        }
-        if (values[index] != null) {
-          return null;
-        }
-        ConstInstruction value = arrayPut.value().getConstInstruction();
-        values[index] = value;
-        --remaining;
-        if (remaining == 0) {
-          return values;
+        } 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;
+          }
         }
       }
-      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);
+      info.arrayPutsToRemove.add(arrayPut);
+      values[index] = value;
+      --remaining;
+      if (remaining == 0) {
+        info.lastArrayPutIterator = it;
+        return info;
+      }
+    }
     return null;
   }
 
-  private boolean allowNewFilledArrayConstruction(Instruction instruction) {
-    if (!(instruction instanceof NewArrayEmpty)) {
+  private static class FilledArrayCandidate {
+    final NewArrayEmpty newArrayEmpty;
+    final int size;
+    final boolean encodeAsFilledNewArray;
+
+    public FilledArrayCandidate(
+        NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+      assert size > 0;
+      this.newArrayEmpty = newArrayEmpty;
+      this.size = size;
+      this.encodeAsFilledNewArray = encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledNewArray() {
+      return encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledArrayData() {
+      return !useFilledNewArray();
+    }
+  }
+
+  private FilledArrayCandidate computeFilledArrayCandiate(
+      Instruction instruction, RewriteArrayOptions options) {
+    NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+    if (newArrayEmpty == null) {
+      return null;
+    }
+    if (instruction.getLocalInfo() != null) {
+      return null;
+    }
+    if (!newArrayEmpty.size().isConstant()) {
+      return null;
+    }
+    int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+    if (!options.isPotentialSize(size)) {
+      return null;
+    }
+    DexType arrayType = newArrayEmpty.type;
+    boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
+    if (!encodeAsFilledNewArray
+        && !canUseFilledArrayData(arrayType, size, instruction.getBlock(), options)) {
+      return null;
+    }
+    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+  }
+
+  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+    if (size < options.minSizeForFilledNewArray) {
       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()) {
+    // 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 (!arrayType.isPrimitiveArrayType()) {
+      if (size > options.maxSizeForFilledNewArrayOfReferences) {
+        return false;
+      }
+      if (arrayType == dexItemFactory.stringArrayType) {
+        return options.canUseFilledNewArrayOfStrings();
+      }
+      if (!options.canUseFilledNewArrayOfNonStringObjects()) {
+        return false;
+      }
+      if (!options.canUseFilledNewArrayOfArrays()
+          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+        return false;
+      }
       return true;
     }
-    return newArray.type == dexItemFactory.stringArrayType
-        && options.canUseFilledNewArrayOfObjects();
+    if (arrayType == dexItemFactory.intArrayType) {
+      return size <= options.maxSizeForFilledNewArrayOfInts;
+    }
+    return false;
+  }
+
+  private boolean canUseFilledArrayData(
+      DexType arrayType, int size, BasicBlock block, RewriteArrayOptions options) {
+    // If there is only one element it is typically smaller to generate the array put
+    // instruction instead of fill array data.
+    if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
+      return false;
+    }
+    if (!arrayType.isPrimitiveArrayType()) {
+      return false;
+    }
+    // NewArrayFilledData can throw, so creating one as done below would add a second
+    // throwing instruction to the same block (the first one being NewArrayEmpty).
+    // TODO(b/246971330): Support splitting the block when inserting the additional instruction.
+    if (block.hasCatchHandlers()) {
+      return false;
+    }
+    return true;
   }
 
   /**
-   * Replace new-array followed by stores of constants to all entries with new-array
-   * and fill-array-data / filled-new-array.
+   * Replace new-array followed by stores of constants to all entries with new-array and
+   * fill-array-data / filled-new-array.
+   *
+   * <p>The format of the new-array and its puts must be of the form:
+   *
+   * <pre>
+   *   v0 <- new-array T vSize
+   *   ...
+   *   array-put v0 vValue1 vIndex1
+   *   ...
+   *   array-put v0 vValueN vIndexN
+   * </pre>
+   *
+   * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides
+   * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and
+   * also cannot have catch handlers that would transfer out control (those could then have uses of
+   * v0).
+   *
+   * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
+   * remain active on the translated code. Translated code can have two forms.
+   *
+   * <p>The first is using the original array allocation and filling in its data if it can be
+   * encoded:
+   *
+   * <pre>
+   *   v0 <- new-array T vSize
+   *   filled-array-data v0
+   *   ...
+   *   ...
+   * </pre>
+   *
+   * The data payload is encoded directly in the instruction so no dependencies are needed for
+   * filling the data array. Thus, the fill is inserted at the point of the allocation. If the
+   * allocation has catch handlers its block must be split and the handlers put on the fill
+   * instruction too. This is correct only because there are no exceptional transfers in (...) that
+   * could observe the early initialization of the data.
+   *
+   * <p>The second is using filled-new-array and has the form:
+   *
+   * <pre>
+   * ...
+   * ...
+   * v0 <- filled-new-array T vValue1 ... vValueN
+   * </pre>
+   *
+   * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
+   * allocation of the array. The late allocation ensures that the values are available at
+   * allocation time. If the original allocation has catch handlers then the new allocation needs to
+   * link those too. In general that may require splitting the block twice so that the new
+   * allocation is the single throwing instruction in its block.
    */
   public void simplifyArrayConstruction(IRCode code) {
     if (options.isGeneratingClassFiles()) {
       return;
     }
-    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<>();
-      Map<Value, Integer> storesToRemoveForArray = new HashMap<>();
-      // First pass: identify candidates and insert fill array data instruction.
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
-          continue;
-        }
-        NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-        int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-        ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
-        if (values == null) {
-          continue;
-        }
-        if (newArray.type == dexItemFactory.stringArrayType) {
-          // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
-          if (size > 200) {
-            continue;
-          }
-          List<Value> stringValues = new ArrayList<>(size);
-          for (ConstInstruction value : values) {
-            stringValues.add(value.outValue());
-          }
-          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
-          InvokeNewArray invoke =
-              new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
-          for (Value value : newArray.inValues()) {
-            value.removeUser(newArray);
-          }
-          newArray.outValue().replaceUsers(invokeValue);
-          it.removeOrReplaceByDebugLocalRead();
-          instructionToInsertForArray.put(invokeValue, invoke);
-          storesToRemoveForArray.put(invokeValue, size);
-        } else {
-          // If there is only one element it is typically smaller to generate the array put
-          // instruction instead of fill array data.
-          if (size == 1) {
-            continue;
-          }
-          int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
-          short[] contents = computeArrayFilledData(values, size, elementSize);
-          if (contents == null) {
-            continue;
-          }
-          if (block.hasCatchHandlers()) {
-            continue;
-          }
-          int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-          NewArrayFilledData fillArray =
-              new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
-          fillArray.setPosition(newArray.getPosition());
-          it.add(fillArray);
-          storesToRemoveForArray.put(newArray.outValue(), size);
-        }
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+    while (worklist.hasNext()) {
+      BasicBlock block = worklist.next();
+      simplifyArrayConstructionBlock(block, worklist, code, options.rewriteArrayOptions());
+    }
+  }
+
+  private void simplifyArrayConstructionBlock(
+      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, RewriteArrayOptions options) {
+    InstructionListIterator it = block.listIterator(code);
+    while (it.hasNext()) {
+      FilledArrayCandidate candidate = computeFilledArrayCandiate(it.next(), options);
+      if (candidate == null) {
+        continue;
       }
-      // 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();
-        do {
-          visitedBlocks.add(block);
-          it = block.listIterator(code);
-          while (it.hasNext()) {
-            Instruction instruction = it.next();
-            if (instruction.isArrayPut()) {
-              Value array = instruction.asArrayPut().array();
-              Integer toRemoveCount = storesToRemoveForArray.get(array);
-              if (toRemoveCount != null) {
-                if (toRemoveCount > 0) {
-                  storesToRemoveForArray.put(array, --toRemoveCount);
-                  it.remove();
-                }
-                if (toRemoveCount == 0) {
-                  storesToRemoveForArray.put(array, --toRemoveCount);
-                  Instruction construction = instructionToInsertForArray.get(array);
-                  if (construction != null) {
-                    // Set the position of the new array construction to be the position of the
-                    // last removed put at which point we are now adding the construction.
-                    construction.setPosition(instruction.getPosition());
-                    it.add(construction);
-                  }
-                }
-              }
+      FilledArrayConversionInfo info =
+          computeConversionInfo(
+              candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
+      if (info == null) {
+        continue;
+      }
+
+      Instruction instructionAfterCandidate = it.peekNext();
+      NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+      DexType arrayType = newArrayEmpty.type;
+      int size = candidate.size;
+      Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
+      if (candidate.useFilledNewArray()) {
+        assert newArrayEmpty.getLocalInfo() == null;
+        Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
+        Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
+        InvokeNewArray invoke =
+            new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
+        invoke.setPosition(lastArrayPut.getPosition());
+        for (Value value : newArrayEmpty.inValues()) {
+          value.removeUser(newArrayEmpty);
+        }
+        newArrayEmpty.outValue().replaceUsers(invokeValue);
+        instructionsToRemove.add(newArrayEmpty);
+
+        boolean originalAllocationPointHasHandlers = newArrayEmpty.getBlock().hasCatchHandlers();
+        boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
+
+        if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+          info.lastArrayPutIterator.add(invoke);
+        } else {
+          BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
+          if (originalAllocationPointHasHandlers) {
+            if (!insertionBlock.isTrivialGoto()) {
+              BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
+              assert insertionBlock.isTrivialGoto();
+              worklist.addIfNotSeen(blockAfterInsertion);
+            }
+            insertionBlock.moveCatchHandlers(block);
+          } else {
+            worklist.addIfNotSeen(insertionBlock);
+          }
+          insertionBlock.listIterator(code).add(invoke);
+        }
+      } else {
+        assert candidate.useFilledArrayData();
+        int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+        short[] contents = computeArrayFilledData(info.values, size, elementSize);
+        if (contents == null) {
+          continue;
+        }
+        // fill-array-data requires the new-array-empty instruction to remain, as it does not
+        // itself create an array.
+        NewArrayFilledData fillArray =
+            new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
+        fillArray.setPosition(newArrayEmpty.getPosition());
+        it.add(fillArray);
+      }
+
+      instructionsToRemove.addAll(info.arrayPutsToRemove);
+      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+      for (Instruction instruction : instructionsToRemove) {
+        BasicBlock ownerBlock = instruction.getBlock();
+        if (visitedBlocks.add(ownerBlock)) {
+          InstructionListIterator removeIt = ownerBlock.listIterator(code);
+          while (removeIt.hasNext()) {
+            if (instructionsToRemove.contains(removeIt.next())) {
+              removeIt.removeOrReplaceByDebugLocalRead();
             }
           }
-          BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
-          block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-        } while (block != null);
+        }
       }
+
+      // The above has invalidated the block iterator so reset it and continue.
+      it = block.listIterator(code, instructionAfterCandidate);
     }
-    assert code.isConsistentSSA(appView);
   }
 
   // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
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 173c302..6c5c07b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -105,7 +105,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;
@@ -5090,25 +5092,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();
@@ -5170,20 +5190,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 3b87985..4f8669b 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,51 @@
         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 minSizeForFilledNewArray = 1;
+    public int maxSizeForFilledNewArrayOfReferences = 200;
+    public int maxSizeForFilledNewArrayOfInts = 5;
+
+    // Arbitrary limits of number of inputs to fill-array-data.
+    public int minSizeForFilledArrayData = 2;
+    public int maxSizeForFilledArrayData = 8 * 1024;
+
+    // Check the most relaxed size range allowed.
+    public boolean isPotentialSize(int size) {
+      return minSizeForFilledNewArray <= size && size <= maxSizeForFilledArrayData;
+    }
+
+    // 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 canUseFilledNewArrayOfStrings() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.K);
+    }
+
+    // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues.
+    // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview
+    // It somehow had a new-array-filled return null.
+    public boolean canUseFilledNewArrayOfNonStringObjects() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.N);
+    }
+
+    // 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;
@@ -2424,19 +2474,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/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
index 8cc0379..0003703 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
@@ -3,17 +3,25 @@
 // 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.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-// Regression test for issue found in b/259986613
 @RunWith(Parameterized.class)
 public class NewArrayInCatchRangeTest extends TestBase {
 
@@ -46,19 +54,32 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(NewArrayInCatchRangeTest.class)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(1, filledArrayInstructions.size());
+    InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+    assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
   }
 
   static class TestClass {
 
     public static int foo() {
       int value = 1;
-      int[] array = new int[1];
+      int[] array = null;
       try {
-        array[0] = value;
-      } catch (RuntimeException e) {
-        return array[0];
+        array = new int[1];
+      } catch (Exception e) {
+        return array == null ? -1 : array.length;
       }
+      array[0] = value;
       return array[0];
     }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
new file mode 100644
index 0000000..5ad8f6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
@@ -0,0 +1,81 @@
+// 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.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayInSameCatchRangeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayInSameCatchRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      try {
+        int[] array = new int[2];
+        array[0] = value;
+        array[1] = value;
+        return System.nanoTime() > 0 ? array[0] : 2;
+      } catch (RuntimeException e) {
+        return 42;
+      }
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
index ab11b3d..3996647 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.arrays;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,7 +49,15 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
new file mode 100644
index 0000000..40d1557
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
@@ -0,0 +1,89 @@
+// 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.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayMonitorTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayMonitorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayMonitorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(1, filledArrayInstructions.size());
+    InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+    assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
+  }
+
+  static class TestClass {
+
+    public static synchronized int foo() {
+      int value = 1;
+      int[] array = new int[1];
+      try {
+        array[0] = value;
+      } catch (RuntimeException e) {
+        return array[0];
+      }
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
new file mode 100644
index 0000000..d7dc866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
@@ -0,0 +1,85 @@
+// 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.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for issue found in b/259986613
+@RunWith(Parameterized.class)
+public class NewArrayPutInCatchRangeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayPutInCatchRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+    assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      int[] array = new int[1];
+      try {
+        array[0] = value;
+      } catch (RuntimeException e) {
+        return array[0];
+      }
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
new file mode 100644
index 0000000..3319ccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -0,0 +1,84 @@
+// 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.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+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 java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArraySynchronizedBlockTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArraySynchronizedBlockTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArraySynchronizedBlockTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArraySynchronizedBlockTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(0, filledArrayInstructions.size());
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      int[] array;
+      synchronized (TestClass.class) {
+        array = new int[1];
+      } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+      array[0] = value;
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
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..939753f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,774 @@
+// 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]",
+    "[1, 2]",
+    "[1, 2, 3, 4, 5]",
+    "[1]",
+    "[a, 1, null, 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 stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays");
+    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 String before K.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+      assertArrayTypes(stringArrays, DexNewArray.class);
+    } else {
+      assertArrayTypes(stringArrays, DexFilledNewArray.class);
+    }
+    // Cannot use filled-new-array of Object before L.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      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) {
+      stringArrays();
+      referenceArraysNoCasts();
+      referenceArraysWithSubclasses();
+      interfaceArrayWithRawObject();
+      phiFilledNewArray();
+      intsThatUseFilledNewArray();
+      twoDimensionalArrays();
+      objectArraysFilledNewArrayRange();
+      arraysThatUseFilledData();
+      arraysThatUseNewArrayEmpty();
+      reversedArray();
+      arrayWithCorrectCountButIncompleteCoverage();
+      arrayWithExtraInitialPuts();
+      catchHandlerThrowing();
+      catchHandlerNonThrowingFilledNewArray();
+      catchHandlerNonThrowingFillArrayData();
+      arrayIntoAnotherArray();
+      assumedValues();
+    }
+
+    @NeverInline
+    private static void stringArrays() {
+      // Test exact class, no null.
+      String[] stringArr = {"a"};
+      System.out.println(Arrays.toString(stringArr));
+    }
+
+    @NeverInline
+    private static void referenceArraysNoCasts() {
+      // 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() {
+      Object[] 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.
+      Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+      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));
+      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));
+    }
+  }
+}