blob: 230b15b566f253fa398002b92a2c9decbef0f715 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.info;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.utils.MapUtils.canonicalizeEmptyMap;
import static java.util.Objects.requireNonNull;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.List;
// Accumulated optimization info from call sites.
public class ConcreteCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
// inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
// That is, this information takes into account the receiver as well.
private final int size;
private final Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes;
private final Int2ReferenceMap<AbstractValue> constants;
private ConcreteCallSiteOptimizationInfo(int size) {
this(size, new Int2ReferenceArrayMap<>(size), new Int2ReferenceArrayMap<>(size));
}
private ConcreteCallSiteOptimizationInfo(
int size,
Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes,
Int2ReferenceMap<AbstractValue> constants) {
assert size > 0;
this.size = size;
this.dynamicUpperBoundTypes = requireNonNull(dynamicUpperBoundTypes);
this.constants = requireNonNull(constants);
}
private static CallSiteOptimizationInfo create(
int size,
Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes,
Int2ReferenceMap<AbstractValue> constants) {
return constants.isEmpty() && dynamicUpperBoundTypes.isEmpty()
? top()
: new ConcreteCallSiteOptimizationInfo(
size, canonicalizeEmptyMap(dynamicUpperBoundTypes), canonicalizeEmptyMap(constants));
}
public CallSiteOptimizationInfo fixupAfterExtraNullParameters(int extraNullParameters) {
return extraNullParameters > 0
? new ConcreteCallSiteOptimizationInfo(
size + extraNullParameters, dynamicUpperBoundTypes, constants)
: this;
}
public CallSiteOptimizationInfo fixupAfterParametersChanged(
RewrittenPrototypeDescription prototypeChanges) {
if (prototypeChanges.isEmpty()) {
return this;
}
ArgumentInfoCollection parameterChanges = prototypeChanges.getArgumentInfoCollection();
if (parameterChanges.isEmpty()) {
if (prototypeChanges.hasExtraParameters()) {
return new ConcreteCallSiteOptimizationInfo(
size + prototypeChanges.numberOfExtraParameters(), dynamicUpperBoundTypes, constants);
}
return this;
}
assert parameterChanges.getRemovedParameterIndices().stream()
.allMatch(removedParameterIndex -> removedParameterIndex < size);
int newSizeAfterParameterRemoval = size - parameterChanges.numberOfRemovedArguments();
if (newSizeAfterParameterRemoval == 0) {
return top();
}
Int2ReferenceMap<AbstractValue> rewrittenConstants =
new Int2ReferenceArrayMap<>(newSizeAfterParameterRemoval);
Int2ReferenceMap<TypeElement> rewrittenDynamicUpperBoundTypes =
new Int2ReferenceArrayMap<>(newSizeAfterParameterRemoval);
for (int parameterIndex = 0, rewrittenParameterIndex = 0;
parameterIndex < size;
parameterIndex++) {
if (!parameterChanges.isArgumentRemoved(parameterIndex)) {
AbstractValue abstractValue =
constants.getOrDefault(parameterIndex, AbstractValue.unknown());
if (!abstractValue.isUnknown()) {
rewrittenConstants.put(rewrittenParameterIndex, abstractValue);
}
TypeElement dynamicUpperBoundType = dynamicUpperBoundTypes.get(parameterIndex);
if (dynamicUpperBoundType != null) {
rewrittenDynamicUpperBoundTypes.put(rewrittenParameterIndex, dynamicUpperBoundType);
}
rewrittenParameterIndex++;
}
}
return ConcreteCallSiteOptimizationInfo.create(
newSizeAfterParameterRemoval + prototypeChanges.numberOfExtraParameters(),
rewrittenDynamicUpperBoundTypes,
rewrittenConstants);
}
CallSiteOptimizationInfo join(
ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
assert size == other.size;
assert size == method.getNumberOfArguments();
ConcreteCallSiteOptimizationInfo result = new ConcreteCallSiteOptimizationInfo(size);
for (int i = 0; i < result.size; i++) {
AbstractValue abstractValue =
getAbstractArgumentValue(i)
.join(
other.getAbstractArgumentValue(i),
appView.abstractValueFactory(),
method.getArgumentType(i));
if (abstractValue.isNonTrivial()) {
result.constants.put(i, abstractValue);
}
TypeElement thisUpperBoundType = getDynamicUpperBoundType(i);
if (thisUpperBoundType == null) {
// This means the corresponding argument is primitive. The counterpart should be too.
assert other.getDynamicUpperBoundType(i) == null;
continue;
}
assert thisUpperBoundType.isReferenceType();
TypeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
assert otherUpperBoundType != null && otherUpperBoundType.isReferenceType();
result.dynamicUpperBoundTypes.put(
i, thisUpperBoundType.join(otherUpperBoundType, appView));
}
if (result.hasUsefulOptimizationInfo(appView, method)) {
return result;
}
// As soon as we know the argument collection so far does not have any useful optimization info,
// move to TOP so that further collection can be simply skipped.
return top();
}
private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod method) {
int argOffset = method.getFirstNonReceiverArgumentIndex();
int size = method.getReference().getArity() + argOffset;
TypeElement[] staticTypes = new TypeElement[size];
if (!method.isStatic()) {
staticTypes[0] =
TypeElement.fromDexType(method.getHolderType(), definitelyNotNull(), appView);
}
for (int i = 0; i < method.getReference().getArity(); i++) {
staticTypes[i + argOffset] =
TypeElement.fromDexType(method.getParameter(i), maybeNull(), appView);
}
return staticTypes;
}
@Override
public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
TypeElement[] staticTypes = getStaticTypes(appView, method);
for (int i = 0; i < size; i++) {
AbstractValue abstractValue = getAbstractArgumentValue(i);
if (abstractValue.isNonTrivial()) {
return true;
}
if (!staticTypes[i].isReferenceType()) {
continue;
}
TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
if (dynamicUpperBoundType == null) {
continue;
}
// To avoid the full join of type lattices below, separately check if the nullability of
// arguments is improved, and if so, we can eagerly conclude that we've collected useful
// call site information for this method.
Nullability nullability = dynamicUpperBoundType.nullability();
if (nullability.isDefinitelyNull()) {
return true;
}
// TODO(b/139246447): Similar to nullability, if dynamic lower bound type is available,
// we stop here and regard that call sites of this method have useful info.
// In general, though, we're looking for (strictly) better dynamic types for arguments.
if (dynamicUpperBoundType.strictlyLessThan(staticTypes[i], appView)) {
return true;
}
}
return false;
}
@Override
public TypeElement getDynamicUpperBoundType(int argIndex) {
assert 0 <= argIndex && argIndex < size;
return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
}
@Override
public AbstractValue getAbstractArgumentValue(int argIndex) {
assert 0 <= argIndex && argIndex < size;
return constants.getOrDefault(argIndex, UnknownValue.getInstance());
}
public static CallSiteOptimizationInfo fromArguments(
AppView<AppInfoWithLiveness> appView,
DexMethod invokedMethod,
List<Value> arguments,
ProgramMethod context) {
ConcreteCallSiteOptimizationInfo newCallSiteInfo =
new ConcreteCallSiteOptimizationInfo(arguments.size());
boolean hasReceiver = arguments.size() > invokedMethod.getArity();
boolean isTop = true;
for (int i = 0; i < newCallSiteInfo.size; i++) {
Value arg = arguments.get(i);
// Constant propagation.
Value aliasedValue = arg.getAliasedValue();
if (!aliasedValue.isPhi()) {
AbstractValue abstractValue = aliasedValue.definition.getAbstractValue(appView, context);
if (abstractValue.isNonTrivial()) {
newCallSiteInfo.constants.put(i, abstractValue);
isTop = false;
}
}
// Type propagation.
if (arg.getType().isReferenceType()) {
TypeElement staticType =
TypeElement.fromDexType(
hasReceiver ? invokedMethod.holder : invokedMethod.proto.getParameter(i),
maybeNull(),
appView);
TypeElement dynamicUpperBoundType = arg.getDynamicUpperBoundType(appView);
if (dynamicUpperBoundType != staticType) {
newCallSiteInfo.dynamicUpperBoundTypes.put(i, dynamicUpperBoundType);
isTop = false;
} else {
newCallSiteInfo.dynamicUpperBoundTypes.put(i, staticType);
}
}
}
return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
}
public static CallSiteOptimizationInfo fromMethodState(
AppView<AppInfoWithLiveness> appView,
ProgramMethod method,
ConcreteMonomorphicMethodState methodState) {
ConcreteCallSiteOptimizationInfo newCallSiteInfo =
new ConcreteCallSiteOptimizationInfo(methodState.size());
boolean isTop = true;
for (int argumentIndex = 0; argumentIndex < methodState.size(); argumentIndex++) {
ParameterState parameterState = methodState.getParameterState(argumentIndex);
if (parameterState.isUnknown()) {
continue;
}
ConcreteParameterState concreteParameterState = parameterState.asConcrete();
// Constant propagation.
AbstractValue abstractValue = concreteParameterState.getAbstractValue(appView);
if (abstractValue.isNonTrivial()) {
newCallSiteInfo.constants.put(argumentIndex, abstractValue);
isTop = false;
}
// Type propagation.
DexType staticType = method.getDefinition().getArgumentType(argumentIndex);
if (staticType.isReferenceType()) {
TypeElement staticTypeElement = staticType.toTypeElement(appView);
if (staticType.isArrayType()) {
Nullability nullability = concreteParameterState.asArrayParameter().getNullability();
if (nullability.isDefinitelyNull()) {
newCallSiteInfo.constants.put(
argumentIndex, appView.abstractValueFactory().createNullValue());
isTop = false;
} else if (nullability.isDefinitelyNotNull()) {
newCallSiteInfo.dynamicUpperBoundTypes.put(
argumentIndex, staticTypeElement.asArrayType().asDefinitelyNotNull());
isTop = false;
} else {
// The nullability should never be unknown, since we should use the unknown method state
// in this case. It should also not be bottom, since we should change the method's body
// to throw null in this case.
assert false;
}
} else if (staticType.isClassType()) {
DynamicType dynamicType = concreteParameterState.asReferenceParameter().getDynamicType();
if (!dynamicType.isUnknown()) {
newCallSiteInfo.dynamicUpperBoundTypes.put(
argumentIndex, dynamicType.getDynamicUpperBoundType());
isTop = false;
}
}
}
}
return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
}
@Override
public boolean isConcreteCallSiteOptimizationInfo() {
return true;
}
@Override
public ConcreteCallSiteOptimizationInfo asConcreteCallSiteOptimizationInfo() {
return this;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ConcreteCallSiteOptimizationInfo)) {
return false;
}
ConcreteCallSiteOptimizationInfo otherInfo = (ConcreteCallSiteOptimizationInfo) other;
return dynamicUpperBoundTypes.equals(otherInfo.dynamicUpperBoundTypes)
&& constants.equals(otherInfo.constants);
}
@Override
public int hashCode() {
return System.identityHashCode(dynamicUpperBoundTypes) * 7 + System.identityHashCode(constants);
}
@Override
public String toString() {
return dynamicUpperBoundTypes.toString()
+ (constants == null ? "" : (System.lineSeparator() + constants));
}
}