Aggressively map into InvokeNewArray then eliminate in backend

Bug: b/293501981
Change-Id: I75ea3ad4172ae0564b356de79b331144053bf79e
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index e0e2bd6..c7f8aa2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -401,15 +401,6 @@
     return leadingSquareBrackets;
   }
 
-  public DexType toDimensionMinusOneType(DexItemFactory dexItemFactory) {
-    DexType baseType = toBaseType(dexItemFactory);
-    int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
-    if (leadingSquareBrackets <= 1) {
-      return baseType;
-    }
-    return dexItemFactory.createArrayType(leadingSquareBrackets - 1, baseType);
-  }
-
   public DexType toBaseType(DexItemFactory dexItemFactory) {
     int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
     if (leadingSquareBrackets == 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
index 64305dd..2bf91f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
@@ -52,14 +52,31 @@
   @Override
   public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     // TODO(b/203731608): Add parameters to the method and use abstract value in R8.
-    if (index().isConstant() && !array().isPhi() && array().definition.isNewArrayEmpty()) {
-      Value newArraySizeValue = array().definition.asNewArrayEmpty().size();
-      if (newArraySizeValue.isConstant()) {
-        int newArraySize = newArraySizeValue.getConstInstruction().asConstNumber().getIntValue();
-        int index = index().getConstInstruction().asConstNumber().getIntValue();
-        return newArraySize <= 0 || index < 0 || newArraySize <= index;
+    int arraySize;
+    Value arrayRoot = array().getAliasedValue();
+    if (arrayRoot.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmptyOrInvokeNewArray)) {
+      Instruction definition = arrayRoot.getDefinition();
+      if (definition.isNewArrayEmpty()) {
+        Value newArraySizeValue = definition.asNewArrayEmpty().size();
+        if (newArraySizeValue.isConstant()) {
+          arraySize = newArraySizeValue.getConstInstruction().asConstNumber().getIntValue();
+        } else {
+          return true;
+        }
+      } else {
+        arraySize = definition.asInvokeNewArray().size();
       }
+    } else {
+      return true;
     }
-    return true;
+
+    int index;
+    if (index().isConstant()) {
+      index = index().getConstInstruction().asConstNumber().getIntValue();
+    } else {
+      return true;
+    }
+
+    return arraySize <= 0 || index < 0 || arraySize <= index;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 5daba45..a96638e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -141,31 +141,36 @@
   public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     // Check that the array is guaranteed to be non-null and that the index is within bounds.
     Value array = array().getAliasedValue();
-    if (!array.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+    if (!array.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmptyOrInvokeNewArray)
+        || array.hasLocalInfo()) {
       return true;
     }
 
-    NewArrayEmpty definition = array.definition.asNewArrayEmpty();
-    Value sizeValue = definition.size().getAliasedValue();
-    if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
-      return true;
+    Instruction arrayDefinition = array.getDefinition();
+    int size;
+    if (arrayDefinition.isNewArrayEmpty()) {
+      Value sizeValue = arrayDefinition.asNewArrayEmpty().size();
+      if (sizeValue.isConstant()) {
+        size = sizeValue.getConstInstruction().asConstNumber().getIntValue();
+      } else {
+        return true;
+      }
+    } else {
+      size = arrayDefinition.asInvokeNewArray().size();
     }
 
+    int index;
     Value indexValue = index().getAliasedValue();
-    if (indexValue.isPhi() || !indexValue.definition.isConstNumber()) {
+    if (indexValue.isConstant()) {
+      index = indexValue.getConstInstruction().asConstNumber().getIntValue();
+    } else {
       return true;
     }
 
-    long index = indexValue.definition.asConstNumber().getRawValue();
-    long size = sizeValue.definition.asConstNumber().getRawValue();
     if (index < 0 || index >= size) {
       return true;
     }
 
-    if (array.hasLocalInfo() || indexValue.hasLocalInfo() || sizeValue.hasLocalInfo()) {
-      return true;
-    }
-
     // Check for type errors.
     TypeElement arrayType = array.getType();
     TypeElement valueType = value().getType();
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 588a39a..55882f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1702,27 +1702,27 @@
     return true;
   }
 
-  public InstructionIterator iterator() {
+  public BasicBlockInstructionIterator iterator() {
     return new BasicBlockInstructionIterator(this);
   }
 
-  public InstructionIterator iterator(int index) {
+  public BasicBlockInstructionIterator iterator(int index) {
     return new BasicBlockInstructionIterator(this, index);
   }
 
-  public InstructionIterator iterator(Instruction instruction) {
+  public BasicBlockInstructionIterator iterator(Instruction instruction) {
     return new BasicBlockInstructionIterator(this, instruction);
   }
 
-  public InstructionListIterator listIterator(IRCode code) {
+  public BasicBlockInstructionListIterator listIterator(IRCode code) {
     return listIterator(code.metadata());
   }
 
-  public InstructionListIterator listIterator(IRMetadata metadata) {
+  public BasicBlockInstructionListIterator listIterator(IRMetadata metadata) {
     return new BasicBlockInstructionListIterator(metadata, this);
   }
 
-  public InstructionListIterator listIterator(IRCode code, int index) {
+  public BasicBlockInstructionListIterator listIterator(IRCode code, int index) {
     return new BasicBlockInstructionListIterator(code.metadata(), this, index);
   }
 
@@ -1733,7 +1733,7 @@
    * the returned iterator will return the instruction after <code>instruction</code>. Calling
    * <code>previous</code> will return <code>instruction</code>.
    */
-  public InstructionListIterator listIterator(IRCode code, Instruction instruction) {
+  public BasicBlockInstructionListIterator listIterator(IRCode code, Instruction instruction) {
     return new BasicBlockInstructionListIterator(code.metadata(), this, instruction);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 510ed2d..f0e5789 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -63,6 +63,10 @@
     nextUntil(x -> x == instruction);
   }
 
+  public BasicBlock getBlock() {
+    return block;
+  }
+
   @Override
   public boolean hasNext() {
     return listIterator.hasNext();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index cd79501..4c6632e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -202,6 +202,10 @@
     return result;
   }
 
+  public boolean mayHaveInvokeNewArray() {
+    return get(Opcodes.INVOKE_NEW_ARRAY);
+  }
+
   public boolean mayHaveInvokePolymorphic() {
     return get(Opcodes.INVOKE_POLYMORPHIC);
   }
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 1d63180..581c7c2 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
@@ -35,6 +35,10 @@
     this.type = type;
   }
 
+  public DexType getArrayType() {
+    return type;
+  }
+
   @Override
   public int opcode() {
     return Opcodes.NEW_ARRAY_EMPTY;
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 98d46dd..ade2770 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
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.passes.CodeRewriterPassCollection;
 import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer;
+import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter;
 import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
 import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer;
@@ -786,6 +787,11 @@
 
     previous = printMethod(code, "IR after outline handler (SSA)", previous);
 
+    if (!code.getConversionOptions().isGeneratingLir()) {
+      new FilledNewArrayRewriter(appView)
+          .run(code, methodProcessor, methodProcessingContext, timing);
+    }
+
     if (code.getConversionOptions().isStringSwitchConversionEnabled()) {
       // Remove string switches prior to canonicalization to ensure that the constants that are
       // being introduced will be canonicalized if possible.
@@ -973,6 +979,9 @@
 
   public void removeDeadCodeAndFinalizeIR(
       IRCode code, OptimizationFeedback feedback, Timing timing) {
+    if (!code.getConversionOptions().isGeneratingLir()) {
+      new FilledNewArrayRewriter(appView).run(code, timing);
+    }
     if (stringSwitchRemover != null) {
       stringSwitchRemover.run(code);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index d30217b..5496a1b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.lightir.LirCode;
@@ -334,6 +335,7 @@
       method.setCode(rewrittenLirCode, appView);
     }
     IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
+    new FilledNewArrayRewriter(appView).run(irCode, onThreadTiming);
     // Processing is done and no further uses of the meta-data should arise.
     BytecodeMetadataProvider noMetadata = BytecodeMetadataProvider.empty();
     // During processing optimization info may cause previously live code to become dead.
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
index fb834e4..812abf2 100644
--- 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
@@ -4,11 +4,8 @@
 
 package com.android.tools.r8.ir.conversion.passes;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -18,7 +15,6 @@
 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.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.utils.InternalOptions;
@@ -108,7 +104,7 @@
 
   @Override
   protected boolean shouldRewriteCode(IRCode code) {
-    return appView.options().isGeneratingDex();
+    return true;
   }
 
   private boolean simplifyArrayConstructionBlock(
@@ -133,55 +129,36 @@
       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);
+      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();
+      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);
-        }
+      if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+        info.lastArrayPutIterator.add(invoke);
       } else {
-        assert candidate.useFilledArrayData();
-        int elementSize = arrayType.elementSizeForPrimitiveArrayType();
-        short[] contents = computeArrayFilledData(info.values, size, elementSize);
-        if (contents == null) {
-          continue;
+        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);
         }
-        // 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);
-        }
+        insertionBlock.listIterator(code).add(invoke);
       }
 
       instructionsToRemove.addAll(info.arrayPutsToRemove);
@@ -212,38 +189,6 @@
     return hasChanged;
   }
 
-  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;
@@ -269,7 +214,7 @@
     //   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);
+    DexType elementType = newArrayEmpty.type.toArrayElementType(dexItemFactory);
     boolean needsTypeCheck =
         !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
 
@@ -352,22 +297,11 @@
 
     final NewArrayEmpty newArrayEmpty;
     final int size;
-    final boolean encodeAsFilledNewArray;
 
-    public FilledArrayCandidate(
-        NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+    public FilledArrayCandidate(NewArrayEmpty newArrayEmpty, int size) {
       assert size > 0;
       this.newArrayEmpty = newArrayEmpty;
       this.size = size;
-      this.encodeAsFilledNewArray = encodeAsFilledNewArray;
-    }
-
-    public boolean useFilledNewArray() {
-      return encodeAsFilledNewArray;
-    }
-
-    public boolean useFilledArrayData() {
-      return !useFilledNewArray();
     }
   }
 
@@ -387,67 +321,6 @@
     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);
-      if (!elementType.isClassType()) {
-        return null;
-      }
-      DexProgramClass clazz = null;
-      if (appView.enableWholeProgramOptimizations()) {
-        clazz = asProgramClassOrNull(appView.definitionFor(elementType, code.context()));
-      } else if (elementType == code.context().getHolderType()) {
-        clazz = code.context().getHolder();
-      }
-      if (clazz == null || !clazz.isFinal()) {
-        return null;
-      }
-    }
-    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
-  }
-
-  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();
+    return new FilledArrayCandidate(newArrayEmpty, size);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
new file mode 100644
index 0000000..8423772
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -0,0 +1,323 @@
+// 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.DexType;
+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.BasicBlockInstructionListIterator;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+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.NewArrayFilledData;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
+import com.google.common.collect.Iterables;
+
+public class FilledNewArrayRewriter extends CodeRewriterPass<AppInfo> {
+
+  private final RewriteArrayOptions rewriteArrayOptions;
+
+  private boolean mayHaveRedundantBlocks;
+
+  public FilledNewArrayRewriter(AppView<?> appView) {
+    super(appView);
+    this.rewriteArrayOptions = options.rewriteArrayOptions();
+  }
+
+  @Override
+  protected String getTimingId() {
+    return "FilledNewArrayRemover";
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    BasicBlockIterator blockIterator = code.listIterator();
+    CodeRewriterResult result = noChange();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      BasicBlockInstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (instruction.isInvokeNewArray()) {
+          result =
+              processInstruction(
+                  code, blockIterator, instructionIterator, instruction.asInvokeNewArray(), result);
+        }
+      }
+    }
+    if (mayHaveRedundantBlocks) {
+      code.removeRedundantBlocks();
+    }
+    return result;
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code) {
+    return code.metadata().mayHaveInvokeNewArray();
+  }
+
+  private CodeRewriterResult processInstruction(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      BasicBlockInstructionListIterator instructionIterator,
+      InvokeNewArray invokeNewArray,
+      CodeRewriterResult result) {
+    if (canUseInvokeNewArray(invokeNewArray)) {
+      return result;
+    }
+    if (invokeNewArray.hasUnusedOutValue()) {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    } else if (canUseNewArrayFilledData(invokeNewArray)) {
+      rewriteToNewArrayFilledData(code, blockIterator, instructionIterator, invokeNewArray);
+    } else {
+      rewriteToArrayPuts(code, blockIterator, instructionIterator, invokeNewArray);
+    }
+    return CodeRewriterResult.HAS_CHANGED;
+  }
+
+  private boolean canUseInvokeNewArray(InvokeNewArray invokeNewArray) {
+    if (!options.isGeneratingDex()) {
+      return false;
+    }
+    int size = invokeNewArray.size();
+    if (size < rewriteArrayOptions.minSizeForFilledNewArray) {
+      return false;
+    }
+    // filled-new-array is implemented only for int[] and Object[].
+    DexType arrayType = invokeNewArray.getArrayType();
+    if (arrayType == dexItemFactory.intArrayType) {
+      // 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 (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfInts) {
+        return false;
+      }
+      if (canUseNewArrayFilledData(invokeNewArray)
+          && size
+              > rewriteArrayOptions
+                  .maxSizeForFilledNewArrayOfIntsWhenNewArrayFilledDataApplicable) {
+        return false;
+      }
+      return true;
+    }
+    if (!arrayType.isPrimitiveArrayType()) {
+      if (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfReferences) {
+        return false;
+      }
+      if (arrayType == dexItemFactory.stringArrayType) {
+        return rewriteArrayOptions.canUseFilledNewArrayOfStrings();
+      }
+      if (!rewriteArrayOptions.canUseFilledNewArrayOfNonStringObjects()) {
+        return false;
+      }
+      if (!rewriteArrayOptions.canUseFilledNewArrayOfArrays()
+          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+        return false;
+      }
+      // Check that all arguments to the array is the array type or that the array is type Object[].
+      if (rewriteArrayOptions.canHaveSubTypesInFilledNewArrayBug()
+          && arrayType != dexItemFactory.objectArrayType
+          && !arrayType.isPrimitiveArrayType()) {
+        DexType arrayElementType = arrayType.toArrayElementType(dexItemFactory);
+        for (Value elementValue : invokeNewArray.inValues()) {
+          if (!canStoreElementInInvokeNewArray(elementValue.getType(), arrayElementType)) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+    return false;
+  }
+
+  private boolean canStoreElementInInvokeNewArray(TypeElement valueType, DexType elementType) {
+    if (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 valueType.isClassType(elementType);
+  }
+
+  private boolean canUseNewArrayFilledData(InvokeNewArray invokeNewArray) {
+    // Only convert into NewArrayFilledData when compiling to DEX.
+    if (!appView.options().isGeneratingDex()) {
+      return false;
+    }
+    // If there is only one element it is typically smaller to generate the array put instruction
+    // instead of fill array data.
+    int size = invokeNewArray.size();
+    if (size < rewriteArrayOptions.minSizeForFilledArrayData
+        || size > rewriteArrayOptions.maxSizeForFilledArrayData) {
+      return false;
+    }
+    if (!invokeNewArray.getArrayType().isPrimitiveArrayType()) {
+      return false;
+    }
+    return Iterables.all(invokeNewArray.inValues(), Value::isConstant);
+  }
+
+  private NewArrayEmpty rewriteToNewArrayEmpty(
+      IRCode code,
+      BasicBlockInstructionListIterator instructionIterator,
+      InvokeNewArray invokeNewArray) {
+    // Load the size before the InvokeNewArray instruction.
+    ConstNumber constNumber =
+        ConstNumber.builder()
+            .setFreshOutValue(code, TypeElement.getInt())
+            .setValue(invokeNewArray.size())
+            .setPosition(options.debug ? invokeNewArray.getPosition() : Position.none())
+            .build();
+    instructionIterator.previous();
+    instructionIterator.add(constNumber);
+    Instruction next = instructionIterator.next();
+    assert next == invokeNewArray;
+
+    // Replace the InvokeNewArray instruction by a NewArrayEmpty instruction.
+    NewArrayEmpty newArrayEmpty =
+        new NewArrayEmpty(
+            invokeNewArray.outValue(), constNumber.outValue(), invokeNewArray.getArrayType());
+    instructionIterator.replaceCurrentInstruction(newArrayEmpty);
+    return newArrayEmpty;
+  }
+
+  private void rewriteToNewArrayFilledData(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      BasicBlockInstructionListIterator instructionIterator,
+      InvokeNewArray invokeNewArray) {
+    NewArrayEmpty newArrayEmpty = rewriteToNewArrayEmpty(code, instructionIterator, invokeNewArray);
+
+    // Insert a new NewArrayFilledData instruction after the NewArrayEmpty instruction.
+    short[] contents = computeArrayFilledData(invokeNewArray);
+    NewArrayFilledData newArrayFilledData =
+        new NewArrayFilledData(
+            invokeNewArray.outValue(),
+            invokeNewArray.getArrayType().elementSizeForPrimitiveArrayType(),
+            invokeNewArray.size(),
+            contents);
+    newArrayFilledData.setPosition(invokeNewArray.getPosition());
+    if (newArrayEmpty.getBlock().hasCatchHandlers()) {
+      BasicBlock splitBlock =
+          instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+      splitBlock.listIterator(code).add(newArrayFilledData);
+    } else {
+      instructionIterator.add(newArrayFilledData);
+    }
+  }
+
+  private short[] computeArrayFilledData(InvokeNewArray invokeNewArray) {
+    int elementSize = invokeNewArray.getArrayType().elementSizeForPrimitiveArrayType();
+    int size = invokeNewArray.size();
+    if (elementSize == 1) {
+      short[] result = new short[(size + 1) / 2];
+      for (int i = 0; i < size; i += 2) {
+        ConstNumber constNumber =
+            invokeNewArray.getOperand(i).getConstInstruction().asConstNumber();
+        short value = (short) (constNumber.getIntValue() & 0xFF);
+        if (i + 1 < size) {
+          ConstNumber nextConstNumber =
+              invokeNewArray.getOperand(i + 1).getConstInstruction().asConstNumber();
+          value |= (short) ((nextConstNumber.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++) {
+      ConstNumber constNumber = invokeNewArray.getOperand(i).getConstInstruction().asConstNumber();
+      for (int part = 0; part < shortsPerConstant; part++) {
+        result[i * shortsPerConstant + part] =
+            (short) ((constNumber.getRawValue() >> (16 * part)) & 0xFFFFL);
+      }
+    }
+    return result;
+  }
+
+  private void rewriteToArrayPuts(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      BasicBlockInstructionListIterator instructionIterator,
+      InvokeNewArray invokeNewArray) {
+    NewArrayEmpty newArrayEmpty = rewriteToNewArrayEmpty(code, instructionIterator, invokeNewArray);
+    int index = 0;
+    for (Value elementValue : invokeNewArray.inValues()) {
+      if (instructionIterator.getBlock().hasCatchHandlers()) {
+        BasicBlock splitBlock =
+            instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+        instructionIterator = splitBlock.listIterator(code);
+        addArrayPut(code, instructionIterator, newArrayEmpty, index, elementValue);
+        blockIterator.positionAfterPreviousBlock(splitBlock);
+        mayHaveRedundantBlocks = true;
+      } else {
+        addArrayPut(code, instructionIterator, newArrayEmpty, index, elementValue);
+      }
+      index++;
+    }
+  }
+
+  private void addArrayPut(
+      IRCode code,
+      BasicBlockInstructionListIterator instructionIterator,
+      NewArrayEmpty newArrayEmpty,
+      int index,
+      Value elementValue) {
+    // Load the array index before the ArrayPut instruction.
+    ConstNumber constNumber =
+        ConstNumber.builder()
+            .setFreshOutValue(code, TypeElement.getInt())
+            .setValue(index)
+            .setPosition(options.debug ? newArrayEmpty.getPosition() : Position.none())
+            .build();
+    instructionIterator.add(constNumber);
+
+    // Add the ArrayPut instruction.
+    DexType arrayElementType = newArrayEmpty.getArrayType().toArrayElementType(dexItemFactory);
+    MemberType memberType = MemberType.fromDexType(arrayElementType);
+    ArrayPut arrayPut =
+        ArrayPut.create(memberType, newArrayEmpty.outValue(), constNumber.outValue(), elementValue);
+    arrayPut.setPosition(newArrayEmpty.getPosition());
+    instructionIterator.add(arrayPut);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index b950826..77f5885 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -237,7 +237,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         ensureConversionMethod(
-            type.toDimensionMinusOneType(factory),
+            type.toArrayElementType(factory),
             srcType == type,
             null,
             eventConsumer,
@@ -256,7 +256,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         getExistingProgramConversionMethod(
-            type.toDimensionMinusOneType(factory),
+            type.toArrayElementType(factory),
             srcType == type,
             null,
             eventConsumer,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
index c267943..1e5bf89 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -443,7 +443,7 @@
     }
     DexType ct1ElementType = null;
     if (ct1Type.isArrayType()) {
-      ct1ElementType = ct1Type.toDimensionMinusOneType(factory);
+      ct1ElementType = ct1Type.toArrayElementType(factory);
       if (ct1ElementType != factory.intType
           && ct1ElementType != factory.longType
           && !ct1ElementType.isReferenceType()) {
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 4ca2bb2..78712ee 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
@@ -595,6 +595,13 @@
             markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
           }
         }
+      } else if (use.isInvokeNewArray()) {
+        DexProgramClass enumClass =
+            getEnumUnboxingCandidateOrNull(
+                use.asInvokeNewArray().getArrayType().toBaseType(factory));
+        if (enumClass != null) {
+          eligibleEnums.add(enumClass.getType());
+        }
       } else if (use.isFieldPut()) {
         DexProgramClass enumClass =
             getEnumUnboxingCandidateOrNull(use.asFieldInstruction().getField().getType());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index bf25b2d..e000f6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.graph.AppView;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 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.MemberType;
@@ -188,6 +190,8 @@
               block,
               iterator,
               instruction.asInvokeMethodWithReceiver());
+        } else if (instruction.isInvokeNewArray()) {
+          rewriteInvokeNewArray(instruction.asInvokeNewArray(), code, iterator);
         } else if (instruction.isInvokeStatic()) {
           rewriteInvokeStatic(
               instruction.asInvokeStatic(),
@@ -479,6 +483,33 @@
     }
   }
 
+  private void rewriteInvokeNewArray(
+      InvokeNewArray invokeNewArray, IRCode code, InstructionListIterator instructionIterator) {
+    DexType arrayBaseType = invokeNewArray.getArrayType().toBaseType(factory);
+    if (!unboxedEnumsData.isUnboxedEnum(arrayBaseType)) {
+      return;
+    }
+    DexType rewrittenArrayType =
+        invokeNewArray.getArrayType().replaceBaseType(factory.intType, factory);
+    List<Value> elements = new ArrayList<>(invokeNewArray.inValues().size());
+    Value zeroValue = null;
+    for (Value element : invokeNewArray.inValues()) {
+      if (element.getType().isNullType()) {
+        if (zeroValue == null) {
+          zeroValue = instructionIterator.insertConstIntInstruction(code, options, 0);
+        }
+        elements.add(zeroValue);
+      } else {
+        elements.add(element);
+      }
+    }
+    instructionIterator.replaceCurrentInstruction(
+        new InvokeNewArray(
+            rewrittenArrayType,
+            code.createValue(factory.intArrayType.toTypeElement(appView, definitelyNotNull())),
+            elements));
+  }
+
   private void rewriteInvokeStatic(
       InvokeStatic invoke,
       IRCode code,
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 0360999..1c2251e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1518,8 +1518,9 @@
     // 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 maxSizeForFilledNewArrayOfInts = 200;
+    public int maxSizeForFilledNewArrayOfIntsWhenNewArrayFilledDataApplicable = 5;
     public int maxSizeForFilledNewArrayOfReferences = 200;
-    public int maxSizeForFilledNewArrayOfInts = 5;
 
     // Arbitrary limits of number of inputs to fill-array-data.
     public int minSizeForFilledArrayData = 2;
@@ -1553,9 +1554,9 @@
 
     // When adding support for emitting filled-new-array for sub-types, ART 13 (Api-level 33) had
     // issues. See b/283715197.
-    public boolean canUseSubTypesInFilledNewArray() {
+    public boolean canHaveSubTypesInFilledNewArrayBug() {
       assert isGeneratingDex();
-      return !canHaveBugPresentUntilInclusive(AndroidApiLevel.U);
+      return canHaveBugPresentUntilInclusive(AndroidApiLevel.U);
     }
 
     // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
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 7f9c7bd..e9ff30c 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier;
+import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -22,22 +23,21 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class ArrayWithDataLengthRewriteTest extends TestBase {
+
+  private static final String[] expectedOutput = {"3", "2"};
+
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
-  private final TestParameters parameters;
-
-  public ArrayWithDataLengthRewriteTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private static final String[] expectedOutput = {"3", "2"};
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Test
   public void d8() throws Exception {
@@ -65,7 +65,9 @@
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-    new ArrayConstructionSimplifier(appView).run(irCode, Timing.empty());
+    Timing timing = Timing.empty();
+    new ArrayConstructionSimplifier(appView).run(irCode, timing);
+    new FilledNewArrayRewriter(appView).run(irCode, timing);
     String name = irCode.context().getReference().getName().toString();
     if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
diff --git a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
index 53794e3..3f7aba6 100644
--- a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
+++ b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
@@ -6,13 +6,14 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -43,7 +44,7 @@
         .release()
         .setMinApi(parameters)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .apply(
             compileResult ->
                 compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors())
@@ -65,17 +66,18 @@
             parameters.isDexRuntime(),
             compileResult ->
                 compileResult
-                    .inspect(this::inspect)
+                    .inspect(inspector -> inspect(inspector, false))
                     .runDex2Oat(parameters.getRuntime())
                     .assertNoVerificationErrors())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean isD8) {
     MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
     assertThat(mainMethodSubject, isPresent());
-    assertFalse(
+    assertEquals(
+        isD8 && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
         mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isFilledNewArray));
   }