Partial support for tracking phi operand flow in value propgation

Bug: b/302281503
Change-Id: I1adf8a22dc20c5d0386903a47c88de4ce0062735
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
index 361ed99..fca5f48 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
@@ -106,6 +106,29 @@
     return this;
   }
 
+  // TODO(b/302281503): Consider returning the list of differentiating path constraints.
+  //  For example, if we have the condition X & Y, then we may not know anything about X but we
+  //  could know something about Y. Add a test showing this.
+  // TODO(b/302281503): Add a field `inactivePathConstraints` and ensure that pathConstraints and
+  //  negatedPathConstraints are disjoint. This way (1) we reduce the size of the sets and (2)
+  //  this does not need to check if each path constraint is not in the negated set.
+  public ComputationTreeNode getDifferentiatingPathConstraint(
+      ConcretePathConstraintAnalysisState other) {
+    for (ComputationTreeNode pathConstraint : pathConstraints) {
+      if (!negatedPathConstraints.contains(pathConstraint)
+          && other.negatedPathConstraints.contains(pathConstraint)) {
+        return pathConstraint;
+      }
+    }
+    for (ComputationTreeNode negatedPathConstraint : negatedPathConstraints) {
+      if (!pathConstraints.contains(negatedPathConstraint)
+          && other.pathConstraints.contains(negatedPathConstraint)) {
+        return negatedPathConstraint;
+      }
+    }
+    return null;
+  }
+
   public ConcretePathConstraintAnalysisState join(ConcretePathConstraintAnalysisState other) {
     Set<ComputationTreeNode> newPathConstraints = join(pathConstraints, other.pathConstraints);
     Set<ComputationTreeNode> newNegatedPathConstraints =
@@ -143,8 +166,13 @@
       return false;
     }
     ConcretePathConstraintAnalysisState state = (ConcretePathConstraintAnalysisState) obj;
-    return pathConstraints.equals(state.pathConstraints)
-        && negatedPathConstraints.equals(state.negatedPathConstraints);
+    return equals(state.pathConstraints, state.negatedPathConstraints);
+  }
+
+  public boolean equals(
+      Set<ComputationTreeNode> pathConstraints, Set<ComputationTreeNode> negatedPathConstraints) {
+    return this.pathConstraints.equals(pathConstraints)
+        && this.negatedPathConstraints.equals(negatedPathConstraints);
   }
 
   public boolean identical(
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 7a8cbdb..68a353e 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
-import static com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow.asBaseInFlowOrNull;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -16,6 +15,10 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.path.PathConstraintAnalysis;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
+import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -26,6 +29,7 @@
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.FieldGet;
 import com.android.tools.r8.ir.code.FieldPut;
@@ -34,6 +38,7 @@
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
@@ -49,7 +54,9 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverValueState;
 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.FieldValue;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
+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.InstanceFieldReadAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
@@ -60,6 +67,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 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;
@@ -79,6 +87,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -274,39 +283,47 @@
     private NonEmptyValueState computeFieldState(
         FieldPut fieldPut, ProgramField resolvedField, Timing timing) {
       timing.begin("Compute field state for field-put");
-      NonEmptyValueState result = computeFieldState(fieldPut, resolvedField);
+      Value value = fieldPut.value();
+      NonEmptyValueState result = computeFieldState(value, value, resolvedField);
       timing.end();
       return result;
     }
 
-    private NonEmptyValueState computeFieldState(FieldPut fieldPut, ProgramField field) {
+    private NonEmptyValueState computeFieldState(
+        Value value, Value initialValue, ProgramField field) {
+      assert value == initialValue || initialValue.getAliasedValue().isPhi();
+
       TypeElement fieldType = field.getType().toTypeElement(appView);
-      if (!fieldPut.value().getType().lessThanOrEqual(fieldType, appView)) {
+      if (!value.getType().lessThanOrEqual(fieldType, appView)) {
         return ValueState.unknown();
       }
 
       NonEmptyValueState inFlowState =
-          computeInFlowState(field.getType(), fieldPut.value(), context);
+          computeInFlowState(
+              field.getType(),
+              value,
+              initialValue,
+              context,
+              phiOperandValue -> computeFieldState(phiOperandValue, initialValue, field));
       if (inFlowState != null) {
         return inFlowState;
       }
 
       if (field.getType().isArrayType()) {
-        Nullability nullability = fieldPut.value().getType().nullability();
+        Nullability nullability = value.getType().nullability();
         return ConcreteArrayTypeValueState.create(nullability);
       }
 
-      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
+      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(value);
       if (abstractValue.isUnknown()) {
         abstractValue =
             getFallbackAbstractValueForField(
-                field,
-                () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
+                field, () -> ObjectStateAnalysis.computeObjectState(value, appView, context));
       }
       if (field.getType().isClassType()) {
         DynamicType dynamicType =
             WideningUtils.widenDynamicNonReceiverType(
-                appView, fieldPut.value().getDynamicType(appView), field.getType());
+                appView, value.getDynamicType(appView), field.getType());
         return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
       } else {
         assert field.getType().isPrimitiveType();
@@ -314,10 +331,41 @@
       }
     }
 
+    private BaseInFlow computeBaseInFlow(DexType staticType, Value value, ProgramMethod context) {
+      Value valueRoot = value.getAliasedValue();
+      if (valueRoot.isArgument()) {
+        MethodParameter inParameter =
+            methodParameterFactory.create(
+                context, valueRoot.getDefinition().asArgument().getIndex());
+        if (!widenBaseInFlow(staticType, inParameter, context).isUnknownAbstractFunction()) {
+          return inParameter;
+        }
+      } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
+        FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
+        ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
+        if (field != null) {
+          FieldValue fieldValue = fieldValueFactory.create(field);
+          if (!widenBaseInFlow(staticType, fieldValue, context).isUnknownAbstractFunction()) {
+            return fieldValue;
+          }
+        }
+      }
+      return null;
+    }
+
     // If the value is an argument of the enclosing method or defined by a field-get, then clearly
     // we have no information about its abstract value (yet). Instead of treating this as having an
     // unknown runtime value, we instead record a flow constraint.
-    private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
+    private InFlow computeInFlow(
+        DexType staticType,
+        Value value,
+        Value initialValue,
+        ProgramMethod context,
+        Function<Value, NonEmptyValueState> valueStateSupplier) {
+      if (value != initialValue) {
+        assert initialValue.getAliasedValue().isPhi();
+        return computeBaseInFlow(staticType, value, context);
+      }
       Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
       if (valueRoot.isArgument()) {
         MethodParameter inParameter =
@@ -332,8 +380,7 @@
         }
         if (fieldGet.isInstanceGet()) {
           Value receiverValue = fieldGet.asInstanceGet().object();
-          BaseInFlow receiverInFlow =
-              asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
+          BaseInFlow receiverInFlow = computeBaseInFlow(staticType, receiverValue, context);
           if (receiverInFlow != null
               && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
             return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
@@ -341,10 +388,46 @@
         }
         return castBaseInFlow(
             widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
+      } else if (value.isPhi()) {
+        return computeIfThenElseAbstractFunction(value.asPhi(), valueStateSupplier);
       }
       return null;
     }
 
+    private IfThenElseAbstractFunction computeIfThenElseAbstractFunction(
+        Phi phi, Function<Value, NonEmptyValueState> valueStateSupplier) {
+      if (!appView.testing().enableIfThenElseAbstractFunction) {
+        return null;
+      }
+      if (phi.getOperands().size() != 2 || !phi.hasOperandThatMatches(Value::isArgument)) {
+        return null;
+      }
+      PathConstraintAnalysis analysis =
+          new PathConstraintAnalysis(appView, code, methodParameterFactory);
+      SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState> result =
+          analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
+      assert result != null;
+      ConcretePathConstraintAnalysisState leftPredecessorPathConstraint =
+          result.getBlockExitState(phi.getBlock().getPredecessors().get(0)).asConcreteState();
+      ConcretePathConstraintAnalysisState rightPredecessorPathConstraint =
+          result.getBlockExitState(phi.getBlock().getPredecessors().get(1)).asConcreteState();
+      if (leftPredecessorPathConstraint == null || rightPredecessorPathConstraint == null) {
+        return null;
+      }
+      ComputationTreeNode condition =
+          leftPredecessorPathConstraint.getDifferentiatingPathConstraint(
+              rightPredecessorPathConstraint);
+      if (condition == null || condition.getSingleOpenVariable() == null) {
+        return null;
+      }
+      NonEmptyValueState leftValue = valueStateSupplier.apply(phi.getOperand(0));
+      NonEmptyValueState rightValue = valueStateSupplier.apply(phi.getOperand(1));
+      if (leftPredecessorPathConstraint.getNegatedPathConstraints().contains(condition)) {
+        return new IfThenElseAbstractFunction(condition, rightValue, leftValue);
+      }
+      return new IfThenElseAbstractFunction(condition, leftValue, rightValue);
+    }
+
     private InFlow castBaseInFlow(InFlow inFlow, Value value) {
       if (inFlow.isUnknownAbstractFunction()) {
         return inFlow;
@@ -373,8 +456,13 @@
     }
 
     private NonEmptyValueState computeInFlowState(
-        DexType staticType, Value value, ProgramMethod context) {
-      InFlow inFlow = computeInFlow(staticType, value, context);
+        DexType staticType,
+        Value value,
+        Value initialValue,
+        ProgramMethod context,
+        Function<Value, NonEmptyValueState> valueStateSupplier) {
+      assert value == initialValue || initialValue.getAliasedValue().isPhi();
+      InFlow inFlow = computeInFlow(staticType, value, initialValue, context, valueStateSupplier);
       if (inFlow == null) {
         return null;
       }
@@ -383,6 +471,7 @@
       }
       assert inFlow.isBaseInFlow()
           || inFlow.isCastAbstractFunction()
+          || inFlow.isIfThenElseAbstractFunction()
           || inFlow.isInstanceFieldReadAbstractFunction();
       return ConcreteValueState.create(staticType, inFlow);
     }
@@ -674,12 +763,14 @@
       }
 
       for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+        Value argumentValue = invoke.getArgument(argumentIndex);
         parameterStates.add(
             computeParameterStateForNonReceiver(
                 invoke,
                 singleTarget,
                 argumentIndex,
-                invoke.getArgument(argumentIndex),
+                argumentValue,
+                argumentValue,
                 existingMethodState));
       }
 
@@ -717,16 +808,17 @@
           : new ConcreteReceiverValueState(dynamicReceiverType);
     }
 
-    @SuppressWarnings("UnusedVariable")
-    private ValueState computeParameterStateForNonReceiver(
+    private NonEmptyValueState computeParameterStateForNonReceiver(
         InvokeMethod invoke,
         ProgramMethod singleTarget,
         int argumentIndex,
-        Value argument,
+        Value value,
+        Value initialValue,
         ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+      assert value == initialValue || initialValue.getAliasedValue().isPhi();
       NonEmptyValueState modeledState =
           modeling.modelParameterStateForArgumentToFunction(
-              invoke, singleTarget, argumentIndex, argument, context);
+              invoke, singleTarget, argumentIndex, value, context);
       if (modeledState != null) {
         return modeledState;
       }
@@ -745,23 +837,36 @@
       // instead record a flow constraint that specifies that all values that flow into the
       // parameter of this enclosing method also flows into the corresponding parameter of the
       // methods potentially called from this invoke instruction.
-      NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
+      NonEmptyValueState inFlowState =
+          computeInFlowState(
+              parameterType,
+              value,
+              initialValue,
+              context,
+              phiOperandArgumentValue ->
+                  computeParameterStateForNonReceiver(
+                      invoke,
+                      singleTarget,
+                      argumentIndex,
+                      phiOperandArgumentValue,
+                      initialValue,
+                      existingMethodState));
       if (inFlowState != null) {
         return inFlowState;
       }
 
       // Only track the nullability for array types.
       if (parameterType.isArrayType()) {
-        Nullability nullability = argument.getType().nullability();
+        Nullability nullability = value.getType().nullability();
         return ConcreteArrayTypeValueState.create(nullability);
       }
 
-      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
+      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(value);
 
       // For class types, we track both the abstract value and the dynamic type. If both are
       // unknown, then use UnknownParameterState.
       if (parameterType.isClassType()) {
-        DynamicType dynamicType = argument.getDynamicType(appView);
+        DynamicType dynamicType = value.getDynamicType(appView);
         DynamicType widenedDynamicType =
             WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
         return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index 7261359..df2c3b8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -175,4 +175,11 @@
     return inFlow != null
         && inFlow.size() > appView.options().callSiteOptimizationOptions().getMaxInFlowSize();
   }
+
+  public boolean verifyOnlyBaseInFlow() {
+    for (InFlow inFlow : inFlow) {
+      assert inFlow.isBaseInFlow();
+    }
+    return true;
+  }
 }
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 4b19a1d..c51a4cf 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
@@ -30,14 +30,16 @@
         }
 
         @Override
-        public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
+        public ValueState getState(
+            MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider) {
           throw new Unreachable();
         }
       };
     }
     // Otherwise, restrict state lookups to the declared base in flow. This is required for arriving
     // at the correct fix point.
-    assert abstractFunction.isInstanceFieldReadAbstractFunction();
+    assert abstractFunction.isIfThenElseAbstractFunction()
+        || abstractFunction.isInstanceFieldReadAbstractFunction();
     return new FlowGraphStateProvider() {
 
       @Override
@@ -47,14 +49,24 @@
       }
 
       @Override
-      public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
-        assert abstractFunction.verifyContainsBaseInFlow(inFlow);
-        return flowGraph.getState(inFlow, defaultStateProvider);
+      public ValueState getState(
+          MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider) {
+        assert abstractFunction.verifyContainsBaseInFlow(methodParameter);
+        return flowGraph.getState(methodParameter, defaultStateProvider);
       }
     };
   }
 
   ValueState getState(DexField field);
 
-  ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider);
+  ValueState getState(MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider);
+
+  default ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
+    if (inFlow.isFieldValue()) {
+      return getState(inFlow.asFieldValue().getField());
+    } else {
+      assert inFlow.isMethodParameter();
+      return getState(inFlow.asMethodParameter(), defaultStateProvider);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
new file mode 100644
index 0000000..1e24de2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
@@ -0,0 +1,157 @@
+// 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.List;
+
+/**
+ * Represents a ternary expression (exp ? u : v). The {@link #condition} is an expression containing
+ * a single open variable which evaluates to an abstract value. If the resulting abstract value is
+ * true, then `u` is chosen. If the abstract value is false, then `v` is chosen. Otherwise, the
+ * result is unknown.
+ */
+// TODO(b/302281503): Evaluate the impact of using the join of `u` and `v` instead of unknown when
+//  the condition does not evaluate to true or false.
+public class IfThenElseAbstractFunction implements AbstractFunction {
+
+  private final ComputationTreeNode condition;
+  private final NonEmptyValueState thenState;
+  private final NonEmptyValueState elseState;
+
+  public IfThenElseAbstractFunction(
+      ComputationTreeNode condition, NonEmptyValueState thenState, NonEmptyValueState elseState) {
+    assert condition.getSingleOpenVariable() != null;
+    assert !thenState.isUnknown() || !elseState.isUnknown();
+    assert !thenState.isConcrete() || thenState.asConcrete().verifyOnlyBaseInFlow();
+    assert !elseState.isConcrete() || elseState.asConcrete().verifyOnlyBaseInFlow();
+    this.condition = condition;
+    this.thenState = thenState;
+    this.elseState = elseState;
+  }
+
+  @Override
+  public ValueState apply(
+      AppView<AppInfoWithLiveness> appView,
+      FlowGraphStateProvider flowGraphStateProvider,
+      ConcreteValueState inState) {
+    AbstractValue conditionValue = evaluateCondition(appView, flowGraphStateProvider);
+    NonEmptyValueState resultState;
+    if (conditionValue.isTrue()) {
+      resultState = thenState;
+    } else if (conditionValue.isFalse()) {
+      resultState = elseState;
+    } else {
+      return ValueState.unknown();
+    }
+    if (resultState.isUnknown()) {
+      return resultState;
+    }
+    assert resultState.isConcrete();
+    ConcreteValueState concreteResultState = resultState.asConcrete();
+    if (!concreteResultState.hasInFlow()) {
+      return concreteResultState;
+    }
+    return resolveInFlow(appView, flowGraphStateProvider, concreteResultState);
+  }
+
+  private AbstractValue evaluateCondition(
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    MethodParameter openVariable = condition.getSingleOpenVariable();
+    assert openVariable != null;
+    ValueState variableState = flowGraphStateProvider.getState(openVariable, () -> null);
+    if (variableState == null) {
+      // TODO(b/302281503): Conservatively return unknown for now. Investigate exactly when this
+      //  happens and whether we can return something more precise instead of unknown.
+      assert false;
+      return AbstractValue.unknown();
+    }
+    AbstractValue variableValue = variableState.getAbstractValue(appView);
+    // Since the condition is guaranteed to have a single open variable we simply return the
+    // `variableValue` for any given argument index.
+    return condition.evaluate(i -> variableValue, appView.abstractValueFactory());
+  }
+
+  private ValueState resolveInFlow(
+      AppView<AppInfoWithLiveness> appView,
+      FlowGraphStateProvider flowGraphStateProvider,
+      ConcreteValueState resultStateWithInFlow) {
+    ValueState resultStateWithoutInFlow = resultStateWithInFlow.mutableCopyWithoutInFlow();
+    for (InFlow inFlow : resultStateWithInFlow.getInFlow()) {
+      // We currently only allow the primitive kinds of in flow (fields and method parameters) to
+      // occur in the states.
+      assert inFlow.isBaseInFlow();
+      ValueState inFlowState = flowGraphStateProvider.getState(inFlow.asBaseInFlow(), () -> null);
+      if (inFlowState == null) {
+        assert false;
+        return ValueState.unknown();
+      }
+      // TODO(b/302281503): The IfThenElseAbstractFunction is only used on input to base in flow.
+      //  We should set  the `outStaticType` to the static type of the current field/parameter.
+      DexType inStaticType = null;
+      DexType outStaticType = null;
+      resultStateWithoutInFlow =
+          resultStateWithoutInFlow.mutableJoin(
+              appView, inFlowState, inStaticType, outStaticType, StateCloner.getCloner());
+    }
+    return resultStateWithoutInFlow;
+  }
+
+  @Override
+  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
+    // TODO(b/302281503): Implement this.
+    return true;
+  }
+
+  @Override
+  public Iterable<BaseInFlow> getBaseInFlow() {
+    List<BaseInFlow> baseInFlow = ListUtils.newArrayList(condition.getSingleOpenVariable());
+    if (thenState.isConcrete()) {
+      for (InFlow inFlow : thenState.asConcrete().getInFlow()) {
+        assert inFlow.isBaseInFlow();
+        baseInFlow.add(inFlow.asBaseInFlow());
+      }
+    }
+    if (elseState.isConcrete()) {
+      for (InFlow inFlow : elseState.asConcrete().getInFlow()) {
+        assert inFlow.isBaseInFlow();
+        baseInFlow.add(inFlow.asBaseInFlow());
+      }
+    }
+    return baseInFlow;
+  }
+
+  @Override
+  public boolean hasSingleInFlow() {
+    // TODO(b/302281503): This method is used to determine if arbitrary state lookup is allowed.
+    //  Rename this method.
+    return false;
+  }
+
+  @Override
+  public int internalCompareToSameKind(InFlow inFlow) {
+    // TODO(b/302281503): Find a way to make this comparable.
+    return hashCode() - inFlow.hashCode();
+  }
+
+  @Override
+  public boolean isIfThenElseAbstractFunction() {
+    return true;
+  }
+
+  @Override
+  public IfThenElseAbstractFunction asIfThenElseAbstractFunction() {
+    return this;
+  }
+
+  @Override
+  public InFlowKind getKind() {
+    return InFlowKind.ABSTRACT_FUNCTION_IF_THEN_ELSE;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
index e261214..4590558 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
@@ -57,6 +57,14 @@
     return null;
   }
 
+  default boolean isIfThenElseAbstractFunction() {
+    return false;
+  }
+
+  default IfThenElseAbstractFunction asIfThenElseAbstractFunction() {
+    return null;
+  }
+
   default boolean isInstanceFieldReadAbstractFunction() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
index d516ee1..0714caf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
@@ -6,6 +6,7 @@
 public enum InFlowKind {
   ABSTRACT_FUNCTION_CAST,
   ABSTRACT_FUNCTION_IDENTITY,
+  ABSTRACT_FUNCTION_IF_THEN_ELSE,
   ABSTRACT_FUNCTION_INSTANCE_FIELD_READ,
   ABSTRACT_FUNCTION_OR,
   ABSTRACT_FUNCTION_UNKNOWN,
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
index 58ebf78..c9649f8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
@@ -9,9 +9,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
 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.FlowGraphStateProvider;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
@@ -97,21 +95,15 @@
   }
 
   @Override
-  public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
-    if (inFlow.isFieldValue()) {
-      FieldValue fieldValue = inFlow.asFieldValue();
-      return getState(fieldValue.getField());
-    } else {
-      assert inFlow.isMethodParameter();
-      MethodParameter methodParameter = inFlow.asMethodParameter();
-      FlowGraphParameterNode parameterNode =
-          parameterNodes
-              .getOrDefault(methodParameter.getMethod(), Int2ReferenceMaps.emptyMap())
-              .get(methodParameter.getIndex());
-      if (parameterNode != null) {
-        return parameterNode.getState();
-      }
-      return defaultStateProvider.get();
+  public ValueState getState(
+      MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider) {
+    FlowGraphParameterNode parameterNode =
+        parameterNodes
+            .getOrDefault(methodParameter.getMethod(), Int2ReferenceMaps.emptyMap())
+            .get(methodParameter.getIndex());
+    if (parameterNode != null) {
+      return parameterNode.getState();
     }
+    return defaultStateProvider.get();
   }
 }
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 cd63846..14de866 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2454,6 +2454,7 @@
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableEnqueuerDeferredTracingForReferenceFields =
         System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null;
+    public boolean enableIfThenElseAbstractFunction = false;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableLegacyClassDefOrdering =
         System.getProperty("com.android.tools.r8.enableLegacyClassDefOrdering") != null;
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 9d571ee..93c4be1 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
@@ -3,7 +3,7 @@
 // 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.CodeMatchers.isInvokeWithTarget;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -41,6 +41,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.getTestingOptions().enableIfThenElseAbstractFunction = true)
         .enableInliningAnnotations()
         .setMinApi(parameters)
         .compile()
@@ -52,24 +54,23 @@
               ClassSubject lambdaClassSubject =
                   inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0));
               assertThat(lambdaClassSubject, isPresent());
-              // TODO(b/302281503): Lambda should not capture the two constant string arguments.
-              assertEquals(2, lambdaClassSubject.allInstanceFields().size());
+              assertEquals(0, lambdaClassSubject.allInstanceFields().size());
 
               MethodSubject lambdaInitSubject = lambdaClassSubject.uniqueInstanceInitializer();
-              assertThat(lambdaInitSubject, isPresent());
-              // TODO(b/302281503): Lambda should not capture the two constant string arguments.
-              assertEquals(2, lambdaInitSubject.getParameters().size());
-              assertTrue(lambdaInitSubject.getParameter(0).is(String.class));
-              assertTrue(lambdaInitSubject.getParameter(1).is(String.class));
+              assertThat(
+                  lambdaInitSubject,
+                  isAbsentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
+              if (lambdaInitSubject.isPresent()) {
+                assertEquals(0, lambdaInitSubject.getParameters().size());
+              }
 
               MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
               assertThat(mainMethodSubject, isPresent());
-              // TODO(b/302281503): This argument should be removed as a result of constant
-              //  propagation into the restartableMethod.
               assertTrue(
                   mainMethodSubject
                       .streamInstructions()
-                      .anyMatch(instruction -> instruction.isConstString("DefaultValueNeverUsed")));
+                      .noneMatch(
+                          instruction -> instruction.isConstString("DefaultValueNeverUsed")));
               // TODO(b/302281503): This argument is never used and should be removed.
               assertTrue(
                   mainMethodSubject
@@ -96,11 +97,6 @@
                       .streamInstructions()
                       .anyMatch(
                           instruction -> instruction.isConstString("DefaultValueAlwaysUsed")));
-              assertTrue(
-                  restartableMethodSubject
-                      .streamInstructions()
-                      .anyMatch(
-                          instruction -> isInvokeWithTarget(lambdaInitSubject).test(instruction)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(