Extend effectively unused argument removal to args with default values
Change-Id: I6b667f2d630a875a720427d89be0b83ae4ee733c
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b55931c..59fde05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -370,6 +370,10 @@
return predecessors.get(0);
}
+ public BasicBlock getPredecessor(int i) {
+ return getPredecessors().get(i);
+ }
+
public List<BasicBlock> getPredecessors() {
return ListUtils.unmodifiableForTesting(predecessors);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index fbccb7c..5fc512f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -371,8 +371,17 @@
}
public Instruction singleUniqueUser() {
- assert ImmutableSet.copyOf(users).size() == 1;
- return users.getFirst();
+ assert hasSingleUniqueUser();
+ return firstUser();
+ }
+
+ public boolean hasSingleUniquePhiUser() {
+ return uniquePhiUsers().size() == 1;
+ }
+
+ public Phi singleUniquePhiUser() {
+ assert hasSingleUniquePhiUser();
+ return firstPhiUser();
}
public Set<Instruction> aliasedUsers() {
@@ -397,6 +406,11 @@
}
}
+ public Instruction firstUser() {
+ assert !users.isEmpty();
+ return users.getFirst();
+ }
+
public Phi firstPhiUser() {
assert !phiUsers.isEmpty();
return phiUsers.getFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 72e0051..e8b022d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -35,7 +35,6 @@
import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
@@ -75,6 +74,7 @@
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstMethodHandle;
import com.android.tools.r8.ir.code.ConstMethodType;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.FieldGet;
import com.android.tools.r8.ir.code.FieldInstruction;
@@ -874,7 +874,7 @@
}
}
if (mayHaveUnreachableBlocks) {
- code.removeUnreachableBlocks(affectedValues, prunedValue -> affectedPhis.remove(prunedValue));
+ code.removeUnreachableBlocks(affectedValues, affectedPhis::remove);
}
affectedValues.narrowingWithAssumeRemoval(appView, code);
if (!affectedPhis.isEmpty()) {
@@ -884,7 +884,7 @@
nullCheckInserter.processWorklist();
code.removeAllDeadAndTrivialPhis();
code.removeRedundantBlocks();
- removeUnusedArguments(method, code, unusedArguments);
+ removeUnusedArguments(code, unusedArguments);
// Finalize cast and null check insertion.
interfaceTypeToClassTypeRewriterHelper.processWorklist();
@@ -1075,16 +1075,31 @@
return replacement;
}
- private void removeUnusedArguments(
- ProgramMethod method, IRCode code, Set<UnusedArgument> unusedArguments) {
+ private void removeUnusedArguments(IRCode code, Set<UnusedArgument> unusedArguments) {
+ AffectedValues affectedValues = new AffectedValues();
for (UnusedArgument unusedArgument : unusedArguments) {
+ InstructionListIterator instructionIterator =
+ unusedArgument.getBlock().listIterator(code, unusedArgument);
if (unusedArgument.outValue().hasAnyUsers()) {
- throw new Unreachable("Unused argument with users in " + method.toSourceString());
+ // This is an unused argument with a default value. The unused argument is an operand of the
+ // phi. This use is eliminated after constant propagation + branch pruning. We eliminate the
+ // UnusedArgument instruction early by replacing it with a const 0.
+ assert unusedArgument.outValue().hasPhiUsers();
+ assert !unusedArgument.outValue().hasUsers();
+ assert !unusedArgument.outValue().hasDebugUsers();
+ TypeElement type = unusedArgument.outValue().getType();
+ instructionIterator.replaceCurrentInstruction(
+ ConstNumber.builder()
+ .setFreshOutValue(code, type.isReferenceType() ? TypeElement.getNull() : type)
+ .setPosition(Position.none())
+ .setValue(0)
+ .build(),
+ affectedValues);
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
}
- InstructionListIterator instructionIterator = unusedArgument.getBlock().listIterator(code);
- instructionIterator.nextUntil(instruction -> instruction == unusedArgument);
- instructionIterator.removeOrReplaceByDebugLocalRead();
}
+ affectedValues.narrowingWithAssumeRemoval(appView, code);
}
private Deque<GraphLensInterval> getUnappliedLenses(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 2273dbb..e0563ca 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -134,7 +134,7 @@
codeScanner.scan(method, code, abstractValueSupplier, pathConstraintSupplier, timing);
assert effectivelyUnusedArgumentsAnalysis != null;
- effectivelyUnusedArgumentsAnalysis.scan(method, code);
+ effectivelyUnusedArgumentsAnalysis.scan(method, code, pathConstraintSupplier);
assert reprocessingCriteriaCollection != null;
reprocessingCriteriaCollection.analyzeArgumentUses(method, code);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
index 1231d76..54cf0b1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
@@ -19,6 +19,10 @@
MethodParameter getSingleOpenVariable();
+ default boolean isArgumentBitSetCompareNode() {
+ return false;
+ }
+
default boolean isUnknown() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
index fb4c465..62455d4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
@@ -5,7 +5,9 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
import java.util.Objects;
import java.util.function.IntFunction;
@@ -33,6 +35,20 @@
}
@Override
+ public boolean isArgumentBitSetCompareNode() {
+ if (!type.isEqualsOrNotEquals() || !(operand instanceof ComputationTreeLogicalBinopAndNode)) {
+ return false;
+ }
+ ComputationTreeLogicalBinopAndNode andOperand = (ComputationTreeLogicalBinopAndNode) operand;
+ return andOperand.left instanceof MethodParameter
+ && andOperand.right instanceof SingleNumberValue;
+ }
+
+ public ComputationTreeUnopCompareNode negate() {
+ return new ComputationTreeUnopCompareNode(operand, type.inverted());
+ }
+
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index 1eec104..860f3d4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -11,12 +11,18 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopCompareNode;
import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ListUtils;
@@ -28,6 +34,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
/**
* Analysis to find arguments that are effectively unused. The analysis first computes the
@@ -68,6 +75,14 @@
private final AppView<AppInfoWithLiveness> appView;
+ // If the condition for a given method parameter evaluates to true then the parameter is unused,
+ // regardless (!) of the constraints for the method parameter in `constraints`. If the condition
+ // evaluates to false, the method parameter can still be unused if all the constraints for the
+ // method parameter are unused.
+ //
+ // All constraints in this map are currently on the form `(arg & const) != 0`.
+ private final Map<MethodParameter, ComputationTreeNode> conditions = new ConcurrentHashMap<>();
+
// Maps each method parameter p to the method parameters that must be effectively unused in order
// for the method parameter p to be effectively unused.
private final Map<MethodParameter, Set<MethodParameter>> constraints = new ConcurrentHashMap<>();
@@ -107,7 +122,8 @@
});
}
- public void scan(ProgramMethod method, IRCode code) {
+ public void scan(
+ ProgramMethod method, IRCode code, PathConstraintSupplier pathConstraintSupplier) {
// If this method is not subject to optimization, then don't compute effectively unused
// constraints for the method parameters.
if (isUnoptimizable(method)) {
@@ -117,31 +133,57 @@
while (argumentIterator.hasNext()) {
Argument argument = argumentIterator.next();
Value argumentValue = argument.outValue();
- Set<MethodParameter> effectivelyUnusedConstraints =
- computeEffectivelyUnusedConstraints(method, argument, argumentValue);
- if (effectivelyUnusedConstraints != null && !effectivelyUnusedConstraints.isEmpty()) {
- MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
- assert !constraints.containsKey(methodParameter);
- constraints.put(methodParameter, effectivelyUnusedConstraints);
- }
+ computeEffectivelyUnusedConstraints(
+ method,
+ argument,
+ argumentValue,
+ pathConstraintSupplier,
+ effectivelyUnusedCondition -> {
+ MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
+ assert !conditions.containsKey(methodParameter);
+ conditions.put(methodParameter, effectivelyUnusedCondition);
+ },
+ effectivelyUnusedConstraints -> {
+ MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
+ assert !constraints.containsKey(methodParameter);
+ constraints.put(methodParameter, effectivelyUnusedConstraints);
+ });
}
}
- private Set<MethodParameter> computeEffectivelyUnusedConstraints(
- ProgramMethod method, Argument argument, Value argumentValue) {
- if (method.getDefinition().isInstanceInitializer() && argumentValue.isThis()) {
- return null;
- }
+ private void computeEffectivelyUnusedConstraints(
+ ProgramMethod method,
+ Argument argument,
+ Value argumentValue,
+ PathConstraintSupplier pathConstraintSupplier,
+ Consumer<ComputationTreeNode> effectivelyUnusedConditionsConsumer,
+ Consumer<Set<MethodParameter>> effectivelyUnusedConstraintsConsumer) {
if (!ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argument.getIndex())) {
- return null;
+ return;
}
- if (!argumentValue.getType().isClassType()
- || argumentValue.hasDebugUsers()
- || argumentValue.hasPhiUsers()) {
- return null;
+ if (!argumentValue.getType().isClassType() || argumentValue.hasDebugUsers()) {
+ return;
+ }
+ Value usedValue;
+ if (argumentValue.hasPhiUsers()) {
+ // If the argument has one or more phi users, we check if there is a single phi due to a
+ // default value for the argument. If so, we record this condition and mark the users of the
+ // phi as effectively unused argument constraints for the current argument.
+ if (!computeEffectivelyUnusedCondition(
+ argumentValue, pathConstraintSupplier, effectivelyUnusedConditionsConsumer)) {
+ return;
+ }
+ assert argumentValue.hasSingleUniquePhiUser();
+ Phi user = argumentValue.singleUniquePhiUser();
+ if (user.hasDebugUsers() || user.hasPhiUsers()) {
+ return;
+ }
+ usedValue = user;
+ } else {
+ usedValue = argumentValue;
}
Set<MethodParameter> effectivelyUnusedConstraints = new HashSet<>();
- for (Instruction user : argumentValue.uniqueUsers()) {
+ for (Instruction user : usedValue.uniqueUsers()) {
if (user.isInvokeMethod()) {
InvokeMethod invoke = user.asInvokeMethod();
ProgramMethod resolvedMethod =
@@ -150,29 +192,68 @@
.unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
.getResolvedProgramMethod();
if (resolvedMethod == null || isUnoptimizable(resolvedMethod)) {
- return null;
+ return;
}
int dependentArgumentIndex =
ListUtils.uniqueIndexMatching(invoke.arguments(), value -> value == argumentValue);
if (dependentArgumentIndex < 0
|| !ParameterRemovalUtils.canRemoveUnusedParameter(
appView, resolvedMethod, dependentArgumentIndex)) {
- return null;
+ return;
}
effectivelyUnusedConstraints.add(
new MethodParameter(resolvedMethod, dependentArgumentIndex));
} else {
- return null;
+ return;
}
}
- return effectivelyUnusedConstraints;
+ if (!effectivelyUnusedConstraints.isEmpty()) {
+ effectivelyUnusedConstraintsConsumer.accept(effectivelyUnusedConstraints);
+ }
+ }
+
+ private boolean computeEffectivelyUnusedCondition(
+ Value argumentValue,
+ PathConstraintSupplier pathConstraintSupplier,
+ Consumer<ComputationTreeNode> effectivelyUnusedConditionsConsumer) {
+ assert argumentValue.hasPhiUsers();
+ if (argumentValue.hasUsers() || !argumentValue.hasSingleUniquePhiUser()) {
+ return false;
+ }
+ Phi phi = argumentValue.singleUniquePhiUser();
+ if (phi.getOperands().size() != 2) {
+ return false;
+ }
+ BasicBlock block = phi.getBlock();
+ ConcretePathConstraintAnalysisState leftState =
+ pathConstraintSupplier.getPathConstraint(block.getPredecessor(0)).asConcreteState();
+ ConcretePathConstraintAnalysisState rightState =
+ pathConstraintSupplier.getPathConstraint(block.getPredecessor(1)).asConcreteState();
+ if (leftState == null || rightState == null) {
+ return false;
+ }
+ // Find a condition that can be used to distinguish program paths coming from the two
+ // predecessors.
+ ComputationTreeNode condition = leftState.getDifferentiatingPathConstraint(rightState);
+ if (condition == null || !condition.isArgumentBitSetCompareNode()) {
+ return false;
+ }
+ // Extract the state corresponding to the program path where the argument is unused. If the
+ // condition evaluates to false on this program path then negate the condition.
+ ConcretePathConstraintAnalysisState unusedState =
+ phi.getOperand(0) == argumentValue ? rightState : leftState;
+ if (unusedState.isNegated(condition)) {
+ condition = ((ComputationTreeUnopCompareNode) condition).negate();
+ }
+ effectivelyUnusedConditionsConsumer.accept(condition);
+ return true;
}
public void computeEffectivelyUnusedArguments(PrunedItems prunedItems) {
// Build a graph where nodes are method parameters and there is an edge from method parameter p0
// to method parameter p1 if the removal of p0 depends on the removal of p1.
EffectivelyUnusedArgumentsGraph dependenceGraph =
- EffectivelyUnusedArgumentsGraph.create(appView, constraints, prunedItems);
+ EffectivelyUnusedArgumentsGraph.create(appView, conditions, constraints, prunedItems);
// Remove all unoptimizable method parameters from the graph, as well as all nodes that depend
// on a node that is unoptimable.
@@ -221,7 +302,9 @@
for (int argumentIndex = 0;
argumentIndex < method.getDefinition().getNumberOfArguments();
argumentIndex++) {
- constraints.remove(new MethodParameter(method, argumentIndex));
+ MethodParameter methodParameter = new MethodParameter(method, argumentIndex);
+ conditions.remove(methodParameter);
+ constraints.remove(methodParameter);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
index 1f795ad..22de2db 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
@@ -9,8 +9,11 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.dfs.DFSStack;
@@ -38,9 +41,33 @@
public static EffectivelyUnusedArgumentsGraph create(
AppView<AppInfoWithLiveness> appView,
+ Map<MethodParameter, ComputationTreeNode> conditions,
Map<MethodParameter, Set<MethodParameter>> constraints,
PrunedItems prunedItems) {
EffectivelyUnusedArgumentsGraph graph = new EffectivelyUnusedArgumentsGraph(appView);
+ conditions.forEach(
+ (methodParameter, condition) -> {
+ ProgramMethod method =
+ asProgramMethodOrNull(appView.definitionFor(methodParameter.getMethod()));
+ if (method == null) {
+ assert false;
+ return;
+ }
+ // Evaluate the condition. If the condition evaluates to true, then create a graph node
+ // for the method parameter and delete all constraints. As a result, the node will not
+ // have any successors, meaning it is effectively unused.
+ CallSiteOptimizationInfo optimizationInfo =
+ method.getOptimizationInfo().getArgumentInfos();
+ if (optimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ AbstractValue result =
+ condition.evaluate(
+ optimizationInfo::getAbstractArgumentValue, appView.abstractValueFactory());
+ if (result.isTrue()) {
+ graph.getOrCreateNode(methodParameter);
+ constraints.remove(methodParameter);
+ }
+ }
+ });
constraints.forEach(
(methodParameter, constraintsForMethodParameter) -> {
EffectivelyUnusedArgumentsGraphNode node = graph.getOrCreateNode(methodParameter);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
index 93c4be1..7eee8e6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
@@ -71,11 +71,10 @@
.streamInstructions()
.noneMatch(
instruction -> instruction.isConstString("DefaultValueNeverUsed")));
- // TODO(b/302281503): This argument is never used and should be removed.
assertTrue(
mainMethodSubject
.streamInstructions()
- .anyMatch(
+ .noneMatch(
instruction ->
instruction.isConstString("Unused[DefaultValueAlwaysUsed]")));