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));
}