| // 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.AppView; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.PrunedItems; |
| import com.android.tools.r8.graph.lens.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.DynamicType; |
| import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; |
| 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.code.InvokeMethod; |
| import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; |
| import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification; |
| 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.optimize.argumentpropagation.codescanner.AbstractFunction; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.MaximumRemovedAndroidLogLevelRule; |
| import com.android.tools.r8.utils.BitSetUtils; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.OptionalBool; |
| import java.util.BitSet; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| public class MutableMethodOptimizationInfo extends MethodOptimizationInfo |
| implements MutableOptimizationInfo { |
| |
| private CallSiteOptimizationInfo argumentInfos = CallSiteOptimizationInfo.top(); |
| private Set<DexType> initializedClassesOnNormalExit = |
| DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT; |
| private int returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT; |
| private AbstractFunction abstractFunction = AbstractFunction.unknown(); |
| private AbstractValue abstractReturnValue = |
| DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE; |
| private ClassInlinerMethodConstraint classInlinerConstraint = |
| ClassInlinerMethodConstraint.alwaysFalse(); |
| private boolean convertCheckNotNull = false; |
| private EnumUnboxerMethodClassification enumUnboxerMethodClassification = |
| EnumUnboxerMethodClassification.unknown(); |
| private DynamicType dynamicType = DynamicType.unknown(); |
| private InlinePreference inlining = InlinePreference.Default; |
| private OptionalBool isReturnValueUsed = OptionalBool.unknown(); |
| // 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 nopInliningConstraint = |
| NeverSimpleInliningConstraint.getInstance(); |
| private SimpleInliningConstraint simpleInliningConstraint = |
| NeverSimpleInliningConstraint.getInstance(); |
| |
| private int maxRemovedAndroidLogLevel = MaximumRemovedAndroidLogLevelRule.NOT_SET; |
| private BitSet parametersWithBitwiseOperations = null; |
| private BitSet unusedArguments = null; |
| |
| // 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 NEVER_RETURNS_NORMALLY_FLAG = 0x20; |
| private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x80; |
| private static final int RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG = 0x100; |
| |
| 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 |= |
| BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG; |
| defaultFlags |= |
| BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaVmAssertions()) |
| * INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG; |
| defaultFlags |= |
| BooleanUtils.intValue(defaultOptInfo.returnValueHasBeenPropagated()) |
| * RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG; |
| DEFAULT_FLAGS = defaultFlags; |
| } |
| |
| private int flags = DEFAULT_FLAGS; |
| |
| MutableMethodOptimizationInfo() { |
| // 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 MutableMethodOptimizationInfo(MutableMethodOptimizationInfo template) { |
| abstractFunction = template.abstractFunction; |
| argumentInfos = template.argumentInfos; |
| flags = template.flags; |
| initializedClassesOnNormalExit = template.initializedClassesOnNormalExit; |
| returnedArgument = template.returnedArgument; |
| abstractReturnValue = template.abstractReturnValue; |
| setDynamicType(template.dynamicType); |
| inlining = template.inlining; |
| nopInliningConstraint = template.nopInliningConstraint; |
| simpleInliningConstraint = template.simpleInliningConstraint; |
| bridgeInfo = template.bridgeInfo; |
| instanceInitializerInfoCollection = template.instanceInitializerInfoCollection; |
| nonNullParamOrThrow = template.nonNullParamOrThrow; |
| nonNullParamOnNormalExits = template.nonNullParamOnNormalExits; |
| classInlinerConstraint = template.classInlinerConstraint; |
| enumUnboxerMethodClassification = template.enumUnboxerMethodClassification; |
| maxRemovedAndroidLogLevel = template.maxRemovedAndroidLogLevel; |
| } |
| |
| public MutableMethodOptimizationInfo applyIf( |
| boolean condition, |
| Consumer<MutableMethodOptimizationInfo> thenConsumer, |
| Consumer<MutableMethodOptimizationInfo> elseConsumer) { |
| if (condition) { |
| thenConsumer.accept(this); |
| } else { |
| elseConsumer.accept(this); |
| } |
| return this; |
| } |
| |
| public MutableMethodOptimizationInfo fixup( |
| AppView<AppInfoWithLiveness> appView, |
| DexEncodedMethod method, |
| MethodOptimizationInfoFixer fixer) { |
| return fixupArgumentInfos(method, fixer) |
| .fixupBridgeInfo(fixer) |
| .fixupClassInlinerMethodConstraint(appView, fixer) |
| .fixupDynamicType(fixer) |
| .fixupAbstractReturnValue(appView, fixer) |
| .fixupEnumUnboxerMethodClassification(fixer) |
| .fixupInstanceInitializerInfo(appView, fixer) |
| .fixupNonNullParamOnNormalExits(fixer) |
| .fixupNonNullParamOrThrow(fixer) |
| .fixupReturnedArgumentIndex(fixer) |
| .fixupParametersWithBitwiseOperations(fixer) |
| .fixupNopInliningConstraint(appView, fixer) |
| .fixupSimpleInliningConstraint(appView, fixer) |
| .fixupUnusedArguments(fixer); |
| } |
| |
| private MutableMethodOptimizationInfo fixupDynamicType(MethodOptimizationInfoFixer fixer) { |
| if (dynamicType.isUnknown()) { |
| return this; |
| } |
| return setDynamicType(fixer.fixupDynamicType(dynamicType)); |
| } |
| |
| public MutableMethodOptimizationInfo fixupClassTypeReferences( |
| AppView<AppInfoWithLiveness> appView, GraphLens lens) { |
| return fixupClassTypeReferences(appView, lens, emptySet()); |
| } |
| |
| public MutableMethodOptimizationInfo fixupClassTypeReferences( |
| AppView<AppInfoWithLiveness> appView, GraphLens lens, Set<DexType> prunedTypes) { |
| DynamicType rewrittenDynamicType = dynamicType.rewrittenWithLens(appView, lens, prunedTypes); |
| if (rewrittenDynamicType.hasDynamicUpperBoundType()) { |
| DynamicTypeWithUpperBound rewrittenDynamicTypeWithUpperBound = |
| rewrittenDynamicType.asDynamicTypeWithUpperBound(); |
| if (rewrittenDynamicTypeWithUpperBound.getDynamicUpperBoundType().isPrimitiveType()) { |
| // Do not store primitive dynamic types. |
| assert verifyDynamicTypeIsUnboxedEnum(appView, dynamicType); |
| return unsetDynamicType(); |
| } |
| } |
| return setDynamicType(rewrittenDynamicType); |
| } |
| |
| private static boolean verifyDynamicTypeIsUnboxedEnum( |
| AppView<?> appView, DynamicType dynamicType) { |
| assert dynamicType.isDynamicTypeWithUpperBound(); |
| DynamicTypeWithUpperBound dynamicTypeWithUpperBound = dynamicType.asDynamicTypeWithUpperBound(); |
| TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType(); |
| assert dynamicUpperBoundType.isClassType(); |
| ClassTypeElement dynamicUpperBoundClassType = dynamicUpperBoundType.asClassType(); |
| assert appView.hasUnboxedEnums(); |
| assert appView.unboxedEnums().isUnboxedEnum(dynamicUpperBoundClassType.getClassType()); |
| return true; |
| } |
| |
| public MutableMethodOptimizationInfo fixupAbstractReturnValue( |
| AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) { |
| if (abstractReturnValue.isUnknown()) { |
| return this; |
| } |
| abstractReturnValue = fixer.fixupAbstractReturnValue(appView, abstractReturnValue); |
| return this; |
| } |
| |
| public MutableMethodOptimizationInfo fixupAbstractReturnValue( |
| AppView<AppInfoWithLiveness> appView, |
| DexEncodedMethod method, |
| GraphLens lens, |
| GraphLens codeLens) { |
| abstractReturnValue = |
| abstractReturnValue.rewrittenWithLens(appView, method.getReturnType(), lens, codeLens); |
| return this; |
| } |
| |
| public MutableMethodOptimizationInfo fixupInstanceInitializerInfo( |
| AppView<AppInfoWithLiveness> appView, |
| GraphLens lens, |
| GraphLens codeLens, |
| PrunedItems prunedItems) { |
| instanceInitializerInfoCollection = |
| instanceInitializerInfoCollection.rewrittenWithLens(appView, lens, codeLens, prunedItems); |
| return this; |
| } |
| |
| 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 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); |
| } |
| |
| void unsetClassInitializerMayBePostponed() { |
| clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG); |
| } |
| |
| @Override |
| public CallSiteOptimizationInfo getArgumentInfos() { |
| return argumentInfos; |
| } |
| |
| public MutableMethodOptimizationInfo fixupArgumentInfos( |
| DexEncodedMethod method, MethodOptimizationInfoFixer fixer) { |
| if (argumentInfos.isConcreteCallSiteOptimizationInfo()) { |
| return setArgumentInfos( |
| method, |
| fixer.fixupCallSiteOptimizationInfo(argumentInfos.asConcreteCallSiteOptimizationInfo())); |
| } |
| return this; |
| } |
| |
| MutableMethodOptimizationInfo setArgumentInfos( |
| // Method reference allows easily debugging when the optimization info for a given method |
| // changes. |
| @SuppressWarnings("unused") DexEncodedMethod method, CallSiteOptimizationInfo argumentInfos) { |
| this.argumentInfos = argumentInfos; |
| return this; |
| } |
| |
| public void unsetArgumentInfos() { |
| argumentInfos = CallSiteOptimizationInfo.top(); |
| } |
| |
| @Override |
| public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() { |
| return classInlinerConstraint; |
| } |
| |
| public MutableMethodOptimizationInfo fixupClassInlinerMethodConstraint( |
| AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) { |
| classInlinerConstraint = |
| fixer.fixupClassInlinerMethodConstraint(appView, classInlinerConstraint); |
| return this; |
| } |
| |
| void setClassInlinerMethodConstraint(ClassInlinerMethodConstraint classInlinerConstraint) { |
| this.classInlinerConstraint = classInlinerConstraint; |
| } |
| |
| void unsetClassInlinerMethodConstraint() { |
| this.classInlinerConstraint = ClassInlinerMethodConstraint.alwaysFalse(); |
| } |
| |
| void setConvertCheckNotNull() { |
| this.convertCheckNotNull = true; |
| } |
| |
| @Override |
| public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() { |
| return enumUnboxerMethodClassification; |
| } |
| |
| public MutableMethodOptimizationInfo setEnumUnboxerMethodClassification( |
| EnumUnboxerMethodClassification enumUnboxerMethodClassification) { |
| // Check monotonicity. |
| assert !this.enumUnboxerMethodClassification.isCheckNotNullClassification() |
| || enumUnboxerMethodClassification.isCheckNotNullClassification(); |
| this.enumUnboxerMethodClassification = enumUnboxerMethodClassification; |
| return this; |
| } |
| |
| public void unsetEnumUnboxerMethodClassification() { |
| this.enumUnboxerMethodClassification = EnumUnboxerMethodClassification.unknown(); |
| } |
| |
| public MutableMethodOptimizationInfo fixupEnumUnboxerMethodClassification( |
| MethodOptimizationInfoFixer fixer) { |
| enumUnboxerMethodClassification = |
| fixer.fixupEnumUnboxerMethodClassification(enumUnboxerMethodClassification); |
| return this; |
| } |
| |
| @Override |
| public DynamicType getDynamicType() { |
| return dynamicType; |
| } |
| |
| @Override |
| public Set<DexType> getInitializedClassesOnNormalExit() { |
| return initializedClassesOnNormalExit; |
| } |
| |
| @Override |
| public InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo() { |
| return instanceInitializerInfoCollection.getContextInsensitive(); |
| } |
| |
| @Override |
| public InstanceInitializerInfo getInstanceInitializerInfo(InvokeDirect invoke) { |
| return instanceInitializerInfoCollection.get(invoke); |
| } |
| |
| public MutableMethodOptimizationInfo fixupInstanceInitializerInfo( |
| AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) { |
| instanceInitializerInfoCollection = |
| fixer.fixupInstanceInitializerInfo(appView, instanceInitializerInfoCollection); |
| return this; |
| } |
| |
| @Override |
| public int getMaxRemovedAndroidLogLevel() { |
| return maxRemovedAndroidLogLevel; |
| } |
| |
| public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) { |
| this.maxRemovedAndroidLogLevel = |
| MaximumRemovedAndroidLogLevelRule.joinMaxRemovedAndroidLogLevel( |
| this.maxRemovedAndroidLogLevel, maxRemovedAndroidLogLevel); |
| } |
| |
| @Override |
| public BitSet getNonNullParamOrThrow() { |
| return nonNullParamOrThrow; |
| } |
| |
| public MutableMethodOptimizationInfo fixupNonNullParamOrThrow(MethodOptimizationInfoFixer fixer) { |
| nonNullParamOrThrow = fixer.fixupNonNullParamOrThrow(nonNullParamOrThrow); |
| return this; |
| } |
| |
| void setNonNullParamOrThrow(BitSet facts) { |
| this.nonNullParamOrThrow = facts; |
| } |
| |
| void unsetNonNullParamOrThrow() { |
| this.nonNullParamOrThrow = null; |
| } |
| |
| @Override |
| public BitSet getNonNullParamOnNormalExits() { |
| return nonNullParamOnNormalExits; |
| } |
| |
| public MutableMethodOptimizationInfo fixupNonNullParamOnNormalExits( |
| MethodOptimizationInfoFixer fixer) { |
| nonNullParamOnNormalExits = fixer.fixupNonNullParamOnNormalExits(nonNullParamOnNormalExits); |
| return this; |
| } |
| |
| void setNonNullParamOnNormalExits(BitSet facts) { |
| this.nonNullParamOnNormalExits = facts; |
| } |
| |
| void unsetNonNullParamOnNormalExits() { |
| nonNullParamOnNormalExits = null; |
| } |
| |
| @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); |
| } |
| |
| void unsetInlinedIntoSingleCallSite() { |
| clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG); |
| } |
| |
| @Override |
| public boolean returnsArgument() { |
| return returnedArgument != -1; |
| } |
| |
| @Override |
| public int getReturnedArgument() { |
| return returnedArgument; |
| } |
| |
| @Override |
| public boolean neverReturnsNormally() { |
| return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG); |
| } |
| |
| @Override |
| public BridgeInfo getBridgeInfo() { |
| return bridgeInfo; |
| } |
| |
| public MutableMethodOptimizationInfo fixupBridgeInfo(MethodOptimizationInfoFixer fixer) { |
| if (bridgeInfo != null) { |
| bridgeInfo = fixer.fixupBridgeInfo(bridgeInfo); |
| } |
| return this; |
| } |
| |
| void setBridgeInfo(BridgeInfo bridgeInfo) { |
| this.bridgeInfo = bridgeInfo; |
| } |
| |
| void unsetBridgeInfo() { |
| this.bridgeInfo = null; |
| } |
| |
| @Override |
| public AbstractFunction getAbstractFunction() { |
| return abstractFunction; |
| } |
| |
| @Override |
| public AbstractValue getAbstractReturnValue() { |
| return abstractReturnValue; |
| } |
| |
| @Override |
| public SimpleInliningConstraint getNopInliningConstraint() { |
| return nopInliningConstraint; |
| } |
| |
| @Override |
| public SimpleInliningConstraint getSimpleInliningConstraint() { |
| return simpleInliningConstraint; |
| } |
| |
| @Override |
| public boolean hasParametersWithBitwiseOperations() { |
| return parametersWithBitwiseOperations != null; |
| } |
| |
| @Override |
| public BitSet getParametersWithBitwiseOperations() { |
| return parametersWithBitwiseOperations; |
| } |
| |
| public void setParametersWithBitwiseOperations(BitSet parametersWithBitwiseOperations) { |
| if (parametersWithBitwiseOperations != null && !parametersWithBitwiseOperations.isEmpty()) { |
| this.parametersWithBitwiseOperations = parametersWithBitwiseOperations; |
| } else { |
| this.parametersWithBitwiseOperations = null; |
| } |
| } |
| |
| public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( |
| MethodOptimizationInfoFixer fixer) { |
| return fixupParametersWithBitwiseOperations(fixer.fixupArguments(unusedArguments)); |
| } |
| |
| public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( |
| BitSet parametersWithBitwiseOperations) { |
| setParametersWithBitwiseOperations(parametersWithBitwiseOperations); |
| return this; |
| } |
| |
| @Override |
| public BitSet getUnusedArguments() { |
| return unusedArguments; |
| } |
| |
| public MutableMethodOptimizationInfo fixupUnusedArguments(MethodOptimizationInfoFixer fixer) { |
| return fixupUnusedArguments(fixer.fixupArguments(unusedArguments)); |
| } |
| |
| public MutableMethodOptimizationInfo fixupUnusedArguments(BitSet unusedArguments) { |
| this.unusedArguments = |
| unusedArguments != null && !unusedArguments.isEmpty() ? unusedArguments : null; |
| return this; |
| } |
| |
| void setUnusedArguments(BitSet unusedArguments) { |
| // Verify monotonicity (i.e., unused arguments should never become used). |
| assert !hasUnusedArguments() || unusedArguments != null; |
| assert !hasUnusedArguments() |
| || BitSetUtils.verifyLessThanOrEqualTo(getUnusedArguments(), unusedArguments); |
| this.unusedArguments = |
| unusedArguments != null && !unusedArguments.isEmpty() ? unusedArguments : null; |
| } |
| |
| void unsetUnusedArguments() { |
| unusedArguments = null; |
| } |
| |
| @Override |
| public boolean isConvertCheckNotNull() { |
| return convertCheckNotNull; |
| } |
| |
| @Override |
| public boolean isInitializerEnablingJavaVmAssertions() { |
| return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG); |
| } |
| |
| @Override |
| public boolean isMultiCallerMethod() { |
| return inlining == InlinePreference.MultiCallerInline; |
| } |
| |
| @Override |
| public OptionalBool isReturnValueUsed() { |
| return isReturnValueUsed; |
| } |
| |
| void setIsReturnValueUsed(OptionalBool isReturnValueUsed) { |
| this.isReturnValueUsed = isReturnValueUsed; |
| } |
| |
| @Override |
| public boolean forceInline() { |
| return inlining == InlinePreference.ForceInline; |
| } |
| |
| @Override |
| public boolean mayHaveSideEffects() { |
| return isFlagSet(MAY_HAVE_SIDE_EFFECT_FLAG); |
| } |
| |
| @Override |
| public boolean mayHaveSideEffects(InvokeMethod invoke, InternalOptions options) { |
| if (!mayHaveSideEffects()) { |
| return false; |
| } |
| if (getNopInliningConstraint().isSatisfied(invoke)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean returnValueOnlyDependsOnArguments() { |
| return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG); |
| } |
| |
| void setNopInliningConstraint(SimpleInliningConstraint constraint) { |
| this.nopInliningConstraint = constraint; |
| } |
| |
| void unsetNopInliningConstraint() { |
| nopInliningConstraint = NeverSimpleInliningConstraint.getInstance(); |
| } |
| |
| public MutableMethodOptimizationInfo fixupNopInliningConstraint( |
| AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) { |
| nopInliningConstraint = |
| fixer.fixupNopInliningConstraint( |
| appView, nopInliningConstraint, appView.simpleInliningConstraintFactory()); |
| return this; |
| } |
| |
| void setSimpleInliningConstraint(SimpleInliningConstraint constraint) { |
| this.simpleInliningConstraint = constraint; |
| } |
| |
| void unsetSimpleInliningConstraint() { |
| simpleInliningConstraint = NeverSimpleInliningConstraint.getInstance(); |
| } |
| |
| public MutableMethodOptimizationInfo fixupSimpleInliningConstraint( |
| AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) { |
| simpleInliningConstraint = |
| fixer.fixupSimpleInliningConstraint( |
| appView, simpleInliningConstraint, appView.simpleInliningConstraintFactory()); |
| return this; |
| } |
| |
| void setInstanceInitializerInfoCollection( |
| InstanceInitializerInfoCollection instanceInitializerInfoCollection) { |
| this.instanceInitializerInfoCollection = instanceInitializerInfoCollection; |
| } |
| |
| void unsetInstanceInitializerInfoCollection() { |
| instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty(); |
| } |
| |
| void setInitializerEnablingJavaAssertions() { |
| setFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG); |
| } |
| |
| void unsetInitializerEnablingJavaVmAssertions() { |
| clearFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG); |
| } |
| |
| void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) { |
| if (initializedClassesOnNormalExit.isEmpty()) { |
| unsetInitializedClassesOnNormalExit(); |
| } else { |
| this.initializedClassesOnNormalExit = initializedClassesOnNormalExit; |
| } |
| } |
| |
| void unsetInitializedClassesOnNormalExit() { |
| initializedClassesOnNormalExit = |
| DefaultMethodOptimizationInfo.getInstance().getInitializedClassesOnNormalExit(); |
| } |
| |
| void markReturnsArgument(int returnedArgumentIndex) { |
| assert returnedArgumentIndex >= 0; |
| assert returnedArgument == -1 || returnedArgument == returnedArgumentIndex; |
| returnedArgument = returnedArgumentIndex; |
| } |
| |
| void unsetReturnedArgument() { |
| returnedArgument = -1; |
| } |
| |
| public MutableMethodOptimizationInfo fixupReturnedArgumentIndex( |
| MethodOptimizationInfoFixer fixer) { |
| returnedArgument = fixer.fixupReturnedArgumentIndex(returnedArgument); |
| return this; |
| } |
| |
| void markMayNotHaveSideEffects() { |
| clearFlag(MAY_HAVE_SIDE_EFFECT_FLAG); |
| } |
| |
| void unsetMayNotHaveSideEffects() { |
| setFlag(MAY_HAVE_SIDE_EFFECT_FLAG); |
| } |
| |
| void markReturnValueOnlyDependsOnArguments() { |
| setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG); |
| } |
| |
| void unsetReturnValueOnlyDependsOnArguments() { |
| clearFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG); |
| } |
| |
| void markNeverReturnsNormally() { |
| setFlag(NEVER_RETURNS_NORMALLY_FLAG); |
| } |
| |
| void unsetNeverReturnsNormally() { |
| clearFlag(NEVER_RETURNS_NORMALLY_FLAG); |
| } |
| |
| void setAbstractReturnValue(AbstractValue value, DexEncodedMethod method) { |
| assert !value.isNull() || method.getReturnType().isReferenceType(); |
| setAbstractReturnValue(value); |
| } |
| |
| private void setAbstractReturnValue(AbstractValue value) { |
| assert !abstractReturnValue.isSingleValue() |
| || abstractReturnValue.equals(value) |
| || (abstractReturnValue.isSingleStatelessFieldValue() |
| && value.isSingleStatefulFieldValue() |
| && abstractReturnValue |
| .asSingleFieldValue() |
| .getField() |
| .isIdenticalTo(value.asSingleFieldValue().getField())) |
| : "return single value changed from " + abstractReturnValue + " to " + value; |
| abstractReturnValue = value; |
| } |
| |
| void unsetAbstractReturnValue() { |
| abstractReturnValue = UnknownValue.getInstance(); |
| } |
| |
| void setAbstractFunction(AbstractFunction abstractFunction) { |
| this.abstractFunction = abstractFunction; |
| } |
| |
| void setDynamicType(AppView<?> appView, DynamicType newDynamicType, DexEncodedMethod method) { |
| setDynamicType(appView, newDynamicType, method.getReturnType().toTypeElement(appView)); |
| } |
| |
| public void setDynamicType( |
| AppView<?> appView, DynamicType newDynamicType, TypeElement staticReturnType) { |
| assert newDynamicType != 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 verifyDynamicType(appView, newDynamicType, staticReturnType); |
| setDynamicType(newDynamicType); |
| } |
| |
| public MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) { |
| assert !dynamicType.hasDynamicUpperBoundType() |
| || !dynamicType.asDynamicTypeWithUpperBound().getDynamicUpperBoundType().isPrimitiveType(); |
| this.dynamicType = dynamicType; |
| return this; |
| } |
| |
| private boolean verifyDynamicType( |
| AppView<?> appView, DynamicType newDynamicType, TypeElement staticReturnType) { |
| if (appView.enableWholeProgramOptimizations()) { |
| TypeElement previousDynamicUpperBoundType = |
| dynamicType.getDynamicUpperBoundType(staticReturnType); |
| TypeElement newDynamicUpperBoundType = |
| newDynamicType.getDynamicUpperBoundType(staticReturnType); |
| assert newDynamicUpperBoundType.lessThanOrEqualUpToNullability( |
| previousDynamicUpperBoundType, appView) |
| : "upper bound type changed from " |
| + previousDynamicUpperBoundType |
| + " to " |
| + newDynamicUpperBoundType; |
| } |
| return true; |
| } |
| |
| public MutableMethodOptimizationInfo unsetDynamicType() { |
| return setDynamicType(DynamicType.unknown()); |
| } |
| |
| // 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; |
| } |
| |
| void unsetForceInline() { |
| inlining = InlinePreference.Default; |
| } |
| |
| void setMultiCallerMethod() { |
| if (inlining == InlinePreference.Default) { |
| inlining = InlinePreference.MultiCallerInline; |
| } else { |
| assert inlining == InlinePreference.ForceInline; |
| } |
| } |
| |
| // 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); |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| public boolean isEffectivelyDefault() { |
| DefaultMethodOptimizationInfo top = DefaultMethodOptimizationInfo.getInstance(); |
| return argumentInfos == top.getArgumentInfos() |
| && initializedClassesOnNormalExit == top.getInitializedClassesOnNormalExit() |
| && returnedArgument == top.getReturnedArgument() |
| && abstractFunction == top.getAbstractFunction() |
| && abstractReturnValue == top.getAbstractReturnValue() |
| && classInlinerConstraint == top.getClassInlinerMethodConstraint() |
| && convertCheckNotNull == top.isConvertCheckNotNull() |
| && enumUnboxerMethodClassification == top.getEnumUnboxerMethodClassification() |
| && dynamicType == top.getDynamicType() |
| && inlining == InlinePreference.Default |
| && isReturnValueUsed == top.isReturnValueUsed() |
| && bridgeInfo == top.getBridgeInfo() |
| && instanceInitializerInfoCollection.isEmpty() |
| && nonNullParamOrThrow == top.getNonNullParamOrThrow() |
| && nonNullParamOnNormalExits == top.getNonNullParamOnNormalExits() |
| && nopInliningConstraint == top.getNopInliningConstraint() |
| && simpleInliningConstraint == top.getSimpleInliningConstraint() |
| && maxRemovedAndroidLogLevel == top.getMaxRemovedAndroidLogLevel() |
| && parametersWithBitwiseOperations == top.getParametersWithBitwiseOperations() |
| && unusedArguments == top.getUnusedArguments() |
| && flags == DEFAULT_FLAGS; |
| } |
| |
| @Override |
| public boolean isMutableOptimizationInfo() { |
| return true; |
| } |
| |
| @Override |
| public MutableMethodOptimizationInfo toMutableOptimizationInfo() { |
| return this; |
| } |
| |
| @Override |
| public MutableMethodOptimizationInfo asMutableMethodOptimizationInfo() { |
| return this; |
| } |
| |
| public MutableMethodOptimizationInfo mutableCopy() { |
| return new MutableMethodOptimizationInfo(this); |
| } |
| } |