blob: 8532dcec9eb180d9ce5a8224697b83a131f61be1 [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 java.util.Collections.emptySet;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.ir.analysis.inlining.NeverSimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
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.InvokeDirect;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import java.util.BitSet;
import java.util.Set;
public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
private Set<DexType> initializedClassesOnNormalExit =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
private int returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
private AbstractValue abstractReturnValue =
DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
private ClassInlinerMethodConstraint classInlinerConstraint =
ClassInlinerMethodConstraint.alwaysFalse();
private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
private ClassTypeElement returnsObjectWithLowerBoundType =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
private InlinePreference inlining = InlinePreference.Default;
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private BridgeInfo bridgeInfo = null;
private InstanceInitializerInfoCollection instanceInitializerInfoCollection =
InstanceInitializerInfoCollection.empty();
// 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 SimpleInliningConstraint simpleInliningConstraint =
NeverSimpleInliningConstraint.getInstance();
// To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
// merged into a flag int field. The various static final FLAG fields indicate which bit is
// used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
// is computed during class initialization from the default method optimization info. The
// methods setFlag, clearFlag and isFlagSet are used to access the booleans.
private static final int CANNOT_BE_KEPT_FLAG = 0x1;
private static final int CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG = 0x2;
private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4;
private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8;
private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
private static final int UNUSED_FLAG_1 = 0x20;
private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
private static final int UNUSED_FLAG_2 = 0x80;
private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
private static final int REACHABILITY_SENSITIVE_FLAG = 0x800;
private static final int RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG = 0x1000;
private static final int DEFAULT_FLAGS;
static {
int defaultFlags = 0;
MethodOptimizationInfo defaultOptInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
defaultFlags |= BooleanUtils.intValue(defaultOptInfo.cannotBeKept()) * CANNOT_BE_KEPT_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.classInitializerMayBePostponed())
* CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.hasBeenInlinedIntoSingleCallSite())
* HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.mayHaveSideEffects()) * MAY_HAVE_SIDE_EFFECT_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments())
* RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG;
defaultFlags |= 0 * UNUSED_FLAG_1;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
defaultFlags |= 0 * UNUSED_FLAG_2;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
* CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.triggersClassInitBeforeAnySideEffect())
* TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaVmAssertions())
* INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.isReachabilitySensitive())
* REACHABILITY_SENSITIVE_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.returnValueHasBeenPropagated())
* RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG;
DEFAULT_FLAGS = defaultFlags;
}
private int flags = DEFAULT_FLAGS;
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) {
flags = template.flags;
initializedClassesOnNormalExit = template.initializedClassesOnNormalExit;
returnedArgument = template.returnedArgument;
abstractReturnValue = template.abstractReturnValue;
returnsObjectWithUpperBoundType = template.returnsObjectWithUpperBoundType;
returnsObjectWithLowerBoundType = template.returnsObjectWithLowerBoundType;
inlining = template.inlining;
simpleInliningConstraint = template.simpleInliningConstraint;
bridgeInfo = template.bridgeInfo;
instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
}
public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
return fixupClassTypeReferences(appView, lens, emptySet());
}
public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
AppView<? extends AppInfoWithClassHierarchy> appView,
GraphLens lens,
Set<DexType> prunedTypes) {
if (returnsObjectWithUpperBoundType != null) {
returnsObjectWithUpperBoundType =
returnsObjectWithUpperBoundType.rewrittenWithLens(appView, lens, prunedTypes);
}
if (returnsObjectWithLowerBoundType != null) {
TypeElement returnsObjectWithLowerBoundType =
this.returnsObjectWithLowerBoundType.rewrittenWithLens(appView, lens, prunedTypes);
if (returnsObjectWithLowerBoundType.isClassType()) {
this.returnsObjectWithLowerBoundType = returnsObjectWithLowerBoundType.asClassType();
} else {
assert returnsObjectWithLowerBoundType.isPrimitiveType();
this.returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
this.returnsObjectWithLowerBoundType = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
}
}
return this;
}
public UpdatableMethodOptimizationInfo fixupAbstractReturnValue(
AppView<AppInfoWithLiveness> appView, GraphLens lens) {
abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens);
return this;
}
public UpdatableMethodOptimizationInfo fixupInstanceInitializerInfo(
AppView<AppInfoWithLiveness> appView, GraphLens lens) {
instanceInitializerInfoCollection =
instanceInitializerInfoCollection.rewrittenWithLens(appView, lens);
return this;
}
private void setFlag(int flag, boolean value) {
if (value) {
setFlag(flag);
} else {
clearFlag(flag);
}
}
private void setFlag(int flag) {
flags |= flag;
}
private void clearFlag(int flag) {
flags &= ~flag;
}
private boolean isFlagSet(int flag) {
return (flags & flag) != 0;
}
@Override
public boolean isDefaultMethodOptimizationInfo() {
return false;
}
@Override
public boolean isUpdatableMethodOptimizationInfo() {
return true;
}
@Override
public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
return this;
}
@Override
public boolean cannotBeKept() {
return isFlagSet(CANNOT_BE_KEPT_FLAG);
}
// TODO(b/140214568): Should be package-private.
public void markCannotBeKept() {
setFlag(CANNOT_BE_KEPT_FLAG);
}
@Override
public boolean classInitializerMayBePostponed() {
return isFlagSet(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
}
void markClassInitializerMayBePostponed() {
setFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
}
@Override
public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
return classInlinerConstraint;
}
void setClassInlinerMethodConstraint(ClassInlinerMethodConstraint classInlinerConstraint) {
this.classInlinerConstraint = classInlinerConstraint;
}
@Override
public TypeElement getDynamicUpperBoundType() {
return returnsObjectWithUpperBoundType;
}
@Override
public ClassTypeElement getDynamicLowerBoundType() {
return returnsObjectWithLowerBoundType;
}
@Override
public Set<DexType> getInitializedClassesOnNormalExit() {
return initializedClassesOnNormalExit;
}
@Override
public InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo() {
return instanceInitializerInfoCollection.getContextInsensitive();
}
@Override
public InstanceInitializerInfo getInstanceInitializerInfo(InvokeDirect invoke) {
return instanceInitializerInfoCollection.get(invoke);
}
@Override
public BitSet getNonNullParamOrThrow() {
return nonNullParamOrThrow;
}
@Override
public BitSet getNonNullParamOnNormalExits() {
return nonNullParamOnNormalExits;
}
@Override
public boolean hasBeenInlinedIntoSingleCallSite() {
return isFlagSet(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
}
void markInlinedIntoSingleCallSite() {
setFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
}
@Override
public boolean isReachabilitySensitive() {
return isFlagSet(REACHABILITY_SENSITIVE_FLAG);
}
@Override
public boolean returnsArgument() {
return returnedArgument != -1;
}
@Override
public int getReturnedArgument() {
assert returnsArgument();
return returnedArgument;
}
@Override
public boolean neverReturnsNormally() {
return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG);
}
@Override
public BridgeInfo getBridgeInfo() {
return bridgeInfo;
}
void setBridgeInfo(BridgeInfo bridgeInfo) {
this.bridgeInfo = bridgeInfo;
}
@Override
public AbstractValue getAbstractReturnValue() {
return abstractReturnValue;
}
@Override
public SimpleInliningConstraint getSimpleInliningConstraint() {
return simpleInliningConstraint;
}
@Override
public boolean isInitializerEnablingJavaVmAssertions() {
return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
@Override
public boolean forceInline() {
return inlining == InlinePreference.ForceInline;
}
@Override
public boolean neverInline() {
return inlining == InlinePreference.NeverInline;
}
@Override
public boolean checksNullReceiverBeforeAnySideEffect() {
return isFlagSet(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
}
@Override
public boolean triggersClassInitBeforeAnySideEffect() {
return isFlagSet(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG);
}
@Override
public boolean mayHaveSideEffects() {
return isFlagSet(MAY_HAVE_SIDE_EFFECT_FLAG);
}
@Override
public boolean returnValueOnlyDependsOnArguments() {
return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
}
void setNonNullParamOrThrow(BitSet facts) {
this.nonNullParamOrThrow = facts;
}
void setNonNullParamOnNormalExits(BitSet facts) {
this.nonNullParamOnNormalExits = facts;
}
public void setReachabilitySensitive(boolean reachabilitySensitive) {
setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
}
void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
this.simpleInliningConstraint = constraint;
}
void setInstanceInitializerInfoCollection(
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
this.instanceInitializerInfoCollection = instanceInitializerInfoCollection;
}
void setInitializerEnablingJavaAssertions() {
setFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
}
void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
void markMayNotHaveSideEffects() {
clearFlag(MAY_HAVE_SIDE_EFFECT_FLAG);
}
void markReturnValueOnlyDependsOnArguments() {
setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
}
void markNeverReturnsNormally() {
setFlag(NEVER_RETURNS_NORMALLY_FLAG);
}
void markReturnsAbstractValue(AbstractValue value) {
assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
: "return single value changed from " + abstractReturnValue + " to " + value;
abstractReturnValue = value;
}
void unsetAbstractReturnValue() {
abstractReturnValue = UnknownValue.getInstance();
}
void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeElement 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;
if (type.isNullType()) {
returnsObjectWithLowerBoundType = null;
}
}
void markReturnsObjectWithLowerBoundType(ClassTypeElement 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)
: "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;
}
void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
setFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
// TODO(b/140214568): Should be package-private.
public void markAsPropagated() {
setFlag(RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG);
}
@Override
public boolean returnValueHasBeenPropagated() {
return isFlagSet(RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG);
}
@Override
public UpdatableMethodOptimizationInfo mutableCopy() {
return new UpdatableMethodOptimizationInfo(this);
}
public void adjustOptimizationInfoAfterRemovingThisParameter(
AppView<AppInfoWithLiveness> appView) {
classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
simpleInliningConstraint =
simpleInliningConstraint.fixupAfterRemovingThisParameter(
appView.simpleInliningConstraintFactory());
// cannotBeKept: doesn't depend on `this`
// classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
// hasBeenInlinedIntoSingleCallSite: then it should not be staticized.
clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
// 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;
// checksNullReceiverBeforeAnySideEffect: no more receiver.
markCheckNullReceiverBeforeAnySideEffect(
DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT);
// triggersClassInitBeforeAnySideEffect: code is not changed.
markTriggerClassInitBeforeAnySideEffect(
DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT);
// initializerInfo: the computed initializer info may become invalid.
instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty();
// initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
setFlag(
INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG,
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS);
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`
}
}