Split ArrayConstructionSimplifier into a CodeRewriterPass

Bug: b/284304606
Change-Id: I4f645ada87e3dff8d069e6d7f4dc570136a0844f
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 688e1f4..5cc5c1c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier;
 import com.android.tools.r8.ir.conversion.passes.BinopRewriter;
 import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
 import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination;
@@ -752,9 +753,7 @@
       timing.end();
     }
     commonSubexpressionElimination.run(context, code, timing);
-    timing.begin("Simplify arrays");
-    codeRewriter.simplifyArrayConstruction(code);
-    timing.end();
+    new ArrayConstructionSimplifier(appView).run(context, code, timing);
     timing.begin("Rewrite move result");
     codeRewriter.rewriteMoveResult(code);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
new file mode 100644
index 0000000..bc8f8fb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -0,0 +1,474 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 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 class ArrayConstructionSimplifier extends CodeRewriterPass<AppInfo> {
+
+  private final DexItemFactory dexItemFactory;
+
+  public ArrayConstructionSimplifier(AppView<?> appView) {
+    super(appView);
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  String getTimingId() {
+    return "ArrayConstructionSimplifier";
+  }
+
+  @Override
+  void rewriteCode(ProgramMethod method, IRCode code) {
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+    while (worklist.hasNext()) {
+      BasicBlock block = worklist.next();
+      simplifyArrayConstructionBlock(block, worklist, code, appView.options());
+    }
+  }
+
+  @Override
+  boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+    return appView.options().isGeneratingDex();
+  }
+
+  private void simplifyArrayConstructionBlock(
+      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
+    RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+    InstructionListIterator it = block.listIterator(code);
+    while (it.hasNext()) {
+      FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
+      if (candidate == null) {
+        continue;
+      }
+      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 = block.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());
+        BasicBlock newBlock =
+            it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
+        if (newBlock != null) {
+          worklist.addIfNotSeen(newBlock);
+        }
+      }
+
+      instructionsToRemove.addAll(info.arrayPutsToRemove);
+      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+      for (Instruction instruction : instructionsToRemove) {
+        BasicBlock ownerBlock = instruction.getBlock();
+        // If owner block is null, then the instruction has been removed already. We can't rely on
+        // just having the block pointer nulled, so the visited blocks guards reprocessing.
+        if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
+          InstructionListIterator removeIt = ownerBlock.listIterator(code);
+          while (removeIt.hasNext()) {
+            if (instructionsToRemove.contains(removeIt.next())) {
+              removeIt.removeOrReplaceByDebugLocalRead();
+            }
+          }
+        }
+      }
+
+      // The above has invalidated the block iterator so reset it and continue.
+      it = block.listIterator(code, instructionAfterCandidate);
+    }
+  }
+
+  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].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
+        if (i + 1 < size) {
+          value |=
+              (short)
+                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
+        }
+        result[i / 2] = value;
+      }
+      return result;
+    }
+    assert elementSize == 2 || elementSize == 4 || elementSize == 8;
+    int shortsPerConstant = elementSize / 2;
+    short[] result = new short[size * shortsPerConstant];
+    for (int i = 0; i < size; i++) {
+      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
+      for (int part = 0; part < shortsPerConstant; part++) {
+        result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
+      }
+    }
+    return result;
+  }
+
+  private static class FilledArrayConversionInfo {
+
+    Value[] values;
+    List<ArrayPut> arrayPutsToRemove;
+    LinearFlowInstructionListIterator lastArrayPutIterator;
+
+    public FilledArrayConversionInfo(int size) {
+      values = new Value[size];
+      arrayPutsToRemove = new ArrayList<>(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 = 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.
+      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;
+        }
+        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;
+        } 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;
+          }
+        }
+      }
+      info.arrayPutsToRemove.add(arrayPut);
+      values[index] = value;
+      --remaining;
+      if (remaining == 0) {
+        info.lastArrayPutIterator = it;
+        return info;
+      }
+    }
+    return null;
+  }
+
+  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 computeFilledArrayCandidate(
+      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, options)) {
+      return null;
+    }
+    // Check that all arguments to the array is the array type or that the array is type Object[].
+    if (!options.canUseSubTypesInFilledNewArray()
+        && arrayType != dexItemFactory.objectArrayType
+        && !arrayType.isPrimitiveArrayType()) {
+      DexType elementType = arrayType.toArrayElementType(dexItemFactory);
+      for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
+        if (uniqueUser.isArrayPut()
+            && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue()
+            && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) {
+          return null;
+        }
+      }
+    }
+    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+  }
+
+  private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) {
+    TypeElement valueType = arrayPut.value().getType();
+    if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) {
+      return true;
+    }
+    if (valueType.isNullType() && !elementType.isPrimitiveType()) {
+      return true;
+    }
+    if (elementType.isArrayType()) {
+      if (valueType.isNullType()) {
+        return true;
+      }
+      ArrayTypeElement arrayTypeElement = valueType.asArrayType();
+      if (arrayTypeElement == null
+          || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
+        return false;
+      }
+      valueType = arrayTypeElement.getBaseType();
+      elementType = elementType.toBaseType(dexItemFactory);
+    }
+    assert !valueType.isArrayType();
+    assert !elementType.isArrayType();
+    if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) {
+      return false;
+    }
+    if (valueType.isPrimitiveType()) {
+      return true;
+    }
+    DexClass clazz = appView.definitionFor(elementType);
+    if (clazz == null) {
+      return false;
+    }
+    return clazz.isInterface() || valueType.isClassType(elementType);
+  }
+
+  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+    if (size < options.minSizeForFilledNewArray) {
+      return false;
+    }
+    // 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;
+    }
+    if (arrayType == dexItemFactory.intArrayType) {
+      return size <= options.maxSizeForFilledNewArrayOfInts;
+    }
+    return false;
+  }
+
+  private boolean canUseFilledArrayData(DexType arrayType, int size, 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;
+    }
+    return arrayType.isPrimitiveArrayType();
+  }
+}
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 5d0abc3..f931cb2 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
@@ -30,7 +30,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -38,7 +37,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeUtils;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.ArrayLength;
-import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -72,13 +70,9 @@
 import com.android.tools.r8.ir.code.InvokeInterface;
 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.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;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
@@ -93,10 +87,7 @@
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
 import com.android.tools.r8.utils.LazyBox;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -118,7 +109,6 @@
 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.Collections;
 import java.util.HashSet;
@@ -1141,6 +1131,53 @@
     assert code.isConsistentSSA(appView);
   }
 
+  public void rewriteKnownArrayLengthCalls(IRCode code) {
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (!current.isArrayLength()) {
+        continue;
+      }
+
+      ArrayLength arrayLength = current.asArrayLength();
+      if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) {
+        continue;
+      }
+
+      Value array = arrayLength.array().getAliasedValue();
+      if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) {
+        continue;
+      }
+
+      AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
+      if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
+        continue;
+      }
+      Instruction arrayDefinition = array.getDefinition();
+      assert arrayDefinition != null;
+
+      Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
+      if (arrayDefinition.isNewArrayEmpty()) {
+        Value size = arrayDefinition.asNewArrayEmpty().size();
+        arrayLength.outValue().replaceUsers(size);
+        iterator.removeOrReplaceByDebugLocalRead();
+      } else if (arrayDefinition.isNewArrayFilledData()) {
+        long size = arrayDefinition.asNewArrayFilledData().size;
+        if (size > Integer.MAX_VALUE) {
+          continue;
+        }
+        iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
+      } else if (abstractValue.hasKnownArrayLength()) {
+        iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
+      } else {
+        continue;
+      }
+
+      phiUsers.forEach(Phi::removeTrivialPhi);
+    }
+    assert code.isConsistentSSA(appView);
+  }
+
   /**
    * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the
    * instruction to use its own constant that will be defined just before the instruction. This
@@ -1558,431 +1595,6 @@
     }
   }
 
-  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].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
-        if (i + 1 < size) {
-          value |=
-              (short)
-                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
-        }
-        result[i / 2] = value;
-      }
-      return result;
-    }
-    assert elementSize == 2 || elementSize == 4 || elementSize == 8;
-    int shortsPerConstant = elementSize / 2;
-    short[] result = new short[size * shortsPerConstant];
-    for (int i = 0; i < size; i++) {
-      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
-      for (int part = 0; part < shortsPerConstant; part++) {
-        result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
-      }
-    }
-    return result;
-  }
-
-  private static class FilledArrayConversionInfo {
-
-    Value[] values;
-    List<ArrayPut> arrayPutsToRemove;
-    LinearFlowInstructionListIterator lastArrayPutIterator;
-
-    public FilledArrayConversionInfo(int size) {
-      values = new Value[size];
-      arrayPutsToRemove = new ArrayList<>(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 = 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.
-      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;
-        }
-        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;
-        } 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;
-          }
-        }
-      }
-      info.arrayPutsToRemove.add(arrayPut);
-      values[index] = value;
-      --remaining;
-      if (remaining == 0) {
-        info.lastArrayPutIterator = it;
-        return info;
-      }
-    }
-    return null;
-  }
-
-  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 computeFilledArrayCandidate(
-      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, options)) {
-      return null;
-    }
-    // Check that all arguments to the array is the array type or that the array is type Object[].
-    if (!options.canUseSubTypesInFilledNewArray()
-        && arrayType != dexItemFactory.objectArrayType
-        && !arrayType.isPrimitiveArrayType()) {
-      DexType elementType = arrayType.toArrayElementType(dexItemFactory);
-      for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
-        if (uniqueUser.isArrayPut()
-            && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue()
-            && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) {
-          return null;
-        }
-      }
-    }
-    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
-  }
-
-  private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) {
-    TypeElement valueType = arrayPut.value().getType();
-    if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) {
-      return true;
-    }
-    if (valueType.isNullType() && !elementType.isPrimitiveType()) {
-      return true;
-    }
-    if (elementType.isArrayType()) {
-      if (valueType.isNullType()) {
-        return true;
-      }
-      ArrayTypeElement arrayTypeElement = valueType.asArrayType();
-      if (arrayTypeElement == null
-          || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
-        return false;
-      }
-      valueType = arrayTypeElement.getBaseType();
-      elementType = elementType.toBaseType(dexItemFactory);
-    }
-    assert !valueType.isArrayType();
-    assert !elementType.isArrayType();
-    if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) {
-      return false;
-    }
-    if (valueType.isPrimitiveType()) {
-      return true;
-    }
-    DexClass clazz = appView.definitionFor(elementType);
-    if (clazz == null) {
-      return false;
-    }
-    return clazz.isInterface() || valueType.isClassType(elementType);
-  }
-
-  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
-    if (size < options.minSizeForFilledNewArray) {
-      return false;
-    }
-    // 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;
-    }
-    if (arrayType == dexItemFactory.intArrayType) {
-      return size <= options.maxSizeForFilledNewArrayOfInts;
-    }
-    return false;
-  }
-
-  private boolean canUseFilledArrayData(DexType arrayType, int size, 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;
-    }
-    return arrayType.isPrimitiveArrayType();
-  }
-
-  /**
-   * 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;
-    }
-    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
-    while (worklist.hasNext()) {
-      BasicBlock block = worklist.next();
-      simplifyArrayConstructionBlock(block, worklist, code, options);
-    }
-  }
-
-  private void simplifyArrayConstructionBlock(
-      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
-    RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
-    InstructionListIterator it = block.listIterator(code);
-    while (it.hasNext()) {
-      FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
-      if (candidate == null) {
-        continue;
-      }
-      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 = block.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());
-        BasicBlock newBlock =
-            it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
-        if (newBlock != null) {
-          worklist.addIfNotSeen(newBlock);
-        }
-      }
-
-      instructionsToRemove.addAll(info.arrayPutsToRemove);
-      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-      for (Instruction instruction : instructionsToRemove) {
-        BasicBlock ownerBlock = instruction.getBlock();
-        // If owner block is null, then the instruction has been removed already. We can't rely on
-        // just having the block pointer nulled, so the visited blocks guards reprocessing.
-        if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
-          InstructionListIterator removeIt = ownerBlock.listIterator(code);
-          while (removeIt.hasNext()) {
-            if (instructionsToRemove.contains(removeIt.next())) {
-              removeIt.removeOrReplaceByDebugLocalRead();
-            }
-          }
-        }
-      }
-
-      // The above has invalidated the block iterator so reset it and continue.
-      it = block.listIterator(code, instructionAfterCandidate);
-    }
-  }
-
   // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
   private static boolean hasLocalOrLineChangeBetween(
       Instruction from, Instruction to, DexString localVar) {
@@ -2552,53 +2164,6 @@
     assert block.exit().asGoto().getTarget() == target;
   }
 
-  public void rewriteKnownArrayLengthCalls(IRCode code) {
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (!current.isArrayLength()) {
-        continue;
-      }
-
-      ArrayLength arrayLength = current.asArrayLength();
-      if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) {
-        continue;
-      }
-
-      Value array = arrayLength.array().getAliasedValue();
-      if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) {
-        continue;
-      }
-
-      AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
-      if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
-        continue;
-      }
-      Instruction arrayDefinition = array.getDefinition();
-      assert arrayDefinition != null;
-
-      Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
-      if (arrayDefinition.isNewArrayEmpty()) {
-        Value size = arrayDefinition.asNewArrayEmpty().size();
-        arrayLength.outValue().replaceUsers(size);
-        iterator.removeOrReplaceByDebugLocalRead();
-      } else if (arrayDefinition.isNewArrayFilledData()) {
-        long size = arrayDefinition.asNewArrayFilledData().size;
-        if (size > Integer.MAX_VALUE) {
-          continue;
-        }
-        iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
-      } else if (abstractValue.hasKnownArrayLength()) {
-        iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
-      } else {
-        continue;
-      }
-
-      phiUsers.forEach(Phi::removeTrivialPhi);
-    }
-    assert code.isConsistentSSA(appView);
-  }
-
   /**
    * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
    * due to debug local info needing a particular value and the live-interval for it then moves it
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 bcacbe7..a09de37 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
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -64,7 +64,7 @@
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-      new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+    new ArrayConstructionSimplifier(appView).run(irCode.context(), irCode);
     String name = irCode.context().getReference().getName().toString();
     if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));