Reland #3: "Use new-filled-array for more things."
This reverts commit b21566f8b8a0308e7c89da9bfb436d416e33ec38.
Bug: b/246971330
Change-Id: I8fd974407b5cf21976bd5af9448e086b42f23fd3
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 30c3b67..2a543a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -29,6 +30,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
@@ -37,6 +39,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
public class StaticFieldValueAnalysis extends FieldValueAnalysis {
@@ -210,7 +213,7 @@
if (value.isPhi()) {
return null;
}
- if (value.definition.isNewArrayEmpty()) {
+ if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
return computeSingleEnumFieldValueForValuesArray(value);
}
if (value.definition.isNewInstance()) {
@@ -220,7 +223,7 @@
}
private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
- if (!value.definition.isNewArrayEmpty()) {
+ if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
return null;
}
AbstractValue valuesValue = computedValues.get(value);
@@ -241,26 +244,38 @@
}
private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
- assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
-
NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
- if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+ InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
+ assert newArrayEmpty != null || invokeNewArray != null;
+
+ DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
+ if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
return null;
}
if (value.hasDebugUsers() || value.hasPhiUsers()) {
return null;
}
- if (!newArrayEmpty.size().isConstNumber()) {
- return null;
- }
- int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- if (valuesSize == 0) {
- // No need to compute the state of an empty array.
+ int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
+ if (valuesSize < 1) {
+ // Array is empty or non-const size.
return null;
}
ObjectState[] valuesState = new ObjectState[valuesSize];
+
+ if (invokeNewArray != null) {
+ // Populate array values from filled-new-array values.
+ List<Value> inValues = invokeNewArray.inValues();
+ for (int i = 0; i < valuesSize; ++i) {
+ if (!updateEnumValueState(valuesState, i, inValues.get(i))) {
+ return null;
+ }
+ }
+ }
+
+ // Populate / update array values from aput-object instructions, and find the static-put
+ // instruction.
DexEncodedField valuesField = null;
for (Instruction user : value.aliasedUsers()) {
switch (user.opcode()) {
@@ -276,18 +291,9 @@
if (index < 0 || index >= valuesSize) {
return null;
}
- ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
- if (objectState == null || objectState.isEmpty()) {
- // We need the state of all fields for the analysis to be valuable.
+ if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
return null;
}
- if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
- return null;
- }
- if (valuesState[index] != null) {
- return null;
- }
- valuesState[index] = objectState;
break;
case ASSUME:
@@ -328,24 +334,34 @@
.createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
}
- private ObjectState computeEnumInstanceObjectState(Value value) {
+ private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
Value root = value.getAliasedValue();
if (root.isPhi()) {
- return ObjectState.empty();
+ return false;
}
Instruction definition = root.getDefinition();
- if (definition.isNewInstance()) {
- return computeObjectState(definition.outValue());
- }
if (definition.isStaticGet()) {
// Enums with many instance rely on staticGets to set the $VALUES data instead of directly
// keeping the values in registers, due to the max capacity of the redundant field load
// elimination. The capacity has already been increased, so that this case is extremely
// uncommon (very large enums).
// TODO(b/169050248): We could consider analysing these to answer the object state here.
- return ObjectState.empty();
+ return false;
}
- return ObjectState.empty();
+ ObjectState objectState =
+ definition.isNewInstance() ? computeObjectState(definition.outValue()) : null;
+ if (objectState == null || objectState.isEmpty()) {
+ // We need the state of all fields for the analysis to be valuable.
+ return false;
+ }
+ if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+ return false;
+ }
+ if (valuesState[index] != null) {
+ return false;
+ }
+ valuesState[index] = objectState;
+ return true;
}
private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 74a3fb5..104b854 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
@@ -46,6 +47,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -316,17 +318,36 @@
Value sizeValue =
instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
Value newObjectsValue = code.createValue(objectArrayType);
- instructionIterator.add(
- new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
// Populate the `objects` array.
- for (int i = 0; i < objects.size(); i++) {
- Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
- Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
- instructionIterator.add(materializingInstruction);
+ var rewriteOptions = appView.options().rewriteArrayOptions();
+ if (rewriteOptions.canUseFilledNewArrayOfNonStringObjects()
+ && objects.size() < rewriteOptions.maxSizeForFilledNewArrayOfReferences) {
+ List<Value> arrayValues = new ArrayList<>(objects.size());
+ for (int i = 0; i < objects.size(); i++) {
+ Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+ instructionIterator.add(materializingInstruction);
+ arrayValues.add(materializingInstruction.outValue());
+ }
instructionIterator.add(
- new ArrayPut(
- MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
+ new InvokeNewArray(
+ appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
+ } else {
+ instructionIterator.add(
+ new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
+
+ for (int i = 0; i < objects.size(); i++) {
+ Value indexValue =
+ instructionIterator.insertConstIntInstruction(code, appView.options(), i);
+ Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+ instructionIterator.add(materializingInstruction);
+ instructionIterator.add(
+ new ArrayPut(
+ MemberType.OBJECT,
+ newObjectsValue,
+ indexValue,
+ materializingInstruction.outValue()));
+ }
}
// Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index fbe3ff5..00182d3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.StaticGet;
@@ -302,18 +303,30 @@
*/
private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
Value objectsValue) throws InvalidRawMessageInfoException {
- if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
+ if (objectsValue.isPhi()) {
throw new InvalidRawMessageInfoException();
}
NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
- int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
+ InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
- // Verify that the size is correct.
+ if (newArrayEmpty == null && invokeNewArray == null) {
+ throw new InvalidRawMessageInfoException();
+ }
+ // Verify that the array is used in only one spot.
+ if (invokeNewArray != null) {
+ if (!objectsValue.hasSingleUniqueUser()) {
+ throw new InvalidRawMessageInfoException();
+ }
+ return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
+ }
+
Value sizeValue = newArrayEmpty.size().getAliasedValue();
- if (sizeValue.isPhi()
- || !sizeValue.definition.isConstNumber()
- || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
+ if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
+ throw new InvalidRawMessageInfoException();
+ }
+ int arraySize = sizeValue.definition.asConstNumber().getIntValue();
+ if (arraySize != objectsValue.uniqueUsers().size() - 1) {
throw new InvalidRawMessageInfoException();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index e06cdbee..99f92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,20 +73,25 @@
* <p>Use with caution!
*/
public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) {
- Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
if (definition.isConstNumber()) {
// No need to explicitly remove `null`, it will be removed by ordinary dead code elimination
// anyway.
assert definition.asConstNumber().isZero();
return;
}
-
- if (definition.isNewArrayEmpty()) {
- Value arrayValue = definition.outValue();
- if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
- return;
- }
-
+ Value arrayValue = definition.outValue();
+ if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+ return;
+ }
+ if (!definition.isNewArrayEmptyOrInvokeNewArray()) {
+ assert false;
+ return;
+ }
+ Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
+ InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+ if (invokeNewArray != null) {
+ worklist.add(definition);
+ } else if (definition.isNewArrayEmpty()) {
for (Instruction user : arrayValue.uniqueUsers()) {
// If we encounter an Assume instruction here, we also need to consider indirect users.
assert !user.isAssume();
@@ -95,11 +100,10 @@
}
worklist.add(user);
}
- internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
- return;
+ } else {
+ assert false;
}
-
- assert false;
+ internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c99ee11..8892da0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1038,6 +1038,10 @@
return false;
}
+ public boolean isNewArrayEmptyOrInvokeNewArray() {
+ return isNewArrayEmpty() || isInvokeNewArray();
+ }
+
public NewArrayEmpty asNewArrayEmpty() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2e815f..f0715b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -221,4 +221,9 @@
void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
registry.registerTypeReference(type);
}
+
+ // Returns the number of elements in the array.
+ public int size() {
+ return inValues.size();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c6d527f..a829247 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -49,10 +49,6 @@
return super.toString() + " " + type.toString();
}
- public Value dest() {
- return outValue;
- }
-
public Value size() {
return inValues.get(0);
}
@@ -60,7 +56,7 @@
@Override
public void buildDex(DexBuilder builder) {
int size = builder.allocatedRegister(size(), getNumber());
- int dest = builder.allocatedRegister(dest(), getNumber());
+ int dest = builder.allocatedRegister(outValue, getNumber());
builder.add(this, new DexNewArray(dest, size, type));
}
@@ -171,4 +167,10 @@
void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
registry.registerTypeReference(type);
}
+
+ // Returns the size of the array if it is known, -1 otherwise.
+ public int sizeIfConst() {
+ Value size = size();
+ return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 23f0de9..d7e5fc1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.algorithms.scc.SCC;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
@@ -55,7 +54,6 @@
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DebugLocalWrite;
@@ -84,6 +82,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -107,9 +106,12 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
@@ -138,10 +140,10 @@
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -162,7 +164,6 @@
FALSE
}
- private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
// This constant was determined by experimentation.
private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
@@ -2101,16 +2102,21 @@
}
}
- private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
- if (values == null) {
- return null;
+ private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+ for (Value v : values) {
+ if (!v.isConstant()) {
+ return null;
+ }
}
if (elementSize == 1) {
short[] result = new short[(size + 1) / 2];
for (int i = 0; i < size; i += 2) {
- short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
+ short value =
+ (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
if (i + 1 < size) {
- value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
+ value |=
+ (short)
+ ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
}
result[i / 2] = value;
}
@@ -2120,7 +2126,7 @@
int shortsPerConstant = elementSize / 2;
short[] result = new short[size * shortsPerConstant];
for (int i = 0; i < size; i++) {
- long value = values[i].asConstNumber().getRawValue();
+ long value = values[i].getConstInstruction().asConstNumber().getRawValue();
for (int part = 0; part < shortsPerConstant; part++) {
result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
}
@@ -2128,185 +2134,354 @@
return result;
}
- private ConstInstruction[] computeConstantArrayValues(
- NewArrayEmpty newArray, BasicBlock block, int size) {
- if (size > MAX_FILL_ARRAY_SIZE) {
- return null;
+ private static class FilledArrayConversionInfo {
+
+ Value[] values;
+ List<ArrayPut> arrayPutsToRemove;
+ LinearFlowInstructionListIterator lastArrayPutIterator;
+
+ public FilledArrayConversionInfo(int size) {
+ values = new Value[size];
+ arrayPutsToRemove = new ArrayList<>(size);
}
- ConstInstruction[] values = new ConstInstruction[size];
+ }
+
+ private FilledArrayConversionInfo computeConversionInfo(
+ FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
+ NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+ assert it.peekPrevious() == newArrayEmpty;
+ Value arrayValue = newArrayEmpty.outValue();
+ int size = candidate.size;
+
+ // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+ // if types require a cast.
+ // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+ // if aput-object throws a ClassCastException if given an object that does not implement the
+ // desired interface, then we could add check-cast instructions for arguments we're not sure
+ // about.
+ DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+ boolean needsTypeCheck =
+ !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+ FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
+ Value[] values = info.values;
int remaining = size;
- Set<Instruction> users = newArray.outValue().uniqueUsers();
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
- // an instruction instance that can throw an exception.
- InstructionIterator it = block.iterator();
- it.nextUntil(i -> i == newArray);
- do {
- visitedBlocks.add(block);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- // If we encounter an instruction that can throw an exception we need to bail out of the
- // optimization so that we do not transform half-initialized arrays into fully initialized
- // arrays on exceptional edges. If the block has no handlers it is not observable so
- // we perform the rewriting.
- if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+ Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ BasicBlock block = instruction.getBlock();
+ // If we encounter an instruction that can throw an exception we need to bail out of the
+ // optimization so that we do not transform half-initialized arrays into fully initialized
+ // arrays on exceptional edges. If the block has no handlers it is not observable so
+ // we perform the rewriting.
+ // TODO(b/246971330): Allow simplification when all users of the array are in the same
+ // try/catch.
+ if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+ return null;
+ }
+ if (!users.contains(instruction)) {
+ // If any instruction can transfer control between the new-array and the last array put
+ // then it is not safe to move the new array to the point of the last put.
+ if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
return null;
}
- if (!users.contains(instruction)) {
- continue;
- }
- // If the initialization sequence is broken by another use we cannot use a
- // fill-array-data instruction.
- if (!instruction.isArrayPut()) {
+ continue;
+ }
+ ArrayPut arrayPut = instruction.asArrayPut();
+ // If the initialization sequence is broken by another use we cannot use a fill-array-data
+ // instruction.
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ return null;
+ }
+ if (!arrayPut.index().isConstNumber()) {
+ return null;
+ }
+ if (arrayPut.instructionInstanceCanThrow()) {
+ assert false;
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ if (index < 0 || index >= values.length) {
+ return null;
+ }
+ if (values[index] != null) {
+ return null;
+ }
+ Value value = arrayPut.value();
+ if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+ DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+ if (elementType.isArrayType()) {
+ if (elementType != valueDexType) {
+ return null;
+ }
+ } else if (valueDexType.isArrayType()) {
+ // isSubtype asserts for this case.
return null;
- }
- ArrayPut arrayPut = instruction.asArrayPut();
- if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
- return null;
- }
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- if (index < 0 || index >= values.length) {
- return null;
- }
- if (values[index] != null) {
- return null;
- }
- ConstInstruction value = arrayPut.value().getConstInstruction();
- values[index] = value;
- --remaining;
- if (remaining == 0) {
- return values;
+ } else if (valueDexType.isNullValueType()) {
+ // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
+ // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+ // that hits this case.
+ } else {
+ // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+ // library types (which this helper does not do).
+ if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+ return null;
+ }
}
}
- BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
- block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
- it = block != null ? block.iterator() : null;
- } while (it != null);
+ info.arrayPutsToRemove.add(arrayPut);
+ values[index] = value;
+ --remaining;
+ if (remaining == 0) {
+ info.lastArrayPutIterator = it;
+ return info;
+ }
+ }
return null;
}
- private boolean allowNewFilledArrayConstruction(Instruction instruction) {
- if (!(instruction instanceof NewArrayEmpty)) {
+ private static class FilledArrayCandidate {
+ final NewArrayEmpty newArrayEmpty;
+ final int size;
+ final boolean encodeAsFilledNewArray;
+
+ public FilledArrayCandidate(
+ NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+ assert size > 0;
+ this.newArrayEmpty = newArrayEmpty;
+ this.size = size;
+ this.encodeAsFilledNewArray = encodeAsFilledNewArray;
+ }
+
+ public boolean useFilledNewArray() {
+ return encodeAsFilledNewArray;
+ }
+
+ public boolean useFilledArrayData() {
+ return !useFilledNewArray();
+ }
+ }
+
+ private FilledArrayCandidate computeFilledArrayCandiate(
+ Instruction instruction, RewriteArrayOptions options) {
+ NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+ if (newArrayEmpty == null) {
+ return null;
+ }
+ if (instruction.getLocalInfo() != null) {
+ return null;
+ }
+ if (!newArrayEmpty.size().isConstant()) {
+ return null;
+ }
+ int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ if (!options.isPotentialSize(size)) {
+ return null;
+ }
+ DexType arrayType = newArrayEmpty.type;
+ boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
+ if (!encodeAsFilledNewArray
+ && !canUseFilledArrayData(arrayType, size, instruction.getBlock(), options)) {
+ return null;
+ }
+ return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+ }
+
+ private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+ if (size < options.minSizeForFilledNewArray) {
return false;
}
- NewArrayEmpty newArray = instruction.asNewArrayEmpty();
- if (!newArray.size().isConstant()) {
- return false;
- }
- assert newArray.size().isConstNumber();
- int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- if (size < 1) {
- return false;
- }
- if (newArray.type.isPrimitiveArrayType()) {
+ // filled-new-array is implemented only for int[] and Object[].
+ // For int[], using filled-new-array is usually smaller than filled-array-data.
+ // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+ if (!arrayType.isPrimitiveArrayType()) {
+ if (size > options.maxSizeForFilledNewArrayOfReferences) {
+ return false;
+ }
+ if (arrayType == dexItemFactory.stringArrayType) {
+ return options.canUseFilledNewArrayOfStrings();
+ }
+ if (!options.canUseFilledNewArrayOfNonStringObjects()) {
+ return false;
+ }
+ if (!options.canUseFilledNewArrayOfArrays()
+ && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+ return false;
+ }
return true;
}
- return newArray.type == dexItemFactory.stringArrayType
- && options.canUseFilledNewArrayOfObjects();
+ if (arrayType == dexItemFactory.intArrayType) {
+ return size <= options.maxSizeForFilledNewArrayOfInts;
+ }
+ return false;
+ }
+
+ private boolean canUseFilledArrayData(
+ DexType arrayType, int size, BasicBlock block, RewriteArrayOptions options) {
+ // If there is only one element it is typically smaller to generate the array put
+ // instruction instead of fill array data.
+ if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
+ return false;
+ }
+ if (!arrayType.isPrimitiveArrayType()) {
+ return false;
+ }
+ // NewArrayFilledData can throw, so creating one as done below would add a second
+ // throwing instruction to the same block (the first one being NewArrayEmpty).
+ // TODO(b/246971330): Support splitting the block when inserting the additional instruction.
+ if (block.hasCatchHandlers()) {
+ return false;
+ }
+ return true;
}
/**
- * Replace new-array followed by stores of constants to all entries with new-array
- * and fill-array-data / filled-new-array.
+ * Replace new-array followed by stores of constants to all entries with new-array and
+ * fill-array-data / filled-new-array.
+ *
+ * <p>The format of the new-array and its puts must be of the form:
+ *
+ * <pre>
+ * v0 <- new-array T vSize
+ * ...
+ * array-put v0 vValue1 vIndex1
+ * ...
+ * array-put v0 vValueN vIndexN
+ * </pre>
+ *
+ * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides
+ * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and
+ * also cannot have catch handlers that would transfer out control (those could then have uses of
+ * v0).
+ *
+ * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
+ * remain active on the translated code. Translated code can have two forms.
+ *
+ * <p>The first is using the original array allocation and filling in its data if it can be
+ * encoded:
+ *
+ * <pre>
+ * v0 <- new-array T vSize
+ * filled-array-data v0
+ * ...
+ * ...
+ * </pre>
+ *
+ * The data payload is encoded directly in the instruction so no dependencies are needed for
+ * filling the data array. Thus, the fill is inserted at the point of the allocation. If the
+ * allocation has catch handlers its block must be split and the handlers put on the fill
+ * instruction too. This is correct only because there are no exceptional transfers in (...) that
+ * could observe the early initialization of the data.
+ *
+ * <p>The second is using filled-new-array and has the form:
+ *
+ * <pre>
+ * ...
+ * ...
+ * v0 <- filled-new-array T vValue1 ... vValueN
+ * </pre>
+ *
+ * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
+ * allocation of the array. The late allocation ensures that the values are available at
+ * allocation time. If the original allocation has catch handlers then the new allocation needs to
+ * link those too. In general that may require splitting the block twice so that the new
+ * allocation is the single throwing instruction in its block.
*/
public void simplifyArrayConstruction(IRCode code) {
if (options.isGeneratingClassFiles()) {
return;
}
- for (BasicBlock block : code.blocks) {
- // Map from the array value to the number of array put instruction to remove for that value.
- Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
- Map<Value, Integer> storesToRemoveForArray = new HashMap<>();
- // First pass: identify candidates and insert fill array data instruction.
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
- continue;
- }
- NewArrayEmpty newArray = instruction.asNewArrayEmpty();
- int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
- if (values == null) {
- continue;
- }
- if (newArray.type == dexItemFactory.stringArrayType) {
- // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
- if (size > 200) {
- continue;
- }
- List<Value> stringValues = new ArrayList<>(size);
- for (ConstInstruction value : values) {
- stringValues.add(value.outValue());
- }
- Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
- InvokeNewArray invoke =
- new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
- for (Value value : newArray.inValues()) {
- value.removeUser(newArray);
- }
- newArray.outValue().replaceUsers(invokeValue);
- it.removeOrReplaceByDebugLocalRead();
- instructionToInsertForArray.put(invokeValue, invoke);
- storesToRemoveForArray.put(invokeValue, size);
- } else {
- // If there is only one element it is typically smaller to generate the array put
- // instruction instead of fill array data.
- if (size == 1) {
- continue;
- }
- int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
- short[] contents = computeArrayFilledData(values, size, elementSize);
- if (contents == null) {
- continue;
- }
- if (block.hasCatchHandlers()) {
- continue;
- }
- int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- NewArrayFilledData fillArray =
- new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
- fillArray.setPosition(newArray.getPosition());
- it.add(fillArray);
- storesToRemoveForArray.put(newArray.outValue(), size);
- }
+ WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+ while (worklist.hasNext()) {
+ BasicBlock block = worklist.next();
+ simplifyArrayConstructionBlock(block, worklist, code, options.rewriteArrayOptions());
+ }
+ }
+
+ private void simplifyArrayConstructionBlock(
+ BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, RewriteArrayOptions options) {
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ FilledArrayCandidate candidate = computeFilledArrayCandiate(it.next(), options);
+ if (candidate == null) {
+ continue;
}
- // Second pass: remove all the array put instructions for the array for which we have
- // inserted a fill array data instruction instead.
- if (!storesToRemoveForArray.isEmpty()) {
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- do {
- visitedBlocks.add(block);
- it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isArrayPut()) {
- Value array = instruction.asArrayPut().array();
- Integer toRemoveCount = storesToRemoveForArray.get(array);
- if (toRemoveCount != null) {
- if (toRemoveCount > 0) {
- storesToRemoveForArray.put(array, --toRemoveCount);
- it.remove();
- }
- if (toRemoveCount == 0) {
- storesToRemoveForArray.put(array, --toRemoveCount);
- Instruction construction = instructionToInsertForArray.get(array);
- if (construction != null) {
- // Set the position of the new array construction to be the position of the
- // last removed put at which point we are now adding the construction.
- construction.setPosition(instruction.getPosition());
- it.add(construction);
- }
- }
- }
+ FilledArrayConversionInfo info =
+ computeConversionInfo(
+ candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
+ if (info == null) {
+ continue;
+ }
+
+ Instruction instructionAfterCandidate = it.peekNext();
+ NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+ DexType arrayType = newArrayEmpty.type;
+ int size = candidate.size;
+ Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
+ if (candidate.useFilledNewArray()) {
+ assert newArrayEmpty.getLocalInfo() == null;
+ Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
+ Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
+ InvokeNewArray invoke =
+ new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
+ invoke.setPosition(lastArrayPut.getPosition());
+ for (Value value : newArrayEmpty.inValues()) {
+ value.removeUser(newArrayEmpty);
+ }
+ newArrayEmpty.outValue().replaceUsers(invokeValue);
+ instructionsToRemove.add(newArrayEmpty);
+
+ boolean originalAllocationPointHasHandlers = newArrayEmpty.getBlock().hasCatchHandlers();
+ boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
+
+ if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+ info.lastArrayPutIterator.add(invoke);
+ } else {
+ BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
+ if (originalAllocationPointHasHandlers) {
+ if (!insertionBlock.isTrivialGoto()) {
+ BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
+ assert insertionBlock.isTrivialGoto();
+ worklist.addIfNotSeen(blockAfterInsertion);
+ }
+ insertionBlock.moveCatchHandlers(block);
+ } else {
+ worklist.addIfNotSeen(insertionBlock);
+ }
+ insertionBlock.listIterator(code).add(invoke);
+ }
+ } else {
+ assert candidate.useFilledArrayData();
+ int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+ short[] contents = computeArrayFilledData(info.values, size, elementSize);
+ if (contents == null) {
+ continue;
+ }
+ // fill-array-data requires the new-array-empty instruction to remain, as it does not
+ // itself create an array.
+ NewArrayFilledData fillArray =
+ new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
+ fillArray.setPosition(newArrayEmpty.getPosition());
+ it.add(fillArray);
+ }
+
+ instructionsToRemove.addAll(info.arrayPutsToRemove);
+ Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+ for (Instruction instruction : instructionsToRemove) {
+ BasicBlock ownerBlock = instruction.getBlock();
+ if (visitedBlocks.add(ownerBlock)) {
+ InstructionListIterator removeIt = ownerBlock.listIterator(code);
+ while (removeIt.hasNext()) {
+ if (instructionsToRemove.contains(removeIt.next())) {
+ removeIt.removeOrReplaceByDebugLocalRead();
}
}
- BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
- block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
- } while (block != null);
+ }
}
+
+ // The above has invalidated the block iterator so reset it and continue.
+ it = block.listIterator(code, instructionAfterCandidate);
}
- assert code.isConsistentSSA(appView);
}
// TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a649af6..6fb553c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,6 +18,7 @@
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
@@ -68,6 +69,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
@@ -1060,6 +1062,10 @@
instruction.asInstancePut(), code, context, enumClass, enumValue);
case INVOKE_DIRECT:
case INVOKE_INTERFACE:
+ return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+ case INVOKE_NEW_ARRAY:
+ return analyzeInvokeNewArrayUser(
+ instruction.asInvokeNewArray(), code, context, enumClass, enumValue);
case INVOKE_STATIC:
case INVOKE_SUPER:
case INVOKE_VIRTUAL:
@@ -1130,6 +1136,40 @@
return Reason.INVALID_ARRAY_PUT;
}
+ private Reason analyzeInvokeNewArrayUser(
+ InvokeNewArray invokeNewArray,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid.
+ // We need to prove that the value to put in and the array have correct types.
+ TypeElement arrayType = invokeNewArray.getOutType();
+ assert arrayType.isArrayType();
+
+ ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+ if (arrayBaseType == null) {
+ assert false;
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+ if (arrayBaseType.getClassType() != enumClass.type) {
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+
+ for (Value value : invokeNewArray.inValues()) {
+ TypeElement valueBaseType = value.getType();
+ if (valueBaseType.isArrayType()) {
+ assert valueBaseType.asArrayType().getBaseType().isClassType();
+ assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+ valueBaseType = valueBaseType.asArrayType().getBaseType();
+ }
+ if (!arrayBaseType.equalUpToNullability(valueBaseType)) {
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+ }
+ return Reason.ELIGIBLE;
+ }
+
private Reason analyzeCheckCastUser(
CheckCast checkCast,
IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 3e17a0a..7cb0290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,6 +32,8 @@
new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+ public static final Reason INVALID_INVOKE_NEW_ARRAY =
+ new StringReason("INVALID_INVOKE_NEW_ARRAY");
public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 3e4078b..6e14907 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -424,12 +425,16 @@
// Perform a conservative evaluation of an array content of dex type values from its construction
// until its use at a given instruction.
- private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
- NewArrayEmpty newArray,
- List<CheckCast> aliases,
- int size,
- Instruction user,
- DexItemFactory factory) {
+ private static DexTypeList evaluateTypeArrayContentFromConstructionToUse(
+ NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) {
+ int size = newArray.sizeIfConst();
+ if (size < 0) {
+ return null;
+ } else if (size == 0) {
+ // TODO: We should likely still scan to ensure no ArrayPut instructions exist.
+ return DexTypeList.empty();
+ }
+
DexType[] values = new DexType[size];
int remaining = size;
Set<Instruction> users = Sets.newIdentityHashSet();
@@ -453,7 +458,7 @@
if (instruction == user) {
// Return the array content if all elements are known when hitting the user for which
// the content was requested.
- return remaining == 0 ? values : null;
+ return remaining == 0 ? new DexTypeList(values) : null;
}
// Any other kinds of use besides array-put mean that the array escapes and its content
// could be altered.
@@ -498,6 +503,21 @@
return null;
}
+ private static DexTypeList evaluateTypeArrayContent(
+ InvokeNewArray newArray, DexItemFactory factory) {
+ List<Value> arrayValues = newArray.inValues();
+ int size = arrayValues.size();
+ DexType[] values = new DexType[size];
+ for (int i = 0; i < size; ++i) {
+ DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory);
+ if (type == null) {
+ return null;
+ }
+ values[i] = type;
+ }
+ return new DexTypeList(values);
+ }
+
/**
* Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
* as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -551,30 +571,13 @@
}
// Make sure this Value refers to a new array.
- if (!classListValue.definition.isNewArrayEmpty()
- || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
+ if (classListValue.definition.isNewArrayEmpty()) {
+ return evaluateTypeArrayContentFromConstructionToUse(
+ classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
+ } else if (classListValue.definition.isInvokeNewArray()) {
+ return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
+ } else {
return null;
}
-
- int size =
- classListValue
- .definition
- .asNewArrayEmpty()
- .size()
- .getConstInstruction()
- .asConstNumber()
- .getIntValue();
- if (size == 0) {
- return DexTypeList.empty();
- }
-
- DexType[] arrayContent =
- evaluateTypeArrayContentFromConstructionToUse(
- classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
-
- if (arrayContent == null) {
- return null;
- }
- return new DexTypeList(arrayContent);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 173c302..6c5c07b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -105,7 +105,9 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -5090,25 +5092,43 @@
return;
}
Value parametersValue = constructorDefinition.inValues().get(1);
- if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+ if (parametersValue.isPhi()) {
// Give up, we can't tell which constructor is being invoked.
return;
}
-
- Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
- if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
- // Give up, we can't tell which constructor is being invoked.
+ NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
+ InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
+ int parametersSize =
+ newArrayEmpty != null
+ ? newArrayEmpty.sizeIfConst()
+ : invokeNewArray != null ? invokeNewArray.size() : -1;
+ if (parametersSize < 0) {
return;
}
ProgramMethod initializer = null;
- int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
if (parametersSize == 0) {
initializer = clazz.getProgramDefaultInitializer();
} else {
DexType[] parameterTypes = new DexType[parametersSize];
int missingIndices = parametersSize;
+
+ if (newArrayEmpty != null) {
+ missingIndices = parametersSize;
+ } else {
+ missingIndices = 0;
+ List<Value> values = invokeNewArray.inValues();
+ for (int i = 0; i < parametersSize; ++i) {
+ DexType type =
+ ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView);
+ if (type == null) {
+ return;
+ }
+ parameterTypes[i] = type;
+ }
+ }
+
for (Instruction user : parametersValue.uniqueUsers()) {
if (user.isArrayPut()) {
ArrayPut arrayPutInstruction = user.asArrayPut();
@@ -5170,20 +5190,31 @@
}
Value interfacesValue = invoke.arguments().get(1);
- if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
+ if (interfacesValue.isPhi()) {
// Give up, we can't tell which interfaces the proxy implements.
return;
}
- WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
- for (Instruction user : interfacesValue.uniqueUsers()) {
- if (!user.isArrayPut()) {
- continue;
+ InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray();
+ NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty();
+ List<Value> values;
+ if (invokeNewArray != null) {
+ values = invokeNewArray.inValues();
+ } else if (newArrayEmpty != null) {
+ values = new ArrayList<>(interfacesValue.uniqueUsers().size());
+ for (Instruction user : interfacesValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut != null) {
+ values.add(arrayPut.value());
+ }
}
+ } else {
+ return;
+ }
- ArrayPut arrayPut = user.asArrayPut();
- DexType type =
- ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView);
+ WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+ for (Value value : values) {
+ DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
if (type == null || !type.isClassType()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3b87985..4f8669b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,6 +816,7 @@
public boolean debug = false;
+ private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
private final CallSiteOptimizationOptions callSiteOptimizationOptions =
new CallSiteOptimizationOptions();
private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -850,6 +851,10 @@
public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
+ public RewriteArrayOptions rewriteArrayOptions() {
+ return rewriteArrayOptions;
+ }
+
public CallSiteOptimizationOptions callSiteOptimizationOptions() {
return callSiteOptimizationOptions;
}
@@ -1383,6 +1388,51 @@
System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
}
+ public class RewriteArrayOptions {
+ // Arbitrary limit of number of inputs to new-filled-array/range.
+ // The technical limit is 255 (Constants.U8BIT_MAX).
+ public int minSizeForFilledNewArray = 1;
+ public int maxSizeForFilledNewArrayOfReferences = 200;
+ public int maxSizeForFilledNewArrayOfInts = 5;
+
+ // Arbitrary limits of number of inputs to fill-array-data.
+ public int minSizeForFilledArrayData = 2;
+ public int maxSizeForFilledArrayData = 8 * 1024;
+
+ // Check the most relaxed size range allowed.
+ public boolean isPotentialSize(int size) {
+ return minSizeForFilledNewArray <= size && size <= maxSizeForFilledArrayData;
+ }
+
+ // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
+ // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
+ // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
+ // removed during the jelly-bean release cycle and is not there from kitkat.
+ //
+ // Buggy code that accidentally call code that only works on primitives arrays.
+ //
+ // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
+ public boolean canUseFilledNewArrayOfStrings() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.K);
+ }
+
+ // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues.
+ // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview
+ // It somehow had a new-array-filled return null.
+ public boolean canUseFilledNewArrayOfNonStringObjects() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.N);
+ }
+
+ // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
+ // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer; (dalvikvm)
+ public boolean canUseFilledNewArrayOfArrays() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.L);
+ }
+ }
+
public class CallSiteOptimizationOptions {
private boolean enabled = true;
@@ -2424,19 +2474,6 @@
return hasFeaturePresentFrom(AndroidApiLevel.J);
}
- // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
- // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
- // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
- // removed during the jelly-bean release cycle and is not there from kitkat.
- //
- // Buggy code that accidentally call code that only works on primitives arrays.
- //
- // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
- public boolean canUseFilledNewArrayOfObjects() {
- assert isGeneratingDex();
- return hasFeaturePresentFrom(AndroidApiLevel.K);
- }
-
// Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
// where an aget-wide instruction using the same register for the array
// and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
index 50d23c9..7d41ae1 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -32,4 +33,18 @@
}
return result;
}
+
+ public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) {
+ return new ThrowingIterator<>() {
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public T next() throws E {
+ return it.next();
+ }
+ };
+ }
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 812eb63..b00d78b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,7 +10,9 @@
import com.android.tools.r8.dex.code.DexConst4;
import com.android.tools.r8.dex.code.DexConstClass;
import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.dex.code.DexInvokeVirtual;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
import com.android.tools.r8.dex.code.DexNewArray;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.graph.DexCode;
@@ -93,16 +95,29 @@
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof DexConst4);
- assertTrue(code.instructions[1] instanceof DexNewArray);
- assertTrue(code.instructions[2] instanceof DexConst4);
- assertTrue(code.instructions[3] instanceof DexConstClass);
- assertTrue(code.instructions[4] instanceof DexAputObject);
- assertTrue(code.instructions[5] instanceof DexConstClass);
- assertTrue(code.instructions[6] instanceof DexConstString);
- assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
- assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
- assertTrue(code.instructions[8] instanceof DexReturnVoid);
+
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[1] instanceof DexFilledNewArray) {
+ assertTrue(code.instructions[0] instanceof DexConstClass);
+ assertTrue(code.instructions[1] instanceof DexFilledNewArray);
+ assertTrue(code.instructions[2] instanceof DexMoveResultObject);
+ assertTrue(code.instructions[3] instanceof DexConstClass);
+ assertTrue(code.instructions[4] instanceof DexConstString);
+ assertNotEquals("foo", code.instructions[4].asConstString().getString().toString());
+ assertTrue(code.instructions[5] instanceof DexInvokeVirtual);
+ assertTrue(code.instructions[6] instanceof DexReturnVoid);
+ } else {
+ assertTrue(code.instructions[0] instanceof DexConst4);
+ assertTrue(code.instructions[1] instanceof DexNewArray);
+ assertTrue(code.instructions[2] instanceof DexConst4);
+ assertTrue(code.instructions[3] instanceof DexConstClass);
+ assertTrue(code.instructions[4] instanceof DexAputObject);
+ assertTrue(code.instructions[5] instanceof DexConstClass);
+ assertTrue(code.instructions[6] instanceof DexConstString);
+ assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
+ assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
+ assertTrue(code.instructions[8] instanceof DexReturnVoid);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1e43777..7141ad08 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -53,8 +53,7 @@
.addProgramClasses(I.class, A.class)
.addProgramClassFileData(getTransformedMain())
.addKeepMainRule(Main.class)
- // Keep get() to prevent that we optimize it into having static return type A.
- .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+ .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
.addOptionsModification(
options ->
options
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 83ea780..c544c9e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,10 +16,12 @@
import com.android.tools.r8.dex.code.DexConst4;
import com.android.tools.r8.dex.code.DexConstClass;
import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.dex.code.DexInvokeDirect;
import com.android.tools.r8.dex.code.DexInvokeStatic;
import com.android.tools.r8.dex.code.DexInvokeVirtual;
import com.android.tools.r8.dex.code.DexIputObject;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
import com.android.tools.r8.dex.code.DexNewArray;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.dex.code.DexSgetObject;
@@ -639,7 +641,8 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep class R { *; }");
- CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+ CodeInspector inspector =
+ compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -647,19 +650,33 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- checkInstructions(
- code,
- ImmutableList.of(
- DexInvokeDirect.class,
- DexConst4.class,
- DexNewArray.class,
- DexConst4.class,
- DexConstClass.class,
- DexAputObject.class,
- DexConstString.class,
- DexInvokeStatic.class,
- DexReturnVoid.class));
- DexConstString constString = (DexConstString) code.instructions[6];
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConstClass.class,
+ DexFilledNewArray.class,
+ DexMoveResultObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ } else {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConst4.class,
+ DexNewArray.class,
+ DexConst4.class,
+ DexConstClass.class,
+ DexAputObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ }
+ DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
assertEquals("foo", constString.getString().toString());
}
@@ -700,7 +717,8 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class R { *; }");
- CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+ CodeInspector inspector =
+ compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -708,19 +726,33 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- checkInstructions(
- code,
- ImmutableList.of(
- DexInvokeDirect.class,
- DexConst4.class,
- DexNewArray.class,
- DexConst4.class,
- DexConstClass.class,
- DexAputObject.class,
- DexConstString.class,
- DexInvokeStatic.class,
- DexReturnVoid.class));
- DexConstString constString = (DexConstString) code.instructions[6];
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConstClass.class,
+ DexFilledNewArray.class,
+ DexMoveResultObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ } else {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConst4.class,
+ DexNewArray.class,
+ DexConst4.class,
+ DexConstClass.class,
+ DexAputObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ }
+ DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
assertNotEquals("foo", constString.getString().toString());
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index 4a76a6a..513dc73 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.AppView;
@@ -15,6 +16,7 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -33,7 +35,7 @@
this.parameters = parameters;
}
- private static final String[] expectedOutput = {"3"};
+ private static final String[] expectedOutput = {"3", "2"};
@Test
public void d8() throws Exception {
@@ -44,7 +46,7 @@
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(this::assertNoArrayLength);
+ .inspect(i -> inspect(i, true));
}
@Test
@@ -54,32 +56,52 @@
.addProgramClasses(Main.class)
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(this::assertNoArrayLength);
+ .inspect(i -> inspect(i, false));
}
private void transformArray(IRCode irCode, AppView<?> appView) {
- if (irCode.context().getReference().getName().toString().contains("main")) {
new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+ String name = irCode.context().getReference().getName().toString();
+ if (name.contains("filledArrayData")) {
assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+ } else if (name.contains("filledNewArray")) {
+ assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray));
}
}
- private void assertNoArrayLength(CodeInspector inspector) {
+ private void inspect(CodeInspector inspector, boolean d8) {
ClassSubject mainClass = inspector.clazz(Main.class);
assertTrue(mainClass.isPresent());
- assertTrue(
- mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
+ assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ if (!d8) {
+ MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
+ assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ }
}
public static final class Main {
+ @NeverInline
+ public static void filledArrayData() {
+ short[] values = new short[3];
+ values[0] = 5;
+ values[1] = 6;
+ values[2] = 1;
+ System.out.println(values.length);
+ }
+
+ @NeverInline
+ public static void filledNewArray() {
+ int[] values = new int[] {7, 8};
+ System.out.println(values.length);
+ }
+
public static void main(String[] args) {
- int[] ints = new int[3];
- ints[0] = 5;
- ints[1] = 6;
- ints[2] = 1;
- System.out.println(ints.length);
+ filledArrayData();
+ filledNewArray();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
index 8cc0379..0003703 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
@@ -3,17 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.arrays;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-// Regression test for issue found in b/259986613
@RunWith(Parameterized.class)
public class NewArrayInCatchRangeTest extends TestBase {
@@ -46,19 +54,32 @@
.setMinApi(parameters.getApiLevel())
.addInnerClasses(NewArrayInCatchRangeTest.class)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(1, filledArrayInstructions.size());
+ InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+ assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
}
static class TestClass {
public static int foo() {
int value = 1;
- int[] array = new int[1];
+ int[] array = null;
try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
+ array = new int[1];
+ } catch (Exception e) {
+ return array == null ? -1 : array.length;
}
+ array[0] = value;
return array[0];
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
new file mode 100644
index 0000000..5ad8f6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayInSameCatchRangeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayInSameCatchRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ try {
+ int[] array = new int[2];
+ array[0] = value;
+ array[1] = value;
+ return System.nanoTime() > 0 ? array[0] : 2;
+ } catch (RuntimeException e) {
+ return 42;
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
index ab11b3d..3996647 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
@@ -3,12 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.arrays;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -45,7 +49,15 @@
.setMinApi(parameters.getApiLevel())
.addInnerClasses(NewArrayInTwoCatchRangesTest.class)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
new file mode 100644
index 0000000..40d1557
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayMonitorTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayMonitorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayMonitorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayMonitorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(1, filledArrayInstructions.size());
+ InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+ assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
+ }
+
+ static class TestClass {
+
+ public static synchronized int foo() {
+ int value = 1;
+ int[] array = new int[1];
+ try {
+ array[0] = value;
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
new file mode 100644
index 0000000..d7dc866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for issue found in b/259986613
+@RunWith(Parameterized.class)
+public class NewArrayPutInCatchRangeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayPutInCatchRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+ assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array = new int[1];
+ try {
+ array[0] = value;
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
new file mode 100644
index 0000000..3319ccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArraySynchronizedBlockTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArraySynchronizedBlockTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArraySynchronizedBlockTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArraySynchronizedBlockTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(0, filledArrayInstructions.size());
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array;
+ synchronized (TestClass.class) {
+ array = new int[1];
+ } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+ array[0] = value;
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
new file mode 100644
index 0000000..939753f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,774 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.beust.jcommander.internal.Lists;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimplifyArrayConstructionTest extends TestBase {
+ @Parameters(name = "{0}, mode = {1}")
+ public static Iterable<?> data() {
+ return buildParameters(
+ getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+ CompilationMode.values());
+ }
+
+ private final TestParameters parameters;
+ private final CompilationMode compilationMode;
+
+ public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) {
+ this.parameters = parameters;
+ this.compilationMode = compilationMode;
+ }
+
+ private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = {
+ DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class
+ };
+
+ private static final String[] EXPECTED_OUTPUT = {
+ "[a]",
+ "[a, 1, null]",
+ "[1, null]",
+ "[1, null, 2]",
+ "[1, null, 2]",
+ "[1]",
+ "[1, 2]",
+ "[1, 2, 3, 4, 5]",
+ "[1]",
+ "[a, 1, null, d, e, f]",
+ "[1, null, 3, null, null, 6]",
+ "[1, 2, 3, 4, 5, 6]",
+ "[true, false]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1.0, 2.0]",
+ "[1.0, 2.0]",
+ "[]",
+ "[]",
+ "[true]",
+ "[1]",
+ "[1]",
+ "[1]",
+ "[1.0]",
+ "[1.0]",
+ "[0, 1]",
+ "[1, null]",
+ "[a]",
+ "[0, 1]",
+ "200",
+ "[0, 1, 2, 3, 4]",
+ "[4, 0, 0, 0, 0]",
+ "[4, 1, 2, 3, 4]",
+ "[0, 1, 2]",
+ "[0]",
+ "[0, 1, 2]",
+ "[1, 2, 3]",
+ "[1, 2, 3, 4, 5, 6]",
+ "[0]",
+ "[null, null]",
+ };
+
+ private static final byte[] TRANSFORMED_MAIN = transformedMain();
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeFalse(compilationMode == CompilationMode.DEBUG);
+ testForRuntime(
+ parameters.getRuntime(),
+ d8TestBuilder ->
+ d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode))
+ .addProgramClassFileData(TRANSFORMED_MAIN)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+ .inspect(inspector -> inspect(inspector, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options ->
+ options
+ .getOpenClosedInterfacesOptions()
+ .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class)))
+ .setMode(compilationMode)
+ .addProgramClassFileData(TRANSFORMED_MAIN)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .addKeepAnnotation()
+ .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+ .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }")
+ .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }")
+ .addDontObfuscate()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ private static byte[] transformedMain() {
+ try {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "interfaceArrayWithRawObject",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("getObjectThatImplementsSerializable")) {
+ visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .setReturnType(
+ ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"),
+ Object.class.getTypeName())
+ .transform();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ if (parameters.isCfRuntime()) {
+ return;
+ }
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertTrue(mainClass.isPresent());
+
+ MethodSubject stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays");
+ MethodSubject referenceArraysNoCasts =
+ mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
+ MethodSubject referenceArraysWithSubclasses =
+ mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+ MethodSubject interfaceArrayWithRawObject =
+ mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
+
+ MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+ MethodSubject intsThatUseFilledNewArray =
+ mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
+ MethodSubject twoDimensionalArrays =
+ mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays");
+ MethodSubject objectArraysFilledNewArrayRange =
+ mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange");
+ MethodSubject arraysThatUseFilledData =
+ mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData");
+ MethodSubject arraysThatUseNewArrayEmpty =
+ mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty");
+ MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray");
+ MethodSubject arrayWithCorrectCountButIncompleteCoverage =
+ mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
+ MethodSubject arrayWithExtraInitialPuts =
+ mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
+ MethodSubject catchHandlerThrowing =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
+ MethodSubject catchHandlerNonThrowingFilledNewArray =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
+ MethodSubject catchHandlerNonThrowingFillArrayData =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+ MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+
+ assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
+ assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
+ assertFilledArrayData(arraysThatUseFilledData);
+
+ if (compilationMode == CompilationMode.DEBUG) {
+ // The explicit assignments can't be collapsed without breaking the debugger's ability to
+ // visit each line.
+ assertArrayTypes(reversedArray, DexNewArray.class);
+ } else {
+ assertArrayTypes(reversedArray, DexFilledNewArray.class);
+ }
+
+ // Cannot use filled-new-array of String before K.
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+ assertArrayTypes(stringArrays, DexNewArray.class);
+ } else {
+ assertArrayTypes(stringArrays, DexFilledNewArray.class);
+ }
+ // Cannot use filled-new-array of Object before L.
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+ assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
+ assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+ assertArrayTypes(phiFilledNewArray, DexNewArray.class);
+ assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
+ assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
+ assertArrayTypes(assumedValues, DexNewArray.class);
+ } else {
+ assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
+ // TODO(b/246971330): Add support for arrays with subtypes.
+ if (isR8) {
+ assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
+ } else {
+ assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+ }
+
+ // TODO(b/246971330): Add support for arrays whose values have conditionals.
+ // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
+
+ assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class);
+
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+ assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class);
+ } else {
+ // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures.
+ }
+
+ assertArrayTypes(assumedValues, DexFilledNewArray.class);
+ }
+ // filled-new-array fails verification when types of parameters are not subclasses (aput-object
+ // does not).
+ assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class);
+
+ // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we
+ // haven't bothered.
+ assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
+ assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
+
+ assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
+ assertArrayTypes(catchHandlerNonThrowingFillArrayData, DexNewArray.class);
+ assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
+ }
+
+ private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
+ return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass());
+ }
+
+ private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) {
+ return isInstruction(Arrays.asList(clazz));
+ }
+
+ private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) {
+ assertTrue(method.isPresent());
+ List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+ disallowedClasses.remove(allowedArrayInst);
+
+ assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst)));
+ assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+ }
+
+ private static void assertFilledArrayData(MethodSubject method) {
+ assertTrue(method.isPresent());
+ List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+ disallowedClasses.remove(DexFillArrayData.class);
+ disallowedClasses.remove(DexNewArray.class);
+
+ assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+ assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut));
+ long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count();
+ long numFillArray =
+ method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count();
+ assertEquals(numNewArray, numFillArray);
+ }
+
+ public static final class Main {
+ static final String assumedNonNullField = null;
+ static final String assumedNullField = null;
+
+ public static void main(String[] args) {
+ stringArrays();
+ referenceArraysNoCasts();
+ referenceArraysWithSubclasses();
+ interfaceArrayWithRawObject();
+ phiFilledNewArray();
+ intsThatUseFilledNewArray();
+ twoDimensionalArrays();
+ objectArraysFilledNewArrayRange();
+ arraysThatUseFilledData();
+ arraysThatUseNewArrayEmpty();
+ reversedArray();
+ arrayWithCorrectCountButIncompleteCoverage();
+ arrayWithExtraInitialPuts();
+ catchHandlerThrowing();
+ catchHandlerNonThrowingFilledNewArray();
+ catchHandlerNonThrowingFillArrayData();
+ arrayIntoAnotherArray();
+ assumedValues();
+ }
+
+ @NeverInline
+ private static void stringArrays() {
+ // Test exact class, no null.
+ String[] stringArr = {"a"};
+ System.out.println(Arrays.toString(stringArr));
+ }
+
+ @NeverInline
+ private static void referenceArraysNoCasts() {
+ // Tests that no type info is needed when array type is Object[].
+ Object[] objectArr = {"a", 1, null};
+ System.out.println(Arrays.toString(objectArr));
+ // Test that interface arrays work when we have the exact interface already.
+ Serializable[] interfaceArr = {getSerializable(1), null};
+ System.out.println(Arrays.toString(interfaceArr));
+ }
+
+ @Keep
+ private static Serializable getSerializable(Integer value) {
+ return value;
+ }
+
+ @NeverInline
+ private static void referenceArraysWithSubclasses() {
+ Serializable[] interfaceArr = {1, null, 2};
+ System.out.println(Arrays.toString(interfaceArr));
+ Number[] objArray = {1, null, 2};
+ System.out.println(Arrays.toString(objArray));
+ }
+
+ @NeverInline
+ private static void interfaceArrayWithRawObject() {
+ // Interfaces can use filled-new-array only when we know types implement the interface.
+ Serializable[] arr = new Serializable[1];
+ // Transformed from `I get()` to `Object get()`.
+ arr[0] = getObjectThatImplementsSerializable();
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @Keep
+ private static /*Object*/ Serializable getObjectThatImplementsSerializable() {
+ return 1;
+ }
+
+ @NeverInline
+ private static void reversedArray() {
+ int[] arr = new int[5];
+ arr[4] = 4;
+ arr[3] = 3;
+ arr[2] = 2;
+ arr[1] = 1;
+ arr[0] = 0;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithCorrectCountButIncompleteCoverage() {
+ int[] arr = new int[5];
+ arr[0] = 0;
+ arr[0] = 1;
+ arr[0] = 2;
+ arr[0] = 3;
+ arr[0] = 4;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithExtraInitialPuts() {
+ int[] arr = new int[5];
+ arr[0] = 0;
+ arr[0] = 1;
+ arr[0] = 2;
+ arr[0] = 3;
+ arr[0] = 4;
+ arr[1] = 1;
+ arr[2] = 2;
+ arr[3] = 3;
+ arr[4] = 4;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerNonThrowingFilledNewArray() {
+ try {
+ int[] arr1 = {1, 2, 3};
+ System.currentTimeMillis();
+ System.out.println(Arrays.toString(arr1));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void catchHandlerNonThrowingFillArrayData() {
+ try {
+ int[] arr = {1, 2, 3, 4, 5, 6};
+ System.currentTimeMillis();
+ System.out.println(Arrays.toString(arr));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void catchHandlerThrowing() {
+ int[] arr1 = new int[3];
+ arr1[0] = 0;
+ arr1[1] = 1;
+ // Since the array is used in only one spot, and that spot is not within the try/catch, it
+ // should be safe to use filled-new-array, but we don't.
+ try {
+ System.currentTimeMillis();
+ arr1[2] = 2;
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ System.out.println(Arrays.toString(arr1));
+
+ try {
+ // Test filled-new-array with a throwing instruction before the last array-put.
+ int[] arr2 = new int[1];
+ System.currentTimeMillis();
+ arr2[0] = 0;
+ System.out.println(Arrays.toString(arr2));
+
+ // Test filled-array-data with a throwing instruction before the last array-put.
+ short[] arr3 = new short[3];
+ arr3[0] = 0;
+ arr3[1] = 1;
+ System.currentTimeMillis();
+ arr3[2] = 2;
+ System.out.println(Arrays.toString(arr3));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void arrayIntoAnotherArray() {
+ // Tests that our computeValues() method does not assume all array-put-object users have
+ // the array as the target.
+ int[] intArr = new int[1];
+ Object[] objArr = new Object[2];
+ objArr[0] = intArr;
+ System.out.println(Arrays.toString((int[]) objArr[0]));
+ }
+
+ @NeverInline
+ private static void assumedValues() {
+ Object[] arr = {assumedNonNullField, assumedNullField};
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void phiFilledNewArray() {
+ // The presence of ? should not affect use of filled-new-array.
+ Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void intsThatUseFilledNewArray() {
+ // Up to 5 ints uses filled-new-array rather than filled-array-data.
+ int[] intArr = {1, 2, 3, 4, 5};
+ System.out.println(Arrays.toString(intArr));
+ }
+
+ @NeverInline
+ private static void twoDimensionalArrays() {
+ Integer[][] twoDimensions = {new Integer[] {1}, null};
+ System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0)));
+ }
+
+ @NeverInline
+ private static void objectArraysFilledNewArrayRange() {
+ // 6 or more elements use /range variant.
+ Object[] objectArr = {"a", 1, null, "d", "e", "f"};
+ System.out.println(Arrays.toString(objectArr));
+ Serializable[] interfaceArr = {
+ getSerializable(1), null, getSerializable(3), null, null, getSerializable(6)
+ };
+ System.out.println(Arrays.toString(interfaceArr));
+ }
+
+ @NeverInline
+ private static void arraysThatUseFilledData() {
+ // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used).
+ int[] intArr = {1, 2, 3, 4, 5, 6};
+ // For other primitives, > 1 element leads to fill-array-data.
+ System.out.println(Arrays.toString(intArr));
+ boolean[] boolArr = {true, false};
+ System.out.println(Arrays.toString(boolArr));
+ byte[] byteArr = {1, 2};
+ System.out.println(Arrays.toString(byteArr));
+ char[] charArr = {'1', '2'};
+ System.out.println(Arrays.toString(charArr));
+ long[] longArr = {1, 2};
+ System.out.println(Arrays.toString(longArr));
+ float[] floatArr = {1, 2};
+ System.out.println(Arrays.toString(floatArr));
+ double[] doubleArr = {1, 2};
+ System.out.println(Arrays.toString(doubleArr));
+ }
+
+ @NeverInline
+ private static void arraysThatUseNewArrayEmpty() {
+ // int/object of size zero should not use filled-new-array.
+ int[] intArr = {};
+ System.out.println(Arrays.toString(intArr));
+ String[] strArr = {};
+ System.out.println(Arrays.toString(strArr));
+
+ // Other primitives with size <= 1 should not use filled-array-data.
+ boolean[] boolArr = {true};
+ System.out.println(Arrays.toString(boolArr));
+ byte[] byteArr = {1};
+ System.out.println(Arrays.toString(byteArr));
+ char[] charArr = {'1'};
+ System.out.println(Arrays.toString(charArr));
+ long[] longArr = {1};
+ System.out.println(Arrays.toString(longArr));
+ float[] floatArr = {1};
+ System.out.println(Arrays.toString(floatArr));
+ double[] doubleArr = {1};
+ System.out.println(Arrays.toString(doubleArr));
+
+ // Array used before all members are set.
+ int[] readArray = new int[2];
+ readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime());
+ readArray[1] = readArray[0] + 1;
+ System.out.println(Arrays.toString(readArray));
+
+ // Array does not have all elements set (we could make this work, but likely this is rare).
+ Integer[] partialArray = new Integer[2];
+ partialArray[0] = 1;
+ System.out.println(Arrays.toString(partialArray));
+
+ // Non-constant array size.
+ int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime());
+ Object[] nonConstSize = new Object[trickyZero + 1];
+ nonConstSize[0] = "a";
+ System.out.println(Arrays.toString(nonConstSize));
+
+ // Non-constant index.
+ Object[] nonConstIndex = new Object[2];
+ nonConstIndex[trickyZero] = 0;
+ nonConstIndex[trickyZero + 1] = 1;
+ System.out.println(Arrays.toString(nonConstIndex));
+
+ // Exceeds our (arbitrary) size limit for /range.
+ String[] bigArr = new String[201];
+ bigArr[0] = "0";
+ bigArr[1] = "1";
+ bigArr[2] = "2";
+ bigArr[3] = "3";
+ bigArr[4] = "4";
+ bigArr[5] = "5";
+ bigArr[6] = "6";
+ bigArr[7] = "7";
+ bigArr[8] = "8";
+ bigArr[9] = "9";
+ bigArr[10] = "10";
+ bigArr[11] = "11";
+ bigArr[12] = "12";
+ bigArr[13] = "13";
+ bigArr[14] = "14";
+ bigArr[15] = "15";
+ bigArr[16] = "16";
+ bigArr[17] = "17";
+ bigArr[18] = "18";
+ bigArr[19] = "19";
+ bigArr[20] = "20";
+ bigArr[21] = "21";
+ bigArr[22] = "22";
+ bigArr[23] = "23";
+ bigArr[24] = "24";
+ bigArr[25] = "25";
+ bigArr[26] = "26";
+ bigArr[27] = "27";
+ bigArr[28] = "28";
+ bigArr[29] = "29";
+ bigArr[30] = "30";
+ bigArr[31] = "31";
+ bigArr[32] = "32";
+ bigArr[33] = "33";
+ bigArr[34] = "34";
+ bigArr[35] = "35";
+ bigArr[36] = "36";
+ bigArr[37] = "37";
+ bigArr[38] = "38";
+ bigArr[39] = "39";
+ bigArr[40] = "40";
+ bigArr[41] = "41";
+ bigArr[42] = "42";
+ bigArr[43] = "43";
+ bigArr[44] = "44";
+ bigArr[45] = "45";
+ bigArr[46] = "46";
+ bigArr[47] = "47";
+ bigArr[48] = "48";
+ bigArr[49] = "49";
+ bigArr[50] = "50";
+ bigArr[51] = "51";
+ bigArr[52] = "52";
+ bigArr[53] = "53";
+ bigArr[54] = "54";
+ bigArr[55] = "55";
+ bigArr[56] = "56";
+ bigArr[57] = "57";
+ bigArr[58] = "58";
+ bigArr[59] = "59";
+ bigArr[60] = "60";
+ bigArr[61] = "61";
+ bigArr[62] = "62";
+ bigArr[63] = "63";
+ bigArr[64] = "64";
+ bigArr[65] = "65";
+ bigArr[66] = "66";
+ bigArr[67] = "67";
+ bigArr[68] = "68";
+ bigArr[69] = "69";
+ bigArr[70] = "70";
+ bigArr[71] = "71";
+ bigArr[72] = "72";
+ bigArr[73] = "73";
+ bigArr[74] = "74";
+ bigArr[75] = "75";
+ bigArr[76] = "76";
+ bigArr[77] = "77";
+ bigArr[78] = "78";
+ bigArr[79] = "79";
+ bigArr[80] = "80";
+ bigArr[81] = "81";
+ bigArr[82] = "82";
+ bigArr[83] = "83";
+ bigArr[84] = "84";
+ bigArr[85] = "85";
+ bigArr[86] = "86";
+ bigArr[87] = "87";
+ bigArr[88] = "88";
+ bigArr[89] = "89";
+ bigArr[90] = "90";
+ bigArr[91] = "91";
+ bigArr[92] = "92";
+ bigArr[93] = "93";
+ bigArr[94] = "94";
+ bigArr[95] = "95";
+ bigArr[96] = "96";
+ bigArr[97] = "97";
+ bigArr[98] = "98";
+ bigArr[99] = "99";
+ bigArr[100] = "100";
+ bigArr[101] = "101";
+ bigArr[102] = "102";
+ bigArr[103] = "103";
+ bigArr[104] = "104";
+ bigArr[105] = "105";
+ bigArr[106] = "106";
+ bigArr[107] = "107";
+ bigArr[108] = "108";
+ bigArr[109] = "109";
+ bigArr[110] = "110";
+ bigArr[111] = "111";
+ bigArr[112] = "112";
+ bigArr[113] = "113";
+ bigArr[114] = "114";
+ bigArr[115] = "115";
+ bigArr[116] = "116";
+ bigArr[117] = "117";
+ bigArr[118] = "118";
+ bigArr[119] = "119";
+ bigArr[120] = "120";
+ bigArr[121] = "121";
+ bigArr[122] = "122";
+ bigArr[123] = "123";
+ bigArr[124] = "124";
+ bigArr[125] = "125";
+ bigArr[126] = "126";
+ bigArr[127] = "127";
+ bigArr[128] = "128";
+ bigArr[129] = "129";
+ bigArr[130] = "130";
+ bigArr[131] = "131";
+ bigArr[132] = "132";
+ bigArr[133] = "133";
+ bigArr[134] = "134";
+ bigArr[135] = "135";
+ bigArr[136] = "136";
+ bigArr[137] = "137";
+ bigArr[138] = "138";
+ bigArr[139] = "139";
+ bigArr[140] = "140";
+ bigArr[141] = "141";
+ bigArr[142] = "142";
+ bigArr[143] = "143";
+ bigArr[144] = "144";
+ bigArr[145] = "145";
+ bigArr[146] = "146";
+ bigArr[147] = "147";
+ bigArr[148] = "148";
+ bigArr[149] = "149";
+ bigArr[150] = "150";
+ bigArr[151] = "151";
+ bigArr[152] = "152";
+ bigArr[153] = "153";
+ bigArr[154] = "154";
+ bigArr[155] = "155";
+ bigArr[156] = "156";
+ bigArr[157] = "157";
+ bigArr[158] = "158";
+ bigArr[159] = "159";
+ bigArr[160] = "160";
+ bigArr[161] = "161";
+ bigArr[162] = "162";
+ bigArr[163] = "163";
+ bigArr[164] = "164";
+ bigArr[165] = "165";
+ bigArr[166] = "166";
+ bigArr[167] = "167";
+ bigArr[168] = "168";
+ bigArr[169] = "169";
+ bigArr[170] = "170";
+ bigArr[171] = "171";
+ bigArr[172] = "172";
+ bigArr[173] = "173";
+ bigArr[174] = "174";
+ bigArr[175] = "175";
+ bigArr[176] = "176";
+ bigArr[177] = "177";
+ bigArr[178] = "178";
+ bigArr[179] = "179";
+ bigArr[180] = "180";
+ bigArr[181] = "181";
+ bigArr[182] = "182";
+ bigArr[183] = "183";
+ bigArr[184] = "184";
+ bigArr[185] = "185";
+ bigArr[186] = "186";
+ bigArr[187] = "187";
+ bigArr[188] = "188";
+ bigArr[189] = "189";
+ bigArr[190] = "190";
+ bigArr[191] = "191";
+ bigArr[192] = "192";
+ bigArr[193] = "193";
+ bigArr[194] = "194";
+ bigArr[195] = "195";
+ bigArr[196] = "196";
+ bigArr[197] = "197";
+ bigArr[198] = "198";
+ bigArr[199] = "199";
+ bigArr[200] = "200";
+ System.out.println(Arrays.asList(bigArr).get(200));
+ }
+ }
+}