Revert "Reland #2: "Use new-filled-array for more things.""
This reverts commit 254e50f850d0120a359b53c3c09317d625e195a5.
Revert "Do not enable filled-new-array for pre-N minSdkVersion"
This reverts commit fde5dbb62eb6516dd126c4b5caeab8fa19012f79.
Bug: b/246971330
Bug: b/259986613
Change-Id: Ib02c3973cd1a157f0bb79f73a5263e7e02b0f194
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 4b70a1c..369e60c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -275,8 +275,8 @@
public boolean isSubtype(DexType subtype, DexType supertype) {
assert subtype != null;
assert supertype != null;
- assert subtype.isClassType() : "subtype not a class: " + subtype;
- assert supertype.isClassType() : "supertype not a class: " + supertype;
+ assert subtype.isClassType();
+ assert supertype.isClassType();
return subtype == supertype || isStrictSubtypeOf(subtype, supertype);
}
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 2a543a8..30c3b67 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,7 +12,6 @@
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;
@@ -30,7 +29,6 @@
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;
@@ -39,7 +37,6 @@
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 {
@@ -213,7 +210,7 @@
if (value.isPhi()) {
return null;
}
- if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
+ if (value.definition.isNewArrayEmpty()) {
return computeSingleEnumFieldValueForValuesArray(value);
}
if (value.definition.isNewInstance()) {
@@ -223,7 +220,7 @@
}
private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
- if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
+ if (!value.definition.isNewArrayEmpty()) {
return null;
}
AbstractValue valuesValue = computedValues.get(value);
@@ -244,38 +241,26 @@
}
private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
- NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
- InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
- assert newArrayEmpty != null || invokeNewArray != null;
+ assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
- DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
- if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+ NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
+ if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
return null;
}
if (value.hasDebugUsers() || value.hasPhiUsers()) {
return null;
}
+ if (!newArrayEmpty.size().isConstNumber()) {
+ return null;
+ }
- int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
- if (valuesSize < 1) {
- // Array is empty or non-const size.
+ int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ if (valuesSize == 0) {
+ // No need to compute the state of an empty array.
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()) {
@@ -291,9 +276,18 @@
if (index < 0 || index >= valuesSize) {
return null;
}
- if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
+ ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
+ if (objectState == null || objectState.isEmpty()) {
+ // We need the state of all fields for the analysis to be valuable.
return null;
}
+ if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+ return null;
+ }
+ if (valuesState[index] != null) {
+ return null;
+ }
+ valuesState[index] = objectState;
break;
case ASSUME:
@@ -334,34 +328,24 @@
.createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
}
- private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
+ private ObjectState computeEnumInstanceObjectState(Value value) {
Value root = value.getAliasedValue();
if (root.isPhi()) {
- return false;
+ return ObjectState.empty();
}
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 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;
+ return ObjectState.empty();
}
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 b4b2359..74a3fb5 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,7 +33,6 @@
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;
@@ -47,7 +46,6 @@
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;
@@ -318,37 +316,17 @@
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.
- var rewriteOptions = appView.options().rewriteArrayOptions();
- if (rewriteOptions.canUseFilledNewArrayOfObjects()
- && objects.size() < rewriteOptions.maxRangeInputs) {
- 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());
- }
+ 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 InvokeNewArray(
- appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
- } else {
- 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);
- instructionIterator.add(
- new ArrayPut(
- MemberType.OBJECT,
- newObjectsValue,
- indexValue,
- materializingInstruction.outValue()));
- }
+ 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 c25c456..fbe3ff5 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,7 +31,6 @@
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;
@@ -303,30 +302,18 @@
*/
private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
Value objectsValue) throws InvalidRawMessageInfoException {
- if (objectsValue.isPhi()) {
+ if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
throw new InvalidRawMessageInfoException();
}
NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
- InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
+ int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
- if (newArrayEmpty == null && invokeNewArray == null) {
- throw new InvalidRawMessageInfoException();
- }
- // Verify that the array is used in only one spot.
- if (invokeNewArray != null) {
- if (objectsValue.uniqueUsers().size() != 1) {
- throw new InvalidRawMessageInfoException();
- }
- return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
- }
-
+ // Verify that the size is correct.
Value sizeValue = newArrayEmpty.size().getAliasedValue();
- if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
- throw new InvalidRawMessageInfoException();
- }
- int arraySize = sizeValue.definition.asConstNumber().getIntValue();
- if (arraySize != objectsValue.uniqueUsers().size() - 1) {
+ if (sizeValue.isPhi()
+ || !sizeValue.definition.isConstNumber()
+ || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
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 99f92eb..e06cdbee 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,25 +73,20 @@
* <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;
}
- 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()) {
+
+ if (definition.isNewArrayEmpty()) {
+ Value arrayValue = definition.outValue();
+ if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+ return;
+ }
+
for (Instruction user : arrayValue.uniqueUsers()) {
// If we encounter an Assume instruction here, we also need to consider indirect users.
assert !user.isAssume();
@@ -100,10 +95,11 @@
}
worklist.add(user);
}
- } else {
- assert false;
+ internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
+ return;
}
- internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
+
+ assert false;
}
/**
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 8892da0..c99ee11 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,10 +1038,6 @@
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 f0715b5..a2e815f 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,9 +221,4 @@
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 a829247..c6d527f 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,6 +49,10 @@
return super.toString() + " " + type.toString();
}
+ public Value dest() {
+ return outValue;
+ }
+
public Value size() {
return inValues.get(0);
}
@@ -56,7 +60,7 @@
@Override
public void buildDex(DexBuilder builder) {
int size = builder.allocatedRegister(size(), getNumber());
- int dest = builder.allocatedRegister(outValue, getNumber());
+ int dest = builder.allocatedRegister(dest(), getNumber());
builder.add(this, new DexNewArray(dest, size, type));
}
@@ -167,10 +171,4 @@
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 f0ca38f..23f0de9 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,6 +16,7 @@
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;
@@ -54,6 +55,7 @@
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;
@@ -82,7 +84,6 @@
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;
@@ -137,7 +138,6 @@
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
@@ -162,6 +162,7 @@
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;
@@ -2100,21 +2101,16 @@
}
}
- private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
- for (Value v : values) {
- if (!v.isConstant()) {
- return null;
- }
+ private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
+ if (values == null) {
+ return null;
}
if (elementSize == 1) {
short[] result = new short[(size + 1) / 2];
for (int i = 0; i < size; i += 2) {
- short value =
- (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
+ short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
if (i + 1 < size) {
- value |=
- (short)
- ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
+ value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
}
result[i / 2] = value;
}
@@ -2124,7 +2120,7 @@
int shortsPerConstant = elementSize / 2;
short[] result = new short[size * shortsPerConstant];
for (int i = 0; i < size; i++) {
- long value = values[i].getConstInstruction().asConstNumber().getRawValue();
+ long value = values[i].asConstNumber().getRawValue();
for (int part = 0; part < shortsPerConstant; part++) {
result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
}
@@ -2132,45 +2128,40 @@
return result;
}
- private Value[] computeArrayValues(LinearFlowInstructionListIterator it, int size) {
- NewArrayEmpty newArrayEmpty = it.next().asNewArrayEmpty();
- assert newArrayEmpty != null;
- Value arrayValue = newArrayEmpty.outValue();
-
- // 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;
-
- Value[] values = new Value[size];
+ private ConstInstruction[] computeConstantArrayValues(
+ NewArrayEmpty newArray, BasicBlock block, int size) {
+ if (size > MAX_FILL_ARRAY_SIZE) {
+ return null;
+ }
+ ConstInstruction[] values = new ConstInstruction[size];
int remaining = size;
- Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+ 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.
- // TODO(b/246971330): Allow simplification when all users of the array are in the same
- // try/catch.
- if (instruction.getBlock().hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+ // If we encounter an instruction that can throw an exception we need to bail out of the
+ // optimization so that we do not transform half-initialized arrays into fully initialized
+ // arrays on exceptional edges. If the block has no handlers it is not observable so
+ // we perform the rewriting.
+ if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
return null;
}
if (!users.contains(instruction)) {
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) {
+ // If the initialization sequence is broken by another use we cannot use a
+ // fill-array-data instruction.
+ if (!instruction.isArrayPut()) {
return null;
}
- if (!arrayPut.index().isConstNumber()) {
+ ArrayPut arrayPut = instruction.asArrayPut();
+ if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
return null;
}
int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
@@ -2180,38 +2171,40 @@
if (values[index] != null) {
return null;
}
- Value value = arrayPut.value();
- if (needsTypeCheck && !value.isAlwaysNull(appView)) {
- DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
- if (elementType.isArrayType()) {
- if (elementType != valueDexType) {
- return null;
- }
- } else if (valueDexType.isArrayType()) {
- // isSubtype asserts for this case.
- return null;
- } else if (valueDexType.isNullValueType()) {
- // Assume instructions can cause value.isAlwaysNull() == false while the DexType is
- // null.
- // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
- // that hits this case.
- } else {
- // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
- // library types (which this helper does not do).
- if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
- return null;
- }
- }
- }
+ ConstInstruction value = arrayPut.value().getConstInstruction();
values[index] = value;
--remaining;
if (remaining == 0) {
return values;
}
}
+ 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);
return null;
}
+ private boolean allowNewFilledArrayConstruction(Instruction instruction) {
+ if (!(instruction instanceof NewArrayEmpty)) {
+ 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()) {
+ return true;
+ }
+ return newArray.type == dexItemFactory.stringArrayType
+ && options.canUseFilledNewArrayOfObjects();
+ }
+
/**
* Replace new-array followed by stores of constants to all entries with new-array
* and fill-array-data / filled-new-array.
@@ -2220,11 +2213,6 @@
if (options.isGeneratingClassFiles()) {
return;
}
- InternalOptions.RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
- boolean canUseForStrings = rewriteOptions.canUseFilledNewArrayOfStrings();
- boolean canUseForObjects = rewriteOptions.canUseFilledNewArrayOfObjects();
- boolean canUseForArrays = rewriteOptions.canUseFilledNewArrayOfArrays();
-
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<>();
@@ -2233,56 +2221,31 @@
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
Instruction instruction = it.next();
- NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
- if (newArrayEmpty == null || !newArrayEmpty.size().isConstant()) {
+ if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
continue;
}
- if (instruction.getLocalInfo() != null) {
- continue;
- }
- int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- if (size < 1 || size > rewriteOptions.maxFillArrayDataInputs) {
- continue;
- }
- DexType arrayType = newArrayEmpty.type;
- if (!arrayType.isPrimitiveArrayType()) {
- if (arrayType == dexItemFactory.stringArrayType) {
- if (!canUseForStrings) {
- continue;
- }
- } else if (!canUseForObjects) {
- continue;
- } else if (!canUseForArrays && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
- continue;
- }
- }
-
- Value[] values =
- computeArrayValues(
- new LinearFlowInstructionListIterator(code, block, it.previousIndex()), size);
+ NewArrayEmpty newArray = instruction.asNewArrayEmpty();
+ int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
+ ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
if (values == null) {
continue;
}
- // 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()
- || (arrayType == dexItemFactory.intArrayType && size <= 5)) {
+ if (newArray.type == dexItemFactory.stringArrayType) {
// Don't replace with filled-new-array if it requires more than 200 consecutive registers.
- if (size > rewriteOptions.maxRangeInputs) {
+ if (size > 200) {
continue;
}
- // block.hasCatchHandlers() is fine here since new-array-filled replaces new-array-empty
- // and computeArrayValues already checks that no throwing instructions exist between the
- // original new-array-empty and the final aput-object (where the new-array-filled will be
- // positioned).
- Value invokeValue =
- code.createValue(newArrayEmpty.getOutType(), newArrayEmpty.getLocalInfo());
- InvokeNewArray invoke = new InvokeNewArray(arrayType, invokeValue, Arrays.asList(values));
- for (Value value : newArrayEmpty.inValues()) {
- value.removeUser(newArrayEmpty);
+ List<Value> stringValues = new ArrayList<>(size);
+ for (ConstInstruction value : values) {
+ stringValues.add(value.outValue());
}
- newArrayEmpty.outValue().replaceUsers(invokeValue);
+ 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);
@@ -2292,33 +2255,26 @@
if (size == 1) {
continue;
}
- // TODO(b/246971330): Allow simplification when all users of the array are in the same
- // try/catch.
- if (block.hasCatchHandlers()) {
- // NewArrayFilledData can throw, so creating one as done below would add a second
- // throwing instruction to the same block (the first one being NewArrayEmpty).
- continue;
- }
- int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+ int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
short[] contents = computeArrayFilledData(values, size, elementSize);
if (contents == null) {
continue;
}
- int arraySize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- // fill-array-data requires the new-array-empty instruction to remain, as it does not
- // itself create an array.
+ if (block.hasCatchHandlers()) {
+ continue;
+ }
+ int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
NewArrayFilledData fillArray =
- new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, arraySize, contents);
- fillArray.setPosition(newArrayEmpty.getPosition());
+ new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
+ fillArray.setPosition(newArray.getPosition());
it.add(fillArray);
- storesToRemoveForArray.put(newArrayEmpty.outValue(), size);
+ storesToRemoveForArray.put(newArray.outValue(), size);
}
}
// 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();
- int numInstructionsInserted = 0;
do {
visitedBlocks.add(block);
it = block.listIterator(code);
@@ -2340,7 +2296,6 @@
// last removed put at which point we are now adding the construction.
construction.setPosition(instruction.getPosition());
it.add(construction);
- numInstructionsInserted += 1;
}
}
}
@@ -2349,7 +2304,6 @@
BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
} while (block != null);
- assert numInstructionsInserted == instructionToInsertForArray.size();
}
}
assert code.isConsistentSSA(appView);
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 6fb553c..a649af6 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,7 +18,6 @@
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;
@@ -69,7 +68,6 @@
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;
@@ -1062,10 +1060,6 @@
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:
@@ -1136,40 +1130,6 @@
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 7cb0290..3e17a0a 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,8 +32,6 @@
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 6e14907..3e4078b 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,7 +27,6 @@
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;
@@ -425,16 +424,12 @@
// Perform a conservative evaluation of an array content of dex type values from its construction
// until its use at a given instruction.
- 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();
- }
-
+ private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
+ NewArrayEmpty newArray,
+ List<CheckCast> aliases,
+ int size,
+ Instruction user,
+ DexItemFactory factory) {
DexType[] values = new DexType[size];
int remaining = size;
Set<Instruction> users = Sets.newIdentityHashSet();
@@ -458,7 +453,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 ? new DexTypeList(values) : null;
+ return remaining == 0 ? values : null;
}
// Any other kinds of use besides array-put mean that the array escapes and its content
// could be altered.
@@ -503,21 +498,6 @@
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.
@@ -571,13 +551,30 @@
}
// Make sure this Value refers to a new array.
- if (classListValue.definition.isNewArrayEmpty()) {
- return evaluateTypeArrayContentFromConstructionToUse(
- classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
- } else if (classListValue.definition.isInvokeNewArray()) {
- return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
- } else {
+ if (!classListValue.definition.isNewArrayEmpty()
+ || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
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 93d387e..213819d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -104,9 +104,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.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;
@@ -5081,43 +5079,25 @@
return;
}
Value parametersValue = constructorDefinition.inValues().get(1);
- if (parametersValue.isPhi()) {
+ if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
// Give up, we can't tell which constructor is being invoked.
return;
}
- NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
- InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
- int parametersSize =
- newArrayEmpty != null
- ? newArrayEmpty.sizeIfConst()
- : invokeNewArray != null ? invokeNewArray.size() : -1;
- if (parametersSize < 0) {
+
+ Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
+ if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
+ // Give up, we can't tell which constructor is being invoked.
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();
@@ -5179,31 +5159,20 @@
}
Value interfacesValue = invoke.arguments().get(1);
- if (interfacesValue.isPhi()) {
+ if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
// Give up, we can't tell which interfaces the proxy implements.
return;
}
- 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;
- }
-
WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
- for (Value value : values) {
- DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
+ for (Instruction user : interfacesValue.uniqueUsers()) {
+ if (!user.isArrayPut()) {
+ continue;
+ }
+
+ ArrayPut arrayPut = user.asArrayPut();
+ DexType type =
+ ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.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 09641c3..b726531 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,7 +816,6 @@
public boolean debug = false;
- private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
private final CallSiteOptimizationOptions callSiteOptimizationOptions =
new CallSiteOptimizationOptions();
private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -851,10 +850,6 @@
public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
- public RewriteArrayOptions rewriteArrayOptions() {
- return rewriteArrayOptions;
- }
-
public CallSiteOptimizationOptions callSiteOptimizationOptions() {
return callSiteOptimizationOptions;
}
@@ -1388,42 +1383,6 @@
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 maxRangeInputs = 200;
- // Arbitrary limit of number of inputs to fill-array-data.
- public int maxFillArrayDataInputs = 8 * 1024;
-
- // 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 canUseFilledNewArrayOfObjects() {
- 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;
@@ -2431,6 +2390,19 @@
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 7d41ae1..50d23c9 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.utils;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -33,18 +32,4 @@
}
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 b00d78b..812eb63 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,9 +10,7 @@
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;
@@ -95,29 +93,16 @@
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
-
- // 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);
- }
+ 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 7141ad08..1e43777 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,7 +53,8 @@
.addProgramClasses(I.class, A.class)
.addProgramClassFileData(getTransformedMain())
.addKeepMainRule(Main.class)
- .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
+ // Keep get() to prevent that we optimize it into having static return type A.
+ .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** 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 c544c9e..83ea780 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,12 +16,10 @@
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;
@@ -641,8 +639,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep class R { *; }");
- CodeInspector inspector =
- compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
+ CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -650,33 +647,19 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- // 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];
+ 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];
assertEquals("foo", constString.getString().toString());
}
@@ -717,8 +700,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class R { *; }");
- CodeInspector inspector =
- compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
+ CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -726,33 +708,19 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- // 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];
+ 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];
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 513dc73..4a76a6a 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,7 +6,6 @@
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;
@@ -16,7 +15,6 @@
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;
@@ -35,7 +33,7 @@
this.parameters = parameters;
}
- private static final String[] expectedOutput = {"3", "2"};
+ private static final String[] expectedOutput = {"3"};
@Test
public void d8() throws Exception {
@@ -46,7 +44,7 @@
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(i -> inspect(i, true));
+ .inspect(this::assertNoArrayLength);
}
@Test
@@ -56,52 +54,32 @@
.addProgramClasses(Main.class)
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.addKeepMainRule(Main.class)
- .enableInliningAnnotations()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(i -> inspect(i, false));
+ .inspect(this::assertNoArrayLength);
}
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 inspect(CodeInspector inspector, boolean d8) {
+ private void assertNoArrayLength(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz(Main.class);
assertTrue(mainClass.isPresent());
- MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
- assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
- if (!d8) {
- MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
- assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
- }
+ assertTrue(
+ mainClass.mainMethod().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) {
- filledArrayData();
- filledNewArray();
+ int[] ints = new int[3];
+ ints[0] = 5;
+ ints[1] = 6;
+ ints[2] = 1;
+ System.out.println(ints.length);
}
}
}
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
deleted file mode 100644
index 939753f..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
+++ /dev/null
@@ -1,774 +0,0 @@
-// 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));
- }
- }
-}