Extend field/parameter propagation to include object state
Bug: b/296030319
Change-Id: I6a609972a0017fa220b7cff64239a5aeabee9dce
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index 1f2b960..d16e2b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -29,7 +28,7 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 28652ca..25a9566 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
@@ -40,7 +39,7 @@
public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
index f05e962..a773fd2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -27,7 +26,7 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 3b73b9d..7acebdd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -32,8 +31,8 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
- return state.getOrDefault(field.getReference(), UnknownValue.getInstance());
+ public AbstractValue getAbstractFieldValue(DexField field) {
+ return state.getOrDefault(field, UnknownValue.getInstance());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 3b02fc4..271cd94 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -47,7 +47,11 @@
return predicate.test(singleValue);
}
- public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
+ public final AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ return getAbstractFieldValue(field.getReference());
+ }
+
+ public abstract AbstractValue getAbstractFieldValue(DexField field);
public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
index dea5836..f53f615 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
@@ -20,13 +20,13 @@
public static ObjectState computeObjectState(
Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- assert !value.hasAliasedValue();
- if (value.isDefinedByInstructionSatisfying(
+ Value valueRoot = value.getAliasedValue();
+ if (valueRoot.isDefinedByInstructionSatisfying(
i -> i.isNewArrayEmpty() || i.isNewArrayFilledData() || i.isNewArrayFilled())) {
- return computeNewArrayObjectState(value, appView, context);
+ return computeNewArrayObjectState(valueRoot, appView, context);
}
- if (value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
- return computeNewInstanceObjectState(value, appView, context);
+ if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+ return computeNewInstanceObjectState(valueRoot, appView, context);
}
return ObjectState.empty();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
index 6b7f62c..c1dee99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -19,6 +19,8 @@
boolean isInstanceGet();
+ InstanceGet asInstanceGet();
+
boolean isStaticGet();
boolean hasUsedOutValue();
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 5ec5473..8df07a0 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,6 +4,8 @@
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;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -19,6 +21,7 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectStateAnalysis;
import com.android.tools.r8.ir.code.AbstractValueSupplier;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -45,6 +48,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
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;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
@@ -66,6 +70,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Analyzes each {@link IRCode} during the primary optimization to collect information about the
@@ -268,8 +273,10 @@
AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
if (abstractValue.isUnknown()) {
- // TODO(b/296030319): Add the current object state to the computed fallback abstract value.
- abstractValue = getFallbackAbstractValueForField(field);
+ abstractValue =
+ getFallbackAbstractValueForField(
+ field,
+ () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
}
if (field.getType().isClassType()) {
DynamicType dynamicType =
@@ -297,6 +304,14 @@
if (field == null) {
return null;
}
+ if (fieldGet.isInstanceGet()) {
+ Value receiverValue = fieldGet.asInstanceGet().object();
+ BaseInFlow receiverInFlow = asBaseInFlowOrNull(computeInFlow(receiverValue, context));
+ if (receiverInFlow != null
+ && receiverInFlow.equals(widenBaseInFlow(receiverInFlow, context))) {
+ return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+ }
+ }
return widenBaseInFlow(fieldValueFactory.create(field), context);
}
return null;
@@ -325,7 +340,7 @@
if (inFlow.isUnknownAbstractFunction()) {
return ValueState.unknown();
}
- assert inFlow.isBaseInFlow();
+ assert inFlow.isBaseInFlow() || inFlow.isInstanceFieldReadAbstractFunction();
return ConcreteValueState.create(staticType, inFlow);
}
@@ -334,7 +349,8 @@
// will never have their value changed after the <clinit> finishes, so value in a static final
// field can always be rematerialized by reading the field.
private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
- AbstractValue fallbackAbstractValue = getFallbackAbstractValueForField(field);
+ AbstractValue fallbackAbstractValue =
+ getFallbackAbstractValueForField(field, ObjectState::empty);
if (!fallbackAbstractValue.isUnknown()) {
AbstractValue abstractValue = fieldState.getAbstractValue(appView);
if (!abstractValue.isUnknown()) {
@@ -359,11 +375,12 @@
}
// TODO(b/296030319): Also handle effectively final fields.
- private AbstractValue getFallbackAbstractValueForField(ProgramField field) {
+ private AbstractValue getFallbackAbstractValueForField(
+ ProgramField field, Supplier<ObjectState> objectStateSupplier) {
if (field.getAccessFlags().isFinal() && field.getAccessFlags().isStatic()) {
return appView
.abstractValueFactory()
- .createSingleFieldValue(field.getReference(), ObjectState.empty());
+ .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
}
return AbstractValue.unknown();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
index 20ec5c7..ef35ee9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
@@ -5,6 +5,10 @@
public interface BaseInFlow extends InFlow {
+ static BaseInFlow asBaseInFlowOrNull(InFlow inFlow) {
+ return inFlow != null ? inFlow.asBaseInFlow() : null;
+ }
+
@Override
default boolean isBaseInFlow() {
return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index bf3d1f9..a38f3bf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -60,7 +60,7 @@
@Override
public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
- if (getDynamicType().getNullability().isDefinitelyNull()) {
+ if (getNullability().isDefinitelyNull()) {
assert abstractValue.isNull() || abstractValue.isUnknown();
return appView.abstractValueFactory().createUncheckedNullValue();
}
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 5bc8659..6fd5ce4 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
@@ -6,6 +6,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
import java.util.Collections;
@@ -27,6 +29,17 @@
this.inFlow = inFlow;
}
+ public static NonEmptyValueState create(DexType staticType, AbstractValue abstractValue) {
+ if (staticType.isArrayType()) {
+ return unknown();
+ } else if (staticType.isClassType()) {
+ return ConcreteClassTypeValueState.create(abstractValue, DynamicType.unknown());
+ } else {
+ assert staticType.isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
+ }
+
public static ConcreteValueState create(DexType staticType, InFlow inFlow) {
if (staticType.isArrayType()) {
return new ConcreteArrayTypeValueState(inFlow);
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 3cb0550..a305998 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
@@ -31,6 +31,14 @@
return null;
}
+ default boolean isInstanceFieldReadAbstractFunction() {
+ return false;
+ }
+
+ default InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+ return null;
+ }
+
default boolean isMethodParameter() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
new file mode 100644
index 0000000..9a8cf97
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
@@ -0,0 +1,59 @@
+// 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.DexField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class InstanceFieldReadAbstractFunction implements AbstractFunction {
+
+ private final BaseInFlow receiver;
+ private final DexField field;
+
+ public InstanceFieldReadAbstractFunction(BaseInFlow receiver, DexField field) {
+ this.receiver = receiver;
+ this.field = field;
+ }
+
+ // TODO(b/296030319): Instead of returning unknown from here, we should fallback to the state of
+ // the instance field node in the graph. A prerequisite for this is the ability to express
+ // multiple inputs to abstract functions.
+ @Override
+ public NonEmptyValueState apply(ConcreteValueState state) {
+ if (!state.isClassState()) {
+ return ValueState.unknown();
+ }
+ ConcreteClassTypeValueState classState = state.asClassState();
+ if (classState.getNullability().isDefinitelyNull()) {
+ // TODO(b/296030319): This should be rare, but we should really return bottom here, since
+ // reading a field from the the null value throws an exception, meaning no flow should be
+ // propagated.
+ return ValueState.unknown();
+ }
+ AbstractValue abstractValue = state.getAbstractValue(null);
+ if (!abstractValue.hasObjectState()) {
+ return ValueState.unknown();
+ }
+ AbstractValue fieldValue = abstractValue.getObjectState().getAbstractFieldValue(field);
+ if (fieldValue.isUnknown()) {
+ return ValueState.unknown();
+ }
+ return ConcreteValueState.create(field.getType(), fieldValue);
+ }
+
+ @Override
+ public InFlow getBaseInFlow() {
+ return receiver;
+ }
+
+ @Override
+ public boolean isInstanceFieldReadAbstractFunction() {
+ return true;
+ }
+
+ @Override
+ public InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
index 728cd78..6272631 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
@@ -51,14 +51,12 @@
MethodSubject printMethodSubject =
mainClassSubject.uniqueMethodWithOriginalName("print");
assertThat(printMethodSubject, isPresent());
- // TODO(b/296030319): Should be 0.
- assertEquals(1, printMethodSubject.getProgramMethod().getArity());
+ assertEquals(0, printMethodSubject.getProgramMethod().getArity());
MethodSubject printlnMethodSubject =
mainClassSubject.uniqueMethodWithOriginalName("println");
assertThat(printlnMethodSubject, isPresent());
- // TODO(b/296030319): Should be 0.
- assertEquals(1, printlnMethodSubject.getProgramMethod().getArity());
+ assertEquals(0, printlnMethodSubject.getProgramMethod().getArity());
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello, world!");