| // Copyright (c) 2017, 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; |
| |
| import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; |
| import static com.google.common.base.Predicates.alwaysTrue; |
| |
| import com.android.tools.r8.graph.AccessControl; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClassAndField; |
| import com.android.tools.r8.graph.DexClassAndMethod; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; |
| import com.android.tools.r8.ir.analysis.type.TypeAnalysis; |
| 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.SingleValue; |
| import com.android.tools.r8.ir.analysis.value.UnknownValue; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.FieldInstruction; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.IRMetadata; |
| import com.android.tools.r8.ir.code.InstancePut; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.StaticGet; |
| import com.android.tools.r8.ir.code.StaticPut; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; |
| import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; |
| import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue; |
| import com.android.tools.r8.utils.IteratorUtils; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.google.common.collect.Sets; |
| import java.util.ListIterator; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| |
| public class MemberValuePropagation { |
| |
| private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance(); |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final Reporter reporter; |
| |
| // Fields for which we have reported warnings to due Proguard configuration rules. |
| private final Set<DexField> warnedFields = Sets.newIdentityHashSet(); |
| |
| public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) { |
| this.appView = appView; |
| this.reporter = appView.options().reporter; |
| } |
| |
| private boolean mayPropagateValueFor(DexClassAndField field) { |
| if (field.isProgramField()) { |
| return appView.appInfo().mayPropagateValueFor(field.getReference()); |
| } |
| return appView.appInfo().assumedValues.containsKey(field.getReference()) |
| || appView.appInfo().noSideEffects.containsKey(field.getReference()); |
| } |
| |
| private boolean mayPropagateValueFor(DexClassAndMethod method) { |
| if (method.isProgramMethod()) { |
| return appView.appInfo().mayPropagateValueFor(method.getReference()); |
| } |
| return appView.appInfo().assumedValues.containsKey(method.getReference()) |
| || appView.appInfo().noSideEffects.containsKey(method.getReference()); |
| } |
| |
| private Instruction createReplacementFromAssumeInfo( |
| AssumeInfo assumeInfo, IRCode code, Instruction instruction) { |
| if (!assumeInfo.hasReturnInfo()) { |
| return null; |
| } |
| |
| ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo(); |
| |
| // Check if this value can be assumed constant. |
| if (returnValueRule.isSingleValue()) { |
| if (instruction.getOutType().isReferenceType()) { |
| if (returnValueRule.getSingleValue() == 0) { |
| return appView |
| .abstractValueFactory() |
| .createNullValue() |
| .createMaterializingInstruction(appView, code, instruction); |
| } |
| return null; |
| } |
| return appView.abstractValueFactory() |
| .createSingleNumberValue(returnValueRule.getSingleValue()) |
| .createMaterializingInstruction(appView, code, instruction); |
| } |
| |
| if (returnValueRule.isField()) { |
| DexField field = returnValueRule.getField(); |
| assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView); |
| |
| DexClassAndField staticField = appView.appInfo().lookupStaticTarget(field); |
| if (staticField == null) { |
| if (warnedFields.add(field)) { |
| reporter.warning( |
| new StringDiagnostic( |
| "Field `" |
| + field.toSourceString() |
| + "` is used in an -assumevalues rule but does not exist.", |
| code.origin)); |
| } |
| return null; |
| } |
| |
| if (AccessControl.isMemberAccessible( |
| staticField, staticField.getHolder(), code.context(), appView) |
| .isTrue()) { |
| return StaticGet.builder() |
| .setField(field) |
| .setFreshOutValue(code, field.getTypeElement(appView), instruction.getLocalInfo()) |
| .build(); |
| } |
| |
| Instruction replacement = |
| staticField |
| .getDefinition() |
| .valueAsConstInstruction(code, instruction.getLocalInfo(), appView); |
| if (replacement == null) { |
| reporter.warning( |
| new StringDiagnostic( |
| "Unable to apply the rule `" |
| + returnValueRule.toString() |
| + "`: Could not determine the value of field `" |
| + field.toSourceString() |
| + "`", |
| code.origin)); |
| return null; |
| } |
| return replacement; |
| } |
| |
| return null; |
| } |
| |
| private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) { |
| if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) { |
| assert !assumeInfo.getReturnInfo().isSingleValue(); |
| value.setValueRange(assumeInfo.getReturnInfo().getValueRange()); |
| } |
| } |
| |
| private boolean applyAssumeInfoIfPossible( |
| IRCode code, |
| Set<Value> affectedValues, |
| ListIterator<BasicBlock> blocks, |
| InstructionListIterator iterator, |
| Instruction current, |
| AssumeInfo assumeInfo) { |
| Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current); |
| if (replacement == null) { |
| // Check to see if a value range can be assumed. |
| if (current.getOutType().isPrimitiveType()) { |
| setValueRangeFromAssumeInfo(assumeInfo, current.outValue()); |
| } |
| return false; |
| } |
| affectedValues.addAll(current.outValue().affectedValues()); |
| if (assumeInfo.isAssumeNoSideEffects()) { |
| iterator.replaceCurrentInstruction(replacement); |
| } else { |
| assert assumeInfo.isAssumeValues(); |
| BasicBlock block = current.getBlock(); |
| Position position = current.getPosition(); |
| if (current.hasOutValue()) { |
| assert replacement.outValue() != null; |
| current.outValue().replaceUsers(replacement.outValue()); |
| } |
| if (current.isInstanceGet()) { |
| iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context()); |
| } else if (current.isStaticGet()) { |
| StaticGet staticGet = current.asStaticGet(); |
| iterator.replaceCurrentInstructionByInitClassIfPossible( |
| appView, code, staticGet.getField().holder); |
| } |
| replacement.setPosition(position); |
| if (block.hasCatchHandlers()) { |
| BasicBlock splitBlock = iterator.split(code, blocks); |
| splitBlock.listIterator(code).add(replacement); |
| |
| // Process the materialized value. |
| blocks.previous(); |
| assert !iterator.hasNext(); |
| assert IteratorUtils.peekNext(blocks) == splitBlock; |
| |
| return true; |
| } else { |
| iterator.add(replacement); |
| } |
| } |
| |
| // Process the materialized value. |
| iterator.previous(); |
| assert iterator.peekNext() == replacement; |
| |
| return true; |
| } |
| |
| private void rewriteInvokeMethodWithConstantValues( |
| IRCode code, |
| ProgramMethod context, |
| Set<Value> affectedValues, |
| ListIterator<BasicBlock> blocks, |
| InstructionListIterator iterator, |
| InvokeMethod invoke) { |
| if (invoke.hasUnusedOutValue()) { |
| return; |
| } |
| |
| DexMethod invokedMethod = invoke.getInvokedMethod(); |
| DexType invokedHolder = invokedMethod.getHolderType(); |
| if (!invokedHolder.isClassType()) { |
| return; |
| } |
| |
| SingleResolutionResult resolutionResult = |
| appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution(); |
| if (resolutionResult == null) { |
| return; |
| } |
| |
| DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); |
| AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget); |
| if (lookup != null |
| && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, invoke, lookup)) { |
| return; |
| } |
| |
| // No Proguard rule could replace the instruction check for knowledge about the return value. |
| if (singleTarget != null && !mayPropagateValueFor(singleTarget)) { |
| return; |
| } |
| |
| AbstractValue abstractReturnValue; |
| if (invokedMethod.getReturnType().isAlwaysNull(appView)) { |
| abstractReturnValue = appView.abstractValueFactory().createNullValue(); |
| } else if (singleTarget != null) { |
| abstractReturnValue = |
| singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue(); |
| } else { |
| abstractReturnValue = UnknownValue.getInstance(); |
| } |
| |
| if (abstractReturnValue.isSingleValue()) { |
| SingleValue singleReturnValue = abstractReturnValue.asSingleValue(); |
| if (singleReturnValue.isMaterializableInContext(appView, context)) { |
| BasicBlock block = invoke.getBlock(); |
| Position position = invoke.getPosition(); |
| |
| Instruction replacement = |
| singleReturnValue.createMaterializingInstruction(appView, code, invoke); |
| affectedValues.addAll(invoke.outValue().affectedValues()); |
| invoke.moveDebugValues(replacement); |
| invoke.outValue().replaceUsers(replacement.outValue()); |
| invoke.setOutValue(null); |
| |
| if (invoke.isInvokeMethodWithReceiver()) { |
| iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context); |
| } else if (invoke.isInvokeStatic() && singleTarget != null) { |
| iterator.replaceCurrentInstructionByInitClassIfPossible( |
| appView, code, singleTarget.getHolderType()); |
| } |
| |
| // Insert the definition of the replacement. |
| replacement.setPosition(position); |
| if (block.hasCatchHandlers()) { |
| iterator |
| .splitCopyCatchHandlers(code, blocks, appView.options()) |
| .listIterator(code) |
| .add(replacement); |
| } else { |
| iterator.add(replacement); |
| } |
| |
| if (singleTarget != null) { |
| singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated(); |
| } |
| } |
| } |
| } |
| |
| private void rewriteFieldGetWithConstantValues( |
| IRCode code, |
| Set<Value> affectedValues, |
| ListIterator<BasicBlock> blocks, |
| InstructionListIterator iterator, |
| FieldInstruction current) { |
| DexField field = current.getField(); |
| |
| // TODO(b/123857022): Should be able to use definitionFor(). |
| SuccessfulFieldResolutionResult resolutionResult = |
| appView.appInfo().resolveField(field).asSuccessfulResolution(); |
| if (resolutionResult == null) { |
| boolean replaceCurrentInstructionWithConstNull = |
| appView.withGeneratedExtensionRegistryShrinker( |
| shrinker -> shrinker.wasRemoved(field), false); |
| if (replaceCurrentInstructionWithConstNull) { |
| iterator.replaceCurrentInstruction(code.createConstNull()); |
| } |
| return; |
| } |
| |
| DexClassAndField target = resolutionResult.getResolutionPair(); |
| DexEncodedField definition = target.getDefinition(); |
| if (definition.isStatic() != current.isStaticGet()) { |
| return; |
| } |
| |
| if (!mayPropagateValueFor(target)) { |
| return; |
| } |
| |
| // Check if there is a Proguard configuration rule that specifies the value of the field. |
| AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target); |
| if (lookup != null |
| && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) { |
| return; |
| } |
| |
| AbstractValue abstractValue; |
| if (field.getType().isAlwaysNull(appView)) { |
| abstractValue = appView.abstractValueFactory().createSingleNumberValue(0); |
| } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(definition)) { |
| abstractValue = definition.getOptimizationInfo().getAbstractValue(); |
| if (abstractValue.isUnknown() && !definition.isStatic()) { |
| AbstractValue abstractReceiverValue = |
| current.asInstanceGet().object().getAbstractValue(appView, code.context()); |
| if (abstractReceiverValue.isSingleFieldValue()) { |
| abstractValue = |
| abstractReceiverValue |
| .asSingleFieldValue() |
| .getState() |
| .getAbstractFieldValue(definition); |
| } |
| } |
| } else if (definition.isStatic()) { |
| // This is guaranteed to read the static value of the field. |
| abstractValue = definition.getStaticValue().toAbstractValue(appView.abstractValueFactory()); |
| // Verify that the optimization info is consistent with the static value. |
| assert definition.getOptimizationInfo().getAbstractValue().isUnknown() |
| || !definition.hasExplicitStaticValue() |
| || abstractValue == definition.getOptimizationInfo().getAbstractValue(); |
| } else { |
| // This is guaranteed to read the default value of the field. |
| abstractValue = appView.abstractValueFactory().createSingleNumberValue(0); |
| } |
| |
| if (abstractValue.isSingleValue()) { |
| SingleValue singleValue = abstractValue.asSingleValue(); |
| if (singleValue.isSingleFieldValue() |
| && singleValue.asSingleFieldValue().getField() == field) { |
| return; |
| } |
| if (singleValue.isMaterializableInContext(appView, code.context())) { |
| BasicBlock block = current.getBlock(); |
| ProgramMethod context = code.context(); |
| Position position = current.getPosition(); |
| |
| // All usages are replaced by the replacement value. |
| Instruction replacement = |
| singleValue.createMaterializingInstruction(appView, code, current); |
| affectedValues.addAll(current.outValue().affectedValues()); |
| current.outValue().replaceUsers(replacement.outValue()); |
| |
| // To preserve side effects, original field-get is replaced by an explicit null-check, if |
| // the field-get instruction may only fail with an NPE, or the field-get remains as-is. |
| if (current.isInstanceGet()) { |
| iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context); |
| } else { |
| assert current.isStaticGet(); |
| iterator.replaceCurrentInstructionByInitClassIfPossible( |
| appView, code, target.getHolderType()); |
| } |
| |
| // Insert the definition of the replacement. |
| replacement.setPosition(position); |
| if (block.hasCatchHandlers()) { |
| iterator |
| .splitCopyCatchHandlers(code, blocks, appView.options()) |
| .listIterator(code) |
| .add(replacement); |
| } else { |
| iterator.add(replacement); |
| } |
| |
| feedback.markFieldAsPropagated(definition); |
| } |
| } |
| } |
| |
| private void replaceInstancePutByNullCheckIfNeverRead( |
| IRCode code, InstructionListIterator iterator, InstancePut current) { |
| DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField(); |
| if (field == null || field.isStatic()) { |
| return; |
| } |
| |
| // If the field is read, we can't remove the instance-put unless the value of the field is known |
| // to be null (in which case the instance-put is a no-op because it assigns the field the same |
| // value as its default value). |
| if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) { |
| return; |
| } |
| |
| iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context()); |
| } |
| |
| private void replaceStaticPutByInitClassIfNeverRead( |
| IRCode code, InstructionListIterator iterator, StaticPut current) { |
| DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField(); |
| if (field == null || !field.isStatic()) { |
| return; |
| } |
| |
| // If the field is read, we can't remove the static-put unless the value of the field is known |
| // to be null (in which case the static-put is a no-op because it assigns the field the same |
| // value as its default value). |
| if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) { |
| return; |
| } |
| |
| iterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, field.getHolderType()); |
| } |
| |
| /** |
| * Replace invoke targets and field accesses with constant values where possible. |
| * |
| * <p>Also assigns value ranges to values where possible. |
| */ |
| public void run(IRCode code) { |
| IRMetadata metadata = code.metadata(); |
| if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) { |
| return; |
| } |
| Set<Value> affectedValues = Sets.newIdentityHashSet(); |
| run(code, code.listIterator(), affectedValues, alwaysTrue()); |
| if (!affectedValues.isEmpty()) { |
| new TypeAnalysis(appView).narrowing(affectedValues); |
| } |
| assert code.isConsistentSSA(); |
| assert code.verifyTypes(appView); |
| } |
| |
| public void run( |
| IRCode code, |
| ListIterator<BasicBlock> blockIterator, |
| Set<Value> affectedValues, |
| Predicate<BasicBlock> blockTester) { |
| ProgramMethod context = code.context(); |
| while (blockIterator.hasNext()) { |
| BasicBlock block = blockIterator.next(); |
| if (!blockTester.test(block)) { |
| continue; |
| } |
| InstructionListIterator iterator = block.listIterator(code); |
| while (iterator.hasNext()) { |
| Instruction current = iterator.next(); |
| if (current.isInvokeMethod()) { |
| rewriteInvokeMethodWithConstantValues( |
| code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod()); |
| } else if (current.isFieldGet()) { |
| rewriteFieldGetWithConstantValues( |
| code, affectedValues, blockIterator, iterator, current.asFieldInstruction()); |
| } else if (current.isInstancePut()) { |
| replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut()); |
| } else if (current.isStaticPut()) { |
| replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut()); |
| } |
| } |
| } |
| } |
| } |