blob: d553a4c2268ca59f52fc1f6618bcd710a0c06280 [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 com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import java.util.BitSet;
import java.util.Set;
import java.util.function.Function;
public class UpdatableMethodOptimizationInfo implements MethodOptimizationInfo {
private boolean cannotBeKept = false;
private boolean classInitializerMayBePostponed = false;
private boolean hasBeenInlinedIntoSingleCallSite = false;
private Set<DexType> initializedClassesOnNormalExit =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
private int returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
private boolean mayHaveSideEffects = DefaultMethodOptimizationInfo.UNKNOWN_MAY_HAVE_SIDE_EFFECTS;
private boolean returnValueOnlyDependsOnArguments =
DefaultMethodOptimizationInfo.UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS;
private boolean neverReturnsNull = DefaultMethodOptimizationInfo.UNKNOWN_NEVER_RETURNS_NULL;
private boolean neverReturnsNormally =
DefaultMethodOptimizationInfo.UNKNOWN_NEVER_RETURNS_NORMALLY;
private AbstractValue constantValue = DefaultMethodOptimizationInfo.UNKNOWN_CONSTANT_VALUE;
private TypeLatticeElement returnsObjectWithUpperBoundType =
DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
private ClassTypeLatticeElement returnsObjectWithLowerBoundType =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
private InlinePreference inlining = InlinePreference.Default;
private boolean useIdentifierNameString =
DefaultMethodOptimizationInfo.DOES_NOT_USE_IDENTIFIER_NAME_STRING;
private boolean checksNullReceiverBeforeAnySideEffect =
DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
private boolean triggersClassInitBeforeAnySideEffect =
DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
private InitializerInfo initializerInfo = null;
private boolean initializerEnablingJavaAssertions =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
private ParameterUsagesInfo parametersUsages =
DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
// Stores information about nullability hint per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or using checkParameterIsNotNull) ensures
// the corresponding parameter is not null, or throws NPE before any other side effects.
// This info is used by {@link UninstantiatedTypeOptimization#rewriteInvoke} that replaces an
// invocation with null throwing code if an always-null argument is passed. Also used by Inliner
// to give a credit to null-safe code, e.g., Kotlin's null safe argument.
// Note that this bit set takes into account the receiver for instance methods.
private BitSet nonNullParamOrThrow =
DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
// Stores information about nullability facts per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or NPE-throwing instructions such as array
// access or another invocation) ensures the corresponding parameter is not null, and that is
// guaranteed until the normal exits. That is, if the invocation of this method is finished
// normally, the recorded parameter is definitely not null. These facts are used to propagate
// non-null information through {@link NonNullTracker}.
// Note that this bit set takes into account the receiver for instance methods.
private BitSet nonNullParamOnNormalExits =
DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
private boolean reachabilitySensitive = false;
private boolean returnValueHasBeenPropagated = false;
UpdatableMethodOptimizationInfo() {
// Intentionally left empty, just use the default values.
}
// Copy constructor used to create a mutable copy. Do not forget to copy from template when a new
// field is added.
private UpdatableMethodOptimizationInfo(UpdatableMethodOptimizationInfo template) {
cannotBeKept = template.cannotBeKept;
classInitializerMayBePostponed = template.classInitializerMayBePostponed;
hasBeenInlinedIntoSingleCallSite = template.hasBeenInlinedIntoSingleCallSite;
initializedClassesOnNormalExit = template.initializedClassesOnNormalExit;
returnedArgument = template.returnedArgument;
mayHaveSideEffects = template.mayHaveSideEffects;
returnValueOnlyDependsOnArguments = template.returnValueOnlyDependsOnArguments;
neverReturnsNull = template.neverReturnsNull;
neverReturnsNormally = template.neverReturnsNormally;
constantValue = template.constantValue;
returnsObjectWithUpperBoundType = template.returnsObjectWithUpperBoundType;
returnsObjectWithLowerBoundType = template.returnsObjectWithLowerBoundType;
inlining = template.inlining;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
triggersClassInitBeforeAnySideEffect = template.triggersClassInitBeforeAnySideEffect;
classInlinerEligibility = template.classInlinerEligibility;
initializerInfo = template.initializerInfo;
initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
parametersUsages = template.parametersUsages;
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
reachabilitySensitive = template.reachabilitySensitive;
returnValueHasBeenPropagated = template.returnValueHasBeenPropagated;
}
public void fixupClassTypeReferences(
Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
if (returnsObjectWithUpperBoundType != null) {
returnsObjectWithUpperBoundType =
returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
}
if (returnsObjectWithLowerBoundType != null) {
returnsObjectWithLowerBoundType =
returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
}
}
@Override
public boolean isDefaultMethodOptimizationInfo() {
return false;
}
@Override
public boolean isUpdatableMethodOptimizationInfo() {
return true;
}
@Override
public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
return this;
}
@Override
public boolean cannotBeKept() {
return cannotBeKept;
}
// TODO(b/140214568): Should be package-private.
public void markCannotBeKept() {
cannotBeKept = true;
}
@Override
public boolean classInitializerMayBePostponed() {
return classInitializerMayBePostponed;
}
void markClassInitializerMayBePostponed() {
classInitializerMayBePostponed = true;
}
@Override
public TypeLatticeElement getDynamicUpperBoundType() {
return returnsObjectWithUpperBoundType;
}
@Override
public ClassTypeLatticeElement getDynamicLowerBoundType() {
return returnsObjectWithLowerBoundType;
}
@Override
public Set<DexType> getInitializedClassesOnNormalExit() {
return initializedClassesOnNormalExit;
}
@Override
public ClassInitializerInfo getClassInitializerInfo() {
return initializerInfo != null ? initializerInfo.asClassInitializerInfo() : null;
}
@Override
public InstanceInitializerInfo getInstanceInitializerInfo() {
if (initializerInfo != null) {
return initializerInfo.asInstanceInitializerInfo();
}
return DefaultInstanceInitializerInfo.getInstance();
}
@Override
public ParameterUsage getParameterUsages(int parameter) {
return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
}
@Override
public BitSet getNonNullParamOrThrow() {
return nonNullParamOrThrow;
}
@Override
public BitSet getNonNullParamOnNormalExits() {
return nonNullParamOnNormalExits;
}
@Override
public boolean hasBeenInlinedIntoSingleCallSite() {
return hasBeenInlinedIntoSingleCallSite;
}
void markInlinedIntoSingleCallSite() {
hasBeenInlinedIntoSingleCallSite = true;
}
@Override
public boolean isReachabilitySensitive() {
return reachabilitySensitive;
}
@Override
public boolean returnsArgument() {
return returnedArgument != -1;
}
@Override
public int getReturnedArgument() {
assert returnsArgument();
return returnedArgument;
}
@Override
public boolean neverReturnsNull() {
return neverReturnsNull;
}
@Override
public boolean neverReturnsNormally() {
return neverReturnsNormally;
}
@Override
public boolean returnsConstantNumber() {
return constantValue.isSingleNumberValue();
}
@Override
public boolean returnsConstantString() {
return constantValue.isSingleStringValue();
}
@Override
public ClassInlinerEligibility getClassInlinerEligibility() {
return classInlinerEligibility;
}
@Override
public long getReturnedConstantNumber() {
assert returnsConstantNumber();
return constantValue.asSingleNumberValue().getValue();
}
@Override
public DexString getReturnedConstantString() {
assert returnsConstantString();
return constantValue.asSingleStringValue().getDexString();
}
@Override
public boolean isInitializerEnablingJavaAssertions() {
return initializerEnablingJavaAssertions;
}
@Override
public boolean useIdentifierNameString() {
return useIdentifierNameString;
}
@Override
public boolean forceInline() {
return inlining == InlinePreference.ForceInline;
}
@Override
public boolean neverInline() {
return inlining == InlinePreference.NeverInline;
}
@Override
public boolean checksNullReceiverBeforeAnySideEffect() {
return checksNullReceiverBeforeAnySideEffect;
}
@Override
public boolean triggersClassInitBeforeAnySideEffect() {
return triggersClassInitBeforeAnySideEffect;
}
@Override
public boolean mayHaveSideEffects() {
return mayHaveSideEffects;
}
@Override
public boolean returnValueOnlyDependsOnArguments() {
return returnValueOnlyDependsOnArguments;
}
void setParameterUsages(ParameterUsagesInfo parametersUsages) {
this.parametersUsages = parametersUsages;
}
void setNonNullParamOrThrow(BitSet facts) {
this.nonNullParamOrThrow = facts;
}
void setNonNullParamOnNormalExits(BitSet facts) {
this.nonNullParamOnNormalExits = facts;
}
public void setReachabilitySensitive(boolean reachabilitySensitive) {
this.reachabilitySensitive = reachabilitySensitive;
}
void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
this.classInlinerEligibility = eligibility;
}
void setInitializerInfo(InitializerInfo initializerInfo) {
this.initializerInfo = initializerInfo;
}
void setInitializerEnablingJavaAssertions() {
this.initializerEnablingJavaAssertions = true;
}
void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
}
void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
void markMayNotHaveSideEffects() {
mayHaveSideEffects = false;
}
void markReturnValueOnlyDependsOnArguments() {
returnValueOnlyDependsOnArguments = true;
}
void markNeverReturnsNull() {
neverReturnsNull = true;
}
void markNeverReturnsNormally() {
neverReturnsNormally = true;
}
void markReturnsConstantNumber(AppView<?> appView, long value) {
assert !constantValue.isSingleStringValue();
assert !constantValue.isSingleNumberValue()
|| constantValue.asSingleNumberValue().getValue() == value
: "return constant number changed from "
+ constantValue.asSingleNumberValue().getValue() + " to " + value;
constantValue = appView.abstractValueFactory().createSingleNumberValue(value);
}
void markReturnsConstantString(AppView<?> appView, DexString value) {
assert !constantValue.isSingleNumberValue();
assert !constantValue.isSingleStringValue()
|| constantValue.asSingleStringValue().getDexString() == value
: "return constant string changed from "
+ constantValue.asSingleStringValue().getDexString() + " to " + value;
constantValue = appView.abstractValueFactory().createSingleStringValue(value);
}
void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeLatticeElement type) {
assert type != null;
// We may get more precise type information if the method is reprocessed (e.g., due to
// optimization info collected from all call sites), and hence the
// `returnsObjectWithUpperBoundType` is allowed to become more precise.
// TODO(b/142559221): non-materializable assume instructions?
// Nullability could be less precise, though. For example, suppose a value is known to be
// non-null after a safe invocation, hence recorded with the non-null variant. If that call is
// inlined and the method is reprocessed, such non-null assumption cannot be made again.
assert returnsObjectWithUpperBoundType == DefaultMethodOptimizationInfo.UNKNOWN_TYPE
|| type.lessThanOrEqualUpToNullability(returnsObjectWithUpperBoundType, appView)
: "upper bound type changed from " + returnsObjectWithUpperBoundType + " to " + type;
returnsObjectWithUpperBoundType = type;
}
void markReturnsObjectWithLowerBoundType(ClassTypeLatticeElement type) {
assert type != null;
// Currently, we only have a lower bound type when we have _exact_ runtime type information.
// Thus, the type should never become more precise (although the nullability could).
assert returnsObjectWithLowerBoundType == DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE
|| (type.equalUpToNullability(returnsObjectWithLowerBoundType)
&& type.nullability()
.lessThanOrEqual(returnsObjectWithLowerBoundType.nullability()))
: "lower bound type changed from " + returnsObjectWithLowerBoundType + " to " + type;
returnsObjectWithLowerBoundType = type;
}
// TODO(b/140214568): Should be package-private.
public void markForceInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.ForceInline;
}
// TODO(b/140214568): Should be package-private.
public void unsetForceInline() {
// For concurrent scenarios we should allow the flag to be already unset
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.Default;
}
// TODO(b/140214568): Should be package-private.
public void markNeverInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.NeverInline;
inlining = InlinePreference.NeverInline;
}
// TODO(b/140214568): Should be package-private.
public void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
checksNullReceiverBeforeAnySideEffect = mark;
}
void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
triggersClassInitBeforeAnySideEffect = mark;
}
// TODO(b/140214568): Should be package-private.
public void markAsPropagated() {
returnValueHasBeenPropagated = true;
}
@Override
public boolean returnValueHasBeenPropagated() {
return returnValueHasBeenPropagated;
}
@Override
public UpdatableMethodOptimizationInfo mutableCopy() {
assert this != DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
return new UpdatableMethodOptimizationInfo(this);
}
public void adjustOptimizationInfoAfterRemovingThisParameter() {
// cannotBeKept: doesn't depend on `this`
// classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
classInitializerMayBePostponed = false;
// hasBeenInlinedIntoSingleCallSite: then it should not be staticized.
hasBeenInlinedIntoSingleCallSite = false;
// initializedClassesOnNormalExit: `this` could trigger <clinit> of the previous holder.
initializedClassesOnNormalExit =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
// At least, `this` pointer is not used in `returnedArgument`.
assert returnedArgument == DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
|| returnedArgument > 0;
returnedArgument =
returnedArgument == DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
? DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT : returnedArgument - 1;
// mayHaveSideEffects: `this` Argument didn't have side effects, so removing it doesn't affect
// whether or not the method may have side effects.
// returnValueOnlyDependsOnArguments:
// if the method did before, so it does even after removing `this` Argument
// code is not changed, and thus the following *return* info is not changed either.
// * neverReturnsNull
// * neverReturnsNormally
// * constantValue
// * returnsObjectWithUpperBoundType
// * returnsObjectWithLowerBoundType
// inlining: it is not inlined, and thus staticized. No more chance of inlining, though.
inlining = InlinePreference.Default;
// useIdentifierNameString: code is not changed.
// checksNullReceiverBeforeAnySideEffect: no more receiver.
checksNullReceiverBeforeAnySideEffect =
DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
// triggersClassInitBeforeAnySideEffect: code is not changed.
triggersClassInitBeforeAnySideEffect =
DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
// classInlinerEligibility: chances are the method is not an instance method anymore.
classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
// initializerInfo: the computed initializer info may become invalid.
initializerInfo = null;
// initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
initializerEnablingJavaAssertions =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
parametersUsages =
parametersUsages == DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
: parametersUsages.remove(0);
nonNullParamOrThrow =
nonNullParamOrThrow == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
: nonNullParamOrThrow.get(1, nonNullParamOrThrow.length());
nonNullParamOnNormalExits =
nonNullParamOnNormalExits
== DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS
? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS
: nonNullParamOnNormalExits.get(1, nonNullParamOnNormalExits.length());
// reachabilitySensitive: doesn't depend on `this`
// returnValueHasBeenPropagated: doesn't depend on `this`
}
}