Reland "Leverage unused value states in argument propagation"
Change-Id: Ie62d0057ab4bf9fe3646c639d52d6ed97985b7aa
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 c059f0a..30b9398 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
@@ -83,8 +83,10 @@
reprocessingCriteriaCollection = new ArgumentPropagatorReprocessingCriteriaCollection(appView);
classesWithSingleCallerInlinedInstanceInitializers = ConcurrentHashMap.newKeySet();
- codeScanner = new ArgumentPropagatorCodeScanner(appView, reprocessingCriteriaCollection);
effectivelyUnusedArgumentsAnalysis = new EffectivelyUnusedArgumentsAnalysis(appView);
+ codeScanner =
+ new ArgumentPropagatorCodeScanner(
+ appView, effectivelyUnusedArgumentsAnalysis, reprocessingCriteriaCollection);
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
@@ -135,7 +137,8 @@
code,
codeScanner.getFieldValueFactory(),
codeScanner.getMethodParameterFactory());
- codeScanner.scan(method, code, abstractValueSupplier, pathConstraintSupplier, timing);
+ codeScanner.scan(
+ method, code, methodProcessor, abstractValueSupplier, pathConstraintSupplier, timing);
assert effectivelyUnusedArgumentsAnalysis != null;
effectivelyUnusedArgumentsAnalysis.scan(method, code, pathConstraintSupplier);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 99759a9..95eda60 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position.SourcePosition;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.CastAbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
@@ -53,6 +54,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.IfThenElseAbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
@@ -70,6 +72,7 @@
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.MethodReprocessingCriteria;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ParameterReprocessingCriteria;
+import com.android.tools.r8.optimize.argumentpropagation.unusedarguments.EffectivelyUnusedArgumentsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
@@ -104,6 +107,8 @@
private final AppView<AppInfoWithLiveness> appView;
+ private final EffectivelyUnusedArgumentsAnalysis effectivelyUnusedArgumentsAnalysis;
+
private final FieldValueFactory fieldValueFactory = new FieldValueFactory();
final MethodParameterFactory methodParameterFactory = new MethodParameterFactory();
@@ -134,14 +139,12 @@
private final InFlowComparator.Builder inFlowComparatorBuilder = InFlowComparator.builder();
- public ArgumentPropagatorCodeScanner(AppView<AppInfoWithLiveness> appView) {
- this(appView, new ArgumentPropagatorReprocessingCriteriaCollection(appView));
- }
-
ArgumentPropagatorCodeScanner(
AppView<AppInfoWithLiveness> appView,
+ EffectivelyUnusedArgumentsAnalysis effectivelyUnusedArgumentsAnalysis,
ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection) {
this.appView = appView;
+ this.effectivelyUnusedArgumentsAnalysis = effectivelyUnusedArgumentsAnalysis;
this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
}
@@ -153,6 +156,16 @@
virtualRootMethods.putAll(extension);
}
+ public ComputationTreeNode getEffectivelyUnusedArgumentCondition(
+ ProgramMethod method, int argumentIndex, MethodProcessor methodProcessor) {
+ if (methodProcessor.isProcessedConcurrently(method)) {
+ // The optimization info for the given method is nondeterministic.
+ return AbstractValue.unknown();
+ }
+ MethodParameter methodParameter = methodParameterFactory.create(method, argumentIndex);
+ return effectivelyUnusedArgumentsAnalysis.getEffectivelyUnusedCondition(methodParameter);
+ }
+
public FieldStateCollection getFieldStates() {
return fieldStates;
}
@@ -229,10 +242,12 @@
public void scan(
ProgramMethod method,
IRCode code,
+ MethodProcessor methodProcessor,
AbstractValueSupplier abstractValueSupplier,
PathConstraintSupplier pathConstraintSupplier,
Timing timing) {
- new CodeScanner(abstractValueSupplier, code, method, pathConstraintSupplier).scan(timing);
+ new CodeScanner(abstractValueSupplier, code, method, methodProcessor, pathConstraintSupplier)
+ .scan(timing);
}
protected class CodeScanner {
@@ -240,16 +255,19 @@
protected final AbstractValueSupplier abstractValueSupplier;
protected final IRCode code;
protected final ProgramMethod context;
+ private final MethodProcessor methodProcessor;
private final PathConstraintSupplier pathConstraintSupplier;
protected CodeScanner(
AbstractValueSupplier abstractValueSupplier,
IRCode code,
ProgramMethod method,
+ MethodProcessor methodProcessor,
PathConstraintSupplier pathConstraintSupplier) {
this.abstractValueSupplier = abstractValueSupplier;
this.code = code;
this.context = method;
+ this.methodProcessor = methodProcessor;
this.pathConstraintSupplier = pathConstraintSupplier;
}
@@ -886,6 +904,9 @@
DexType parameterType =
invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
+ if (isUnused(invoke, singleTarget, argumentIndex)) {
+ return ValueState.unused(parameterType);
+ }
// If the value is an argument of the enclosing method, then clearly we have no information
// about its abstract value. Instead of treating this as having an unknown runtime value, we
@@ -934,7 +955,20 @@
}
}
- @SuppressWarnings("ReferenceEquality")
+ private boolean isUnused(InvokeMethod invoke, ProgramMethod singleTarget, int argumentIndex) {
+ if (singleTarget == null) {
+ return false;
+ }
+ ComputationTreeNode unusedCondition =
+ getEffectivelyUnusedArgumentCondition(singleTarget, argumentIndex, methodProcessor);
+ if (unusedCondition.isUnknown()) {
+ return false;
+ }
+ FlowGraphStateProvider flowGraphStateProvider =
+ FlowGraphStateProvider.createFromInvoke(appView, invoke, singleTarget, context);
+ return unusedCondition.evaluate(appView, flowGraphStateProvider).isTrue();
+ }
+
private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
if (resolvedMethod.getDefinition().belongsToDirectPool()) {
return resolvedMethod.getReference();
@@ -954,7 +988,7 @@
DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
assert rootMethod != null;
assert !isMonomorphicVirtualMethod(resolvedMethod)
- || rootMethod == resolvedMethod.getReference();
+ || resolvedMethod.getReference().isIdenticalTo(rootMethod);
return rootMethod;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
index 639765b..776639f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
@@ -4,11 +4,14 @@
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Supplier;
@@ -83,6 +86,34 @@
};
}
+ static FlowGraphStateProvider createFromInvoke(
+ AppView<AppInfoWithLiveness> appView,
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ ProgramMethod context) {
+ return new FlowGraphStateProvider() {
+
+ @Override
+ public ValueState getState(DexField field) {
+ return ValueState.unknown();
+ }
+
+ @Override
+ public ValueState getState(
+ MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider) {
+ if (methodParameter.getMethod().isNotIdenticalTo(singleTarget.getReference())) {
+ return ValueState.unknown();
+ }
+ AbstractValue abstractValue =
+ invoke.getArgument(methodParameter.getIndex()).getAbstractValue(appView, context);
+ if (abstractValue.isUnknown()) {
+ return ValueState.unknown();
+ }
+ return ConcreteValueState.create(methodParameter.getType(), abstractValue);
+ }
+ };
+ }
+
ValueState getState(DexField field);
ValueState getState(MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
index c8389a1..3fc2bec 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -132,7 +132,7 @@
ValueState parameterState) {
// No need to create nodes for parameters with no in-parameters and parameters we don't know
// anything about.
- if (parameterState.isBottom() || parameterState.isUnknown()) {
+ if (parameterState.isBottom() || parameterState.isUnknown() || parameterState.isUnused()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
index b2e3a53..63f597e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
@@ -160,6 +160,10 @@
return getState().isUnknown();
}
+ boolean isUnused() {
+ return getState().isUnused();
+ }
+
// No need to enqueue the affected node if it is already in the worklist or if it does not have
// any successors (i.e., the successor is a leaf).
void addToWorkList(Deque<FlowGraphNode> worklist) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index fef91ce..840297c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
@@ -161,7 +162,7 @@
}
private void propagate(FlowGraph flowGraph, FlowGraphNode node, Deque<FlowGraphNode> worklist) {
- if (node.isBottom()) {
+ if (node.isBottom() || node.isUnused()) {
return;
}
if (node.isUnknown()) {
@@ -336,6 +337,30 @@
methodStates.set(method, MethodState.bottom());
} else if (monomorphicMethodState.isEffectivelyUnknown()) {
methodStates.set(method, MethodState.unknown());
+ } else {
+ // Replace unused parameters by the constant null.
+ for (int i = 0; i < monomorphicMethodState.getParameterStates().size(); i++) {
+ ValueState parameterState = monomorphicMethodState.getParameterState(i);
+ if (parameterState.isUnused()) {
+ DexType type = method.getArgumentType(i);
+ assert parameterState.identical(ValueState.unused(type));
+ ConcreteValueState replacement;
+ if (type.isArrayType()) {
+ replacement = new ConcreteArrayTypeValueState(Nullability.definitelyNull());
+ } else if (type.isClassType()) {
+ replacement =
+ new ConcreteClassTypeValueState(
+ appView.abstractValueFactory().createUncheckedNullValue(),
+ DynamicType.definitelyNull());
+ } else {
+ assert type.isPrimitiveType();
+ replacement =
+ new ConcretePrimitiveTypeValueState(
+ appView.abstractValueFactory().createZeroValue());
+ }
+ monomorphicMethodState.setParameterState(i, replacement);
+ }
+ }
}
}
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 db497a7..ef389b7 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
@@ -13,6 +13,7 @@
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.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
@@ -98,6 +99,10 @@
this.appView = appView;
}
+ public ComputationTreeNode getEffectivelyUnusedCondition(MethodParameter methodParameter) {
+ return conditions.getOrDefault(methodParameter, AbstractValue.unknown());
+ }
+
public void initializeOptimizableVirtualMethods(Set<DexProgramClass> stronglyConnectedComponent) {
// Group all virtual methods in this strongly connected component by their signature.
Map<DexMethodSignature, ProgramMethodSet> methodsBySignature = new HashMap<>();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/FilterUnusedArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/FilterUnusedArgumentPropagationTest.java
new file mode 100644
index 0000000..d8c0978
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/FilterUnusedArgumentPropagationTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2024, 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.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FilterUnusedArgumentPropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isConstString));
+
+ // Despite the first argument being non-constant, we should be able to perform
+ // constant propagation, since only one of the two provided constants are used within
+ // the method.
+ MethodSubject testMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("test");
+ assertThat(testMethodSubject, isPresent());
+ assertEquals(1, testMethodSubject.getParameters().size());
+ assertTrue(
+ testMethodSubject.streamInstructions().anyMatch(i -> i.isConstString("Hello")));
+ assertTrue(
+ testMethodSubject
+ .streamInstructions()
+ .anyMatch(i -> i.isConstString(", world!")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ test(null, 1);
+ test(", world!", 0);
+ System.out.println();
+ }
+
+ @NeverInline
+ static void test(String s, int flags) {
+ if ((flags & 1) != 0) {
+ s = "Hello";
+ }
+ System.out.print(s);
+ }
+ }
+}