blob: 9082be56afc9bdef27f9ec90db1f492e82c393d0 [file] [log] [blame]
// Copyright (c) 2021, 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.utils;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import java.util.Arrays;
import java.util.List;
public class ValueUtils {
// We allocate an array of this size, so guard against it getting too big.
private static int MAX_ARRAY_SIZE = 100000;
@SuppressWarnings("ReferenceEquality")
public static boolean isStringBuilder(Value value, DexItemFactory dexItemFactory) {
TypeElement type = value.getType();
return type.isClassType()
&& type.asClassType().getClassType() == dexItemFactory.stringBuilderType;
}
@SuppressWarnings("ReferenceEquality")
public static boolean isNonNullStringBuilder(Value value, DexItemFactory dexItemFactory) {
while (true) {
if (value.isPhi()) {
return false;
}
Instruction definition = value.getDefinition();
if (definition.isNewInstance()) {
NewInstance newInstance = definition.asNewInstance();
return newInstance.clazz == dexItemFactory.stringBuilderType;
}
if (definition.isInvokeVirtual()) {
InvokeVirtual invoke = definition.asInvokeVirtual();
if (dexItemFactory.stringBuilderMethods.isAppendMethod(invoke.getInvokedMethod())) {
value = invoke.getReceiver();
continue;
}
}
// Unhandled definition.
return false;
}
}
public static final class ArrayValues {
private List<Value> elementValues;
private ArrayPut[] arrayPutsByIndex;
private final Value arrayValue;
private ArrayValues(Value arrayValue, List<Value> elementValues) {
this.arrayValue = arrayValue;
this.elementValues = elementValues;
}
private ArrayValues(Value arrayValue, ArrayPut[] arrayPutsByIndex) {
this.arrayValue = arrayValue;
this.arrayPutsByIndex = arrayPutsByIndex;
}
/** May contain null entries when array has null entries. */
public List<Value> getElementValues() {
if (elementValues == null) {
ArrayPut[] puts = arrayPutsByIndex;
Value[] elementValuesArr = new Value[puts.length];
for (int i = 0; i < puts.length; ++i) {
ArrayPut arrayPut = puts[i];
elementValuesArr[i] = arrayPut == null ? null : arrayPut.value();
}
elementValues = Arrays.asList(elementValuesArr);
}
return elementValues;
}
public int size() {
return elementValues != null ? elementValues.size() : arrayPutsByIndex.length;
}
public boolean containsHoles() {
for (ArrayPut arrayPut : arrayPutsByIndex) {
if (arrayPut == null) {
return true;
}
}
return false;
}
public ArrayPut[] getArrayPutsByIndex() {
assert arrayPutsByIndex != null;
return arrayPutsByIndex;
}
public Value getArrayValue() {
return arrayValue;
}
public Instruction getDefinition() {
return arrayValue.definition;
}
}
/**
* Attempts to determine all values for the given array. This will work only when:
*
* <pre>
* 1) The Array has a single users (other than array-puts)
* * This constraint is to ensure other users do not modify the array.
* * When users are in different blocks, their order is hard to know.
* 2) The array size is a constant and non-negative.
* 3) All array-put instructions have constant and unique indices.
* 4) The array-put instructions are guaranteed to be executed before singleUser.
* </pre>
*
* @param arrayValue The Value for the array.
* @param singleUser The only non-array-put user, or null to auto-detect.
* @return The computed array values, or null if they could not be determined.
*/
public static ArrayValues computeSingleUseArrayValues(Value arrayValue, Instruction singleUser) {
TypeElement arrayType = arrayValue.getType();
if (!arrayType.isArrayType() || arrayValue.hasDebugUsers() || arrayValue.isPhi()) {
return null;
}
Instruction definition = arrayValue.definition;
NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
NewArrayFilled newArrayFilled = definition.asNewArrayFilled();
if (newArrayFilled != null) {
// It would be possible to have new-array-filled followed by aput-array, but that sequence of
// instructions does not commonly occur, so we don't support it here.
if (!arrayValue.hasSingleUniqueUser() || arrayValue.hasPhiUsers()) {
return null;
}
return new ArrayValues(arrayValue, newArrayFilled.inValues());
} else if (newArrayEmpty == null) {
return null;
}
int arraySize = newArrayEmpty.sizeIfConst();
if (arraySize < 0 || arraySize > MAX_ARRAY_SIZE) {
// Array is non-const size.
return null;
}
return computeArrayValuesInternal(newArrayEmpty, arraySize, singleUser, false);
}
/**
* Determines the values for the given array at the point the last element is assigned.
*
* <pre>
* Returns null under the following conditions:
* * The array has a non-const, negative, or abnormally large size.
* * An array-put with non-constant index exists.
* * An array-put with an out-of-bounds index exists.
* * An array-put for the last index is not found.
* * An array-put is found after the last-index array-put.
* * An array-put is found where the array and value are the same: arr[index] = arr;
* * There are multiple array-put instructions for the same index.
* </pre>
*/
public static ArrayValues computeInitialArrayValues(NewArrayEmpty newArrayEmpty) {
int arraySize = newArrayEmpty.sizeIfConst();
if (arraySize < 0 || arraySize > MAX_ARRAY_SIZE) {
// Array is non-const size.
return null;
}
Value arrayValue = newArrayEmpty.outValue();
// Find array-put for the last element, as well as blocks for other array users.
ArrayPut lastArrayPut = null;
int lastIndex = arraySize - 1;
for (Instruction user : arrayValue.uniqueUsers()) {
ArrayPut arrayPut = user.asArrayPut();
if (arrayPut != null && arrayPut.array() == arrayValue) {
int index = arrayPut.indexOrDefault(-1);
if (index == lastIndex) {
lastArrayPut = arrayPut;
break;
}
}
}
if (lastArrayPut == null) {
return null;
}
// Find all array-puts up until the last one.
// Also checks that no array-puts appear after the last one.
ArrayValues ret = computeArrayValuesInternal(newArrayEmpty, arraySize, lastArrayPut, true);
if (ret == null) {
return null;
}
// Since the last array-put is used as firstUser, it will not already be in arrayPutsByIndex.
if (ret.arrayPutsByIndex[lastIndex] != null) {
return null;
}
ret.arrayPutsByIndex[lastIndex] = lastArrayPut;
return ret;
}
private static ArrayValues computeArrayValuesInternal(
NewArrayEmpty newArrayEmpty, int arraySize, Instruction firstUser, boolean allowOtherUsers) {
ArrayPut[] arrayPutsByIndex = new ArrayPut[arraySize];
Value arrayValue = newArrayEmpty.outValue();
BasicBlock usageBlock = firstUser.getBlock();
// Collect array-puts from non-usage blocks, and (optionally) check for multiple users.
for (Instruction user : arrayValue.uniqueUsers()) {
ArrayPut arrayPut = user.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
if (user == firstUser) {
continue;
}
// Found a second non-array-put user.
if (allowOtherUsers) {
continue;
}
return null;
} else if (arrayPut.value() == arrayValue) {
// An array that contains itself is uncommon and hard to reason about.
// e.g.: arr[0] = arr;
return null;
}
// Process same-block instructions later.
if (user.getBlock() == usageBlock) {
continue;
}
int index = arrayPut.indexIfConstAndInBounds(arraySize);
// We do not know what order blocks are in, so do not allow re-assignment.
if (index < 0 || arrayPutsByIndex[index] != null) {
return null;
}
arrayPutsByIndex[index] = arrayPut;
}
// Ensure that all paths from new-array-empty's block to |usage|'s block contain all array-put
// instructions.
DominatorChecker dominatorChecker =
DominatorChecker.create(newArrayEmpty.getBlock(), usageBlock);
// Visit in reverse order because array-puts generally appear in order, and DominatorChecker's
// cache is more effective when visiting in reverse order.
for (int i = arraySize - 1; i >= 0; --i) {
ArrayPut arrayPut = arrayPutsByIndex[i];
if (arrayPut != null && !dominatorChecker.check(arrayPut.getBlock())) {
return null;
}
}
// Collect array-puts from the usage block, and ensure no array-puts come after the first user.
boolean seenFirstUser = false;
for (Instruction inst : usageBlock.getInstructions()) {
if (inst == firstUser) {
seenFirstUser = true;
continue;
}
ArrayPut arrayPut = inst.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
continue;
}
if (seenFirstUser) {
// Found an array-put after the array was used. This is too uncommon of a thing to support.
return null;
}
int index = arrayPut.indexIfConstAndInBounds(arraySize);
if (index < 0) {
return null;
}
// Do not allow re-assignment so that we can use arrayPutsByIndex to find all array-put
// instructions.
if (arrayPutsByIndex[index] != null) {
return null;
}
arrayPutsByIndex[index] = arrayPut;
}
return new ArrayValues(arrayValue, arrayPutsByIndex);
}
}