Towards the propagation of constants from call sites to the method body.

Bug: 69963623, 139246447
Change-Id: Iab83db43d5cb62bdf535bf88eafb26d3def59567
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 078410a..90cc0f4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -77,7 +77,7 @@
     this.options = options;
     this.rewritePrefix = mapper;
 
-    if (enableWholeProgramOptimizations() && options.enableCallSiteOptimizationInfoPropagation) {
+    if (enableWholeProgramOptimizations() && options.enablePropagationOfDynamicTypesAtCallSites) {
       this.callSiteOptimizationInfoPropagator =
           new CallSiteOptimizationInfoPropagator(withLiveness());
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index f535e42..f560b29 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -48,6 +48,13 @@
     return false;
   }
 
+  public AbstractValue join(AbstractValue other) {
+    if (this.equals(other)) {
+      return this;
+    }
+    return UnknownValue.getInstance();
+  }
+
   @Override
   public abstract boolean equals(Object o);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index ab931cc..388169b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -10,10 +10,11 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -171,7 +172,7 @@
         .hasUsefulOptimizationInfo(appView, code.method);
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     List<Assume<?>> assumeInstructions = new LinkedList<>();
-    List<ConstInstruction> constants = new LinkedList<>();
+    List<Instruction> constants = new LinkedList<>();
     int argumentsSeen = 0;
     InstructionListIterator iterator = code.entryBlock().listIterator(code);
     while (iterator.hasNext()) {
@@ -184,8 +185,23 @@
       if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
         continue;
       }
+      int argIndex = argumentsSeen - 1;
+      AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
+      if (abstractValue.isSingleValue()) {
+        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        SingleValue singleValue = abstractValue.asSingleValue();
+        if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+          Instruction replacement =
+              singleValue.createMaterializingInstruction(appView, code, instr);
+          replacement.setPosition(instr.getPosition());
+          affectedValues.addAll(originalArg.affectedValues());
+          originalArg.replaceUsers(replacement.outValue());
+          constants.add(replacement);
+          continue;
+        }
+      }
       TypeLatticeElement dynamicUpperBoundType =
-          callSiteOptimizationInfo.getDynamicUpperBoundType(argumentsSeen - 1);
+          callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
       if (dynamicUpperBoundType == null) {
         continue;
       }
@@ -197,7 +213,6 @@
         constants.add(nullInstruction);
         continue;
       }
-      // TODO(b/69963623): Handle other kinds of constants, e.g. number, string, or class.
       Value specializedArg;
       if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
         specializedArg = code.createValue(originalArg.getTypeLattice());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index c99f24c..18c709c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 
 // A flat lattice structure:
@@ -63,8 +65,12 @@
 
   // TODO(b/139246447): dynamic lower bound type?
 
-  // TODO(b/69963623): collect constants and if they're all same, propagate it to the callee.
-  //   then, we need to re-run unused argument removal?
+  // TODO(b/69963623): we need to re-run unused argument removal?
+
+  // The index exactly matches with in values of invocation, i.e., even including receiver.
+  public AbstractValue getAbstractArgumentValue(int argIndex) {
+    return UnknownValue.getInstance();
+  }
 
   // TODO(b/139249918): propagate classes that are guaranteed to be initialized.
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index be0bf19..c38c0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -11,9 +11,13 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+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.Value;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.List;
+import java.util.Objects;
 
 // Accumulated optimization info from call sites.
 public class ConcreteCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
@@ -21,26 +25,39 @@
   // inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
   // That is, this information takes into account the receiver as well.
   private final int size;
-  private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
-  // TODO(b/69963623): sparse map from index to ConstantData if any.
+  private final Int2ReferenceMap<TypeLatticeElement> dynamicUpperBoundTypes;
+  private final Int2ReferenceMap<AbstractValue> constants;
 
-  private ConcreteCallSiteOptimizationInfo(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1) > 0;
-    this.size = encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1);
-    this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+  private ConcreteCallSiteOptimizationInfo(
+      DexEncodedMethod encodedMethod, boolean allowConstantPropagation) {
+    this(encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1),
+        allowConstantPropagation);
   }
 
-  private ConcreteCallSiteOptimizationInfo(int size) {
+  private ConcreteCallSiteOptimizationInfo(int size, boolean allowConstantPropagation) {
+    assert size > 0;
     this.size = size;
     this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+    this.constants = allowConstantPropagation ? new Int2ReferenceArrayMap<>(size) : null;
   }
 
   CallSiteOptimizationInfo join(
       ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
     assert this.size == other.size;
-    ConcreteCallSiteOptimizationInfo result = new ConcreteCallSiteOptimizationInfo(this.size);
+    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+    ConcreteCallSiteOptimizationInfo result =
+        new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
     assert result.dynamicUpperBoundTypes != null;
     for (int i = 0; i < result.size; i++) {
+      if (allowConstantPropagation) {
+        assert result.constants != null;
+        AbstractValue abstractValue =
+            getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i));
+        if (abstractValue.isNonTrivial()) {
+          result.constants.put(i, abstractValue);
+        }
+      }
+
       TypeLatticeElement thisUpperBoundType = getDynamicUpperBoundType(i);
       if (thisUpperBoundType == null) {
         // This means the corresponding argument is primitive. The counterpart should be too.
@@ -82,6 +99,12 @@
   public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
     TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
     for (int i = 0; i < size; i++) {
+      AbstractValue abstractValue = getAbstractArgumentValue(i);
+      if (abstractValue.isNonTrivial()) {
+        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        return true;
+      }
+
       if (!staticTypes[i].isReference()) {
         continue;
       }
@@ -89,6 +112,7 @@
       if (dynamicUpperBoundType == null) {
         continue;
       }
+      assert appView.options().enablePropagationOfDynamicTypesAtCallSites;
       // To avoid the full join of type lattices below, separately check if the nullability of
       // arguments is improved, and if so, we can eagerly conclude that we've collected useful
       // call site information for this method.
@@ -109,18 +133,43 @@
   @Override
   public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
     assert 0 <= argIndex && argIndex < size;
+    assert dynamicUpperBoundTypes != null;
     return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
   }
 
+  @Override
+  public AbstractValue getAbstractArgumentValue(int argIndex) {
+    assert 0 <= argIndex && argIndex < size;
+    // TODO(b/69963623): Remove this once enabled.
+    if (constants == null) {
+      return UnknownValue.getInstance();
+    }
+    return constants.getOrDefault(argIndex, UnknownValue.getInstance());
+  }
+
   public static CallSiteOptimizationInfo fromArguments(
       AppView<? extends AppInfoWithSubtyping> appView,
       DexEncodedMethod method,
       List<Value> inValues) {
-    ConcreteCallSiteOptimizationInfo newCallSiteInfo = new ConcreteCallSiteOptimizationInfo(method);
+    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+    ConcreteCallSiteOptimizationInfo newCallSiteInfo =
+        new ConcreteCallSiteOptimizationInfo(method, allowConstantPropagation);
     assert newCallSiteInfo.size == inValues.size();
+    assert newCallSiteInfo.dynamicUpperBoundTypes != null;
     for (int i = 0; i < newCallSiteInfo.size; i++) {
       Value arg = inValues.get(i);
-      // TODO(b/69963623): may need different place to store constants.
+      if (allowConstantPropagation) {
+        assert newCallSiteInfo.constants != null;
+        Value aliasedValue = arg.getAliasedValue();
+        if (!aliasedValue.isPhi()) {
+          AbstractValue abstractValue =
+              aliasedValue.definition.getAbstractValue(appView, method.method.holder);
+          if (abstractValue.isNonTrivial()) {
+            newCallSiteInfo.constants.put(i, abstractValue);
+          }
+        }
+      }
+
       if (arg.getTypeLattice().isPrimitive()) {
         continue;
       }
@@ -151,18 +200,19 @@
       return false;
     }
     ConcreteCallSiteOptimizationInfo otherInfo = (ConcreteCallSiteOptimizationInfo) other;
-    assert this.dynamicUpperBoundTypes != null;
-    return this.dynamicUpperBoundTypes.equals(otherInfo.dynamicUpperBoundTypes);
+    return Objects.equals(this.dynamicUpperBoundTypes, otherInfo.dynamicUpperBoundTypes)
+        && Objects.equals(this.constants, otherInfo.constants);
   }
 
   @Override
   public int hashCode() {
     assert this.dynamicUpperBoundTypes != null;
-    return System.identityHashCode(dynamicUpperBoundTypes);
+    return System.identityHashCode(dynamicUpperBoundTypes) * 7 + System.identityHashCode(constants);
   }
 
   @Override
   public String toString() {
-    return dynamicUpperBoundTypes.toString();
+    return dynamicUpperBoundTypes.toString()
+        + (constants == null ? "" : (System.lineSeparator() + constants.toString()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d8ef9c4..f59675e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -169,7 +169,8 @@
     enableValuePropagation = false;
     enableSideEffectAnalysis = false;
     enableTreeShakingOfLibraryMethodOverrides = false;
-    enableCallSiteOptimizationInfoPropagation = false;
+    enablePropagationOfDynamicTypesAtCallSites = false;
+    enablePropagationOfConstantsAtCallSites = false;
   }
 
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -216,7 +217,9 @@
   public boolean enableNameReflectionOptimization = true;
   public boolean enableStringConcatenationOptimization = true;
   public boolean enableTreeShakingOfLibraryMethodOverrides = false;
-  public boolean enableCallSiteOptimizationInfoPropagation = true;
+  public boolean enablePropagationOfDynamicTypesAtCallSites = true;
+  // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
+  public boolean enablePropagationOfConstantsAtCallSites = false;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
 
@@ -1047,6 +1050,13 @@
     enableNameReflectionOptimization = false;
   }
 
+  // TODO(b/69963623): Remove this once enabled.
+  @VisibleForTesting
+  public void enablePropagationOfConstantsAtCallSites() {
+    assert !enablePropagationOfConstantsAtCallSites;
+    enablePropagationOfConstantsAtCallSites = true;
+  }
+
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 4ff1850..9fe3bb4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -431,9 +432,7 @@
     ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
     assertEquals(0, javaResult.exitCode);
 
-    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
-        internalOptions -> internalOptions.enableInlining = false);
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure);
 
     // Run processed (output) program on ART
     ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
@@ -442,4 +441,13 @@
 
     return processedApp;
   }
+
+  private void configure(InternalOptions options) {
+    // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
+    // bothers what the tests want to check, such as exact instructions in the body that include
+    // invocation kinds, like virtual call to a bridge.
+    options.enablePropagationOfConstantsAtCallSites = false;
+    // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+    options.enableInlining = false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 5e0e77f..a9385a6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -123,7 +123,7 @@
           o.inliningInstructionLimit = 6;
           // Tests depend on nullability of receiver and argument in general. Learning very accurate
           // nullability from actual usage in tests bothers what we want to test.
-          o.enableCallSiteOptimizationInfoPropagation = false;
+          o.enablePropagationOfDynamicTypesAtCallSites = false;
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index c0ec39b..35e9636 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,6 +45,7 @@
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
@@ -54,9 +56,8 @@
     assertThat(main, isPresent());
     MethodSubject test = main.uniqueMethodWithName("test");
     assertThat(test, isPresent());
-    // TODO(b/69963623):
-    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
-    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index 5372bf3..0e82d6c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -43,6 +44,7 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
@@ -60,16 +62,14 @@
     assertThat(a, isPresent());
     MethodSubject a_m = a.uniqueMethodWithName("m");
     assertThat(a_m, isPresent());
-    // TODO(b/69963623):
-    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
-    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
     ClassSubject b = inspector.clazz(B.class);
     assertThat(b, isPresent());
     MethodSubject b_m = b.uniqueMethodWithName("m");
     assertThat(b_m, isPresent());
-    // TODO(b/69963623):
-    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
-    assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
   interface I {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index f12d80d..f18d86d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,6 +43,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
@@ -52,9 +54,8 @@
     assertThat(main, isPresent());
     MethodSubject test = main.uniqueMethodWithName("test");
     assertThat(test, isPresent());
-    // TODO(b/69963623):
-    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
-    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index eaf5924..5673765 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,6 +46,7 @@
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "null")
         .inspect(this::inspect);
@@ -56,9 +58,8 @@
 
     MethodSubject a_m = a.uniqueMethodWithName("m");
     assertThat(a_m, isPresent());
-    // TODO(b/69963623):
-    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
-    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
 
     ClassSubject b = inspector.clazz(B.class);
     assertThat(b, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 90aeb7c..aa8e1d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -135,7 +135,7 @@
     // In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
     // code. Then, remaining call with non-null argument made getClass() replaceable.
     // Disable the propagation of call site information to separate the tests.
-    options.enableCallSiteOptimizationInfoPropagation = false;
+    options.enablePropagationOfDynamicTypesAtCallSites = false;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 8869a01..0ac155c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -35,7 +36,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -53,6 +54,13 @@
         .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
+  private void configure(InternalOptions options) {
+    // This test wants to check if compile-time computation is not applied to non-null,
+    // non-constant value. In a simple test setting, call-site optimization knows the argument is
+    // always a non-null, specific constant, but that is beyond the scope of this test.
+    options.enablePropagationOfConstantsAtCallSites = false;
+  }
+
   private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
     CodeInspector codeInspector = result.inspector();
     ClassSubject mainClass = codeInspector.clazz(MAIN);
@@ -62,7 +70,7 @@
 
     MethodSubject wrapper = mainClass.uniqueMethodWithName("wrapper");
     assertThat(wrapper, isPresent());
-    // Because of nullable, non-constant argument, isEmpty() should remain.
+    // Due to nullable, non-constant argument (w/o call-site optimization), isEmpty() should remain.
     assertEquals(1, countCall(wrapper, "String", "isEmpty"));
   }
 
@@ -74,7 +82,8 @@
         testForD8()
             .debug()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 3);
@@ -83,7 +92,8 @@
         testForD8()
             .release()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 1);
@@ -97,7 +107,8 @@
             .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 1);
@@ -107,7 +118,7 @@
 
     @NeverInline
     static boolean wrapper(String arg) {
-      // Cannot be computed at compile time.
+      // Cannot be computed at compile time (unless call-site optimization is enabled).
       return arg.isEmpty();
     }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 85cf999..f9d9d21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
     // Disable the propagation of call site information to test String#valueOf optimization with
     // nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
     // simplify everything in that method.
-    options.enableCallSiteOptimizationInfoPropagation = false;
+    options.enablePropagationOfDynamicTypesAtCallSites = false;
     options.testing.forceNameReflectionOptimization = true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index ecd6635..cc02426 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -83,7 +83,7 @@
           // Call site optimization propagation will conclude that the input of B...Caller#call is
           // always null, and replace the last call with null-throwing instruction.
           // However, we want to test return type and parameter type are kept in this scenario.
-          o.enableCallSiteOptimizationInfoPropagation = false;
+          o.enablePropagationOfDynamicTypesAtCallSites = false;
           o.enableInlining = false;
         })
         .run(parameters.getRuntime(), MAIN)