filled-new-array: Add support for InvokeNewArray to Enum Unboxing
Bug: 246971330
Change-Id: Ibeba3cdb397ef807a1087cb71599360066a58f2a
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..27227a0 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,47 @@
}
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;
+ if (newArrayEmpty != null) {
+ if (!newArrayEmpty.size().isConstNumber()) {
+ return null;
+ }
+ valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ } else {
+ valuesSize = invokeNewArray.inValues().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()) {
@@ -276,18 +300,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 +343,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/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/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c6d527f..a5f71e5 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));
}
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");