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);
+    }
+  }
+}