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(