Eliminate dead field accesses and their dominated code
Fixes: b/330833514
Change-Id: Iad96367c89d80b84f1a0a5748bed3b423a166662
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 058be03..b73ba99 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -194,7 +194,7 @@
// Find all the code objects that need reprocessing.
new ArgumentPropagatorMethodReprocessingEnqueuer(appView, reprocessingCriteriaCollection)
- .enqueueMethodForReprocessing(
+ .enqueueAndPrepareMethodsForReprocessing(
graphLens, postMethodProcessorBuilder, executorService, timing);
reprocessingCriteriaCollection = null;
@@ -245,7 +245,7 @@
.propagateOptimizationInfo(executorService, timing);
// TODO(b/296030319): Also publish the computed optimization information for fields.
new ArgumentPropagatorOptimizationInfoPopulator(
- appView, converter, methodStates, postMethodProcessorBuilder)
+ appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
.populateOptimizationInfo(executorService, timing);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index b857d7d..89fc2b0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.optimize.argumentpropagation;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -14,17 +15,31 @@
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.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirConstant;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -45,7 +60,7 @@
* Called indirectly from {@link IRConverter} to add all methods that require reprocessing to
* {@param postMethodProcessorBuilder}.
*/
- public void enqueueMethodForReprocessing(
+ public void enqueueAndPrepareMethodsForReprocessing(
ArgumentPropagatorGraphLens graphLens,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
@@ -69,6 +84,10 @@
}
timing.end();
+ timing.begin("Eliminate dead field accesses");
+ eliminateDeadFieldAccesses(executorService);
+ timing.end();
+
timing.end();
}
@@ -144,6 +163,77 @@
postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
}
+ private void eliminateDeadFieldAccesses(ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ clazz.forEachProgramMethodMatching(
+ m -> m.hasCode() && !m.getCode().isSharedCodeObject(),
+ this::eliminateDeadFieldAccesses);
+ },
+ appView.options().getThreadingModule(),
+ executorService);
+ }
+
+ private void eliminateDeadFieldAccesses(ProgramMethod method) {
+ Code code = method.getDefinition().getCode();
+ if (!code.isLirCode()) {
+ assert appView.isCfByteCodePassThrough(method.getDefinition());
+ return;
+ }
+ LirCode<Integer> lirCode = code.asLirCode();
+ LirCode<Integer> rewrittenLirCode = eliminateDeadFieldAccesses(method, lirCode);
+ if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
+ method.setCode(rewrittenLirCode, appView);
+ }
+ }
+
+ private LirCode<Integer> eliminateDeadFieldAccesses(
+ ProgramMethod method, LirCode<Integer> lirCode) {
+ if (ArrayUtils.none(
+ lirCode.getConstantPool(), constant -> isDeadFieldAccess(constant, method))) {
+ return lirCode;
+ }
+ IRCode irCode = lirCode.buildIR(method, appView);
+ AffectedValues affectedValues = new AffectedValues();
+ BasicBlockIterator blocks = irCode.listIterator();
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ if (blocksToRemove.contains(block)) {
+ continue;
+ }
+ InstructionListIterator instructionIterator = block.listIterator(irCode);
+ while (instructionIterator.hasNext()) {
+ FieldInstruction fieldInstruction = instructionIterator.next().asFieldInstruction();
+ if (fieldInstruction == null || !isDeadFieldAccess(fieldInstruction.getField(), method)) {
+ continue;
+ }
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appView, irCode, blocks, blocksToRemove, affectedValues);
+ }
+ }
+ irCode.removeBlocks(blocksToRemove);
+ affectedValues.narrowingWithAssumeRemoval(appView, irCode);
+ irCode.removeRedundantBlocks();
+ return new IRToLirFinalizer(appView)
+ .finalizeCode(irCode, BytecodeMetadataProvider.empty(), Timing.empty());
+ }
+
+ private boolean isDeadFieldAccess(LirConstant constant, ProgramMethod context) {
+ if (!(constant instanceof DexField)) {
+ return false;
+ }
+ DexField fieldReference = (DexField) constant;
+ return isDeadFieldAccess(fieldReference, context);
+ }
+
+ private boolean isDeadFieldAccess(DexField fieldReference, ProgramMethod context) {
+ ProgramField field = appView.appInfo().resolveField(fieldReference, context).getProgramField();
+ return field != null && field.getOptimizationInfo().isDead();
+ }
+
static class AffectedMethodUseRegistry
extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index e4181f2..2b14cf3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -17,11 +18,11 @@
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
@@ -47,6 +48,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final PrimaryR8IRConverter converter;
+ private final FieldStateCollection fieldStates;
private final MethodStateCollectionByReference methodStates;
private final InternalOptions options;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
@@ -54,10 +56,12 @@
public ArgumentPropagatorOptimizationInfoPopulator(
AppView<AppInfoWithLiveness> appView,
PrimaryR8IRConverter converter,
+ FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates,
PostMethodProcessor.Builder postMethodProcessorBuilder) {
this.appView = appView;
this.converter = converter;
+ this.fieldStates = fieldStates;
this.methodStates = methodStates;
this.options = appView.options();
this.postMethodProcessorBuilder = postMethodProcessorBuilder;
@@ -94,11 +98,21 @@
private ProgramMethodSet setOptimizationInfo(DexProgramClass clazz) {
ProgramMethodSet prunedMethods = ProgramMethodSet.create();
+ clazz.forEachProgramField(this::setOptimizationInfo);
clazz.forEachProgramMethod(method -> setOptimizationInfo(method, prunedMethods));
clazz.getMethodCollection().removeMethods(prunedMethods.toDefinitionSet());
return prunedMethods;
}
+ public void setOptimizationInfo(ProgramField field) {
+ ValueState state = fieldStates.remove(field);
+ // TODO(b/296030319): Also publish non-bottom field states.
+ if (state.isBottom()) {
+ getSimpleFeedback()
+ .recordFieldHasAbstractValue(field.getDefinition(), appView, AbstractValue.bottom());
+ }
+ }
+
public void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
setOptimizationInfo(method, prunedMethods, methodStates.remove(method));
}
@@ -184,7 +198,7 @@
if (optimizationInfo.returnsArgument()) {
ValueState returnedArgumentState =
monomorphicMethodState.getParameterState(optimizationInfo.getReturnedArgument());
- OptimizationFeedback.getSimple()
+ getSimpleFeedback()
.methodReturnsAbstractValue(
method.getDefinition(), appView, returnedArgumentState.getAbstractValue(appView));
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index f325f4a..567a559 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -27,7 +27,7 @@
StateCloner cloner,
Action onChangedAction) {
if (state.isBottom()) {
- assert state == bottomPrimitiveTypeParameter();
+ assert state == bottomPrimitiveTypeState();
return this;
}
if (state.isUnknown()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 46a26d7..d973ed8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -40,18 +40,6 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (nullability.isBottom()) {
- return bottomArrayTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
- }
-
- @Override
public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
if (getNullability().isDefinitelyNull()) {
return appView.abstractValueFactory().createUncheckedNullValue();
@@ -65,6 +53,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomArrayTypeState();
+ }
+
+ @Override
public ConcreteParameterStateKind getKind() {
return ConcreteParameterStateKind.ARRAY;
}
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 a38f3bf..4653731 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
@@ -46,19 +46,6 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (abstractValue.isBottom()) {
- assert dynamicType.isBottom();
- return bottomClassTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
- }
-
- @Override
public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
if (getNullability().isDefinitelyNull()) {
assert abstractValue.isNull() || abstractValue.isUnknown();
@@ -68,6 +55,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomClassTypeState();
+ }
+
+ @Override
public DynamicType getDynamicType() {
return dynamicType;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index fbd733b..816e303 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -44,18 +44,6 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (abstractValue.isBottom()) {
- return bottomPrimitiveTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
- }
-
- @Override
public ConcretePrimitiveTypeValueState mutableCopy() {
return new ConcretePrimitiveTypeValueState(abstractValue, copyInFlow());
}
@@ -110,6 +98,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomPrimitiveTypeState();
+ }
+
+ @Override
public ConcreteParameterStateKind getKind() {
return ConcreteParameterStateKind.PRIMITIVE;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index 186dae0..6169d4a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -31,20 +31,13 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (dynamicType.isBottom()) {
- return bottomReceiverParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
+ public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
+ return AbstractValue.unknown();
}
@Override
- public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
- return AbstractValue.unknown();
+ public BottomValueState getCorrespondingBottom() {
+ return bottomReceiverParameter();
}
@Override
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 6fd5ce4..e32324a 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
@@ -51,7 +51,16 @@
}
}
- public abstract ValueState clearInFlow();
+ public ValueState clearInFlow() {
+ if (hasInFlow()) {
+ internalClearInFlow();
+ if (isEffectivelyBottom()) {
+ return getCorrespondingBottom();
+ }
+ }
+ assert !isEffectivelyBottom();
+ return this;
+ }
void internalClearInFlow() {
inFlow = Collections.emptySet();
@@ -74,6 +83,8 @@
return inFlow;
}
+ public abstract BottomValueState getCorrespondingBottom();
+
public abstract ConcreteParameterStateKind getKind();
public abstract boolean isEffectivelyBottom();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
index d323112..544c365 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
@@ -88,7 +88,20 @@
return fieldState != null ? fieldState : ValueState.bottom(field);
}
- public void set(ProgramField field, NonEmptyValueState state) {
- fieldStates.put(field, state);
+ public ValueState remove(ProgramField field) {
+ ValueState removed = fieldStates.remove(field);
+ return removed != null ? removed : ValueState.bottom(field);
+ }
+
+ public ValueState set(ProgramField field, ValueState state) {
+ if (state.isNonEmpty()) {
+ return set(field, state.asNonEmpty());
+ }
+ return remove(field);
+ }
+
+ public ValueState set(ProgramField field, NonEmptyValueState state) {
+ NonEmptyValueState previous = fieldStates.put(field, state);
+ return previous != null ? previous : ValueState.bottom(field);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index 7e91edf..96adb2c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -21,28 +21,28 @@
public static BottomValueState bottom(DexField field) {
DexType fieldType = field.getType();
if (fieldType.isArrayType()) {
- return bottomArrayTypeParameter();
+ return bottomArrayTypeState();
} else if (fieldType.isClassType()) {
- return bottomClassTypeParameter();
+ return bottomClassTypeState();
} else {
assert fieldType.isPrimitiveType();
- return bottomPrimitiveTypeParameter();
+ return bottomPrimitiveTypeState();
}
}
- public static BottomValueState bottomArrayTypeParameter() {
+ public static BottomArrayTypeValueState bottomArrayTypeState() {
return BottomArrayTypeValueState.get();
}
- public static BottomValueState bottomClassTypeParameter() {
+ public static BottomClassTypeValueState bottomClassTypeState() {
return BottomClassTypeValueState.get();
}
- public static BottomValueState bottomPrimitiveTypeParameter() {
+ public static BottomPrimitiveTypeValueState bottomPrimitiveTypeState() {
return BottomPrimitiveTypeValueState.get();
}
- public static BottomValueState bottomReceiverParameter() {
+ public static BottomReceiverValueState bottomReceiverParameter() {
return BottomReceiverValueState.get();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 949bc53..68381bf 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -5,18 +5,32 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramFieldSet;
import com.google.common.collect.Lists;
import java.util.ArrayDeque;
@@ -34,10 +48,15 @@
public class DefaultFieldValueJoiner {
private final AppView<AppInfoWithLiveness> appView;
+ private final FieldStateCollection fieldStates;
private final List<FlowGraph> flowGraphs;
- public DefaultFieldValueJoiner(AppView<AppInfoWithLiveness> appView, List<FlowGraph> flowGraphs) {
+ public DefaultFieldValueJoiner(
+ AppView<AppInfoWithLiveness> appView,
+ FieldStateCollection fieldStates,
+ List<FlowGraph> flowGraphs) {
this.appView = appView;
+ this.fieldStates = fieldStates;
this.flowGraphs = flowGraphs;
}
@@ -53,7 +72,7 @@
Map<DexProgramClass, List<ProgramField>> nonFinalInstanceFields =
removeFieldsNotSubjectToInitializerAnalysis(fieldsOfInterest);
ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
- analyzeInstanceInitializers(fieldsOfInterest, fieldsWithLiveDefaultValue::add, executorService);
+ analyzeInitializers(fieldsOfInterest, fieldsWithLiveDefaultValue::add, executorService);
// For non-final fields where writes in instance initializers may have been subject to
// constructor inlining, we find all new-instance instructions (including subtype allocations)
@@ -65,13 +84,22 @@
private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
- for (FlowGraph flowGraph : flowGraphs) {
- // TODO(b/296030319): We only need to include the fields where including the default value
- // would make a difference. Then we can assert below in updateFlowGraphs() that adding the
- // default value changes the field state.
- flowGraph.forEachFieldNode(
- node -> {
- ProgramField field = node.getField();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramField(
+ field -> {
+ // We only need to include the fields where including the default value would make a
+ // difference. Then we can assert below in updateFlowGraphs() that adding the default
+ // value
+ // changes the field state.
+ // TODO(b/296030319): Implement this for primitive fields.
+ ValueState state = fieldStates.get(field);
+ if (state.isUnknown()) {
+ return;
+ }
+ if (state.isReferenceState()
+ && state.asReferenceState().getNullability().isNullable()) {
+ return;
+ }
fieldsOfInterest
.computeIfAbsent(field.getHolder(), ignoreKey(ArrayList::new))
.add(field);
@@ -108,7 +136,7 @@
return nonFinalInstanceFields;
}
- private void analyzeInstanceInitializers(
+ private void analyzeInitializers(
Map<DexProgramClass, List<ProgramField>> fieldsOfInterest,
Consumer<ProgramField> concurrentLiveDefaultValueConsumer,
ExecutorService executorService)
@@ -201,9 +229,10 @@
flowGraph.forEachFieldNode(
node -> {
ProgramField field = node.getField();
- if (fieldsWithLiveDefaultValue.contains(field)) {
- node.addDefaultValue(
+ if (fieldsWithLiveDefaultValue.remove(field)) {
+ node.addState(
appView,
+ getDefaultValueState(field),
() -> {
if (node.isUnknown()) {
node.clearPredecessors();
@@ -217,7 +246,48 @@
pair -> !pair.getSecond().isEmpty(),
appView.options().getThreadingModule(),
executorService);
+ // Unseen fields are not added to any flow graphs, since they are not needed for flow
+ // propagation. Update these fields directly in the field state collection.
+ for (ProgramField field : fieldsWithLiveDefaultValue) {
+ fieldStates.addTemporaryFieldState(
+ appView, field, () -> getDefaultValueState(field), Timing.empty());
+ }
return MapUtils.newIdentityHashMap(
builder -> worklists.forEach(pair -> builder.put(pair.getFirst(), pair.getSecond())));
}
+
+ private ConcreteValueState getDefaultValueState(ProgramField field) {
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ AbstractValue defaultValue;
+ if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
+ defaultValue = field.getDefinition().getStaticValue().toAbstractValue(abstractValueFactory);
+ } else if (field.getType().isPrimitiveType()) {
+ defaultValue = abstractValueFactory.createZeroValue();
+ } else {
+ defaultValue = abstractValueFactory.createUncheckedNullValue();
+ }
+ NonEmptyValueState fieldStateToAdd;
+ if (field.getType().isArrayType()) {
+ Nullability defaultNullability = Nullability.definitelyNull();
+ fieldStateToAdd = ConcreteArrayTypeValueState.create(defaultNullability);
+ } else if (field.getType().isClassType()) {
+ assert defaultValue.isNull()
+ || defaultValue.isSingleStringValue()
+ || defaultValue.isSingleDexItemBasedStringValue();
+ DynamicType dynamicType =
+ defaultValue.isNull()
+ ? DynamicType.definitelyNull()
+ : DynamicType.createExact(
+ TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+ fieldStateToAdd = ConcreteClassTypeValueState.create(defaultValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ fieldStateToAdd = ConcretePrimitiveTypeValueState.create(defaultValue);
+ }
+ // We should always be able to map static field values to an unknown abstract value.
+ if (fieldStateToAdd.isUnknown()) {
+ throw new Unreachable();
+ }
+ return fieldStateToAdd.asConcrete();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
index a998a15..00d36e6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -98,9 +98,13 @@
}
}
- if (!node.getState().isUnknown()) {
- assert node.getState() == concreteFieldState;
- node.setState(concreteFieldState.clearInFlow());
+ ValueState concreteFieldStateOrBottom = concreteFieldState.clearInFlow();
+ if (concreteFieldStateOrBottom.isBottom()) {
+ fieldStates.remove(field);
+ if (!node.getState().isUnknown()) {
+ assert node.getState() == concreteFieldState;
+ node.setState(concreteFieldStateOrBottom);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 952b3a2..3cd35c2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
@@ -20,6 +21,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayDeque;
+import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
@@ -57,8 +59,9 @@
// perform this analysis after having computed the initial fixpoint(s). The hypothesis is that
// many fields will have reached the unknown state after the initial fixpoint, meaning there is
// fewer fields to analyze.
+ updateFieldStates(fieldStates, flowGraphs);
Map<FlowGraph, Deque<FlowGraphNode>> worklists =
- includeDefaultValuesInFieldStates(flowGraphs, executorService);
+ includeDefaultValuesInFieldStates(fieldStates, flowGraphs, executorService);
// Since the inclusion of default values changes the flow graphs, we need to repeat the
// fixpoint.
@@ -68,6 +71,9 @@
// of these method states have effectively become unknown, we replace them by the canonicalized
// unknown method state.
postProcessMethodStates(executorService);
+
+ // Copy the result of the flow graph propagation back to the field state collection.
+ updateFieldStates(fieldStates, flowGraphs);
}
private List<FlowGraph> computeStronglyConnectedFlowGraphs() {
@@ -83,8 +89,9 @@
}
private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
- List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException {
- DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, flowGraphs);
+ FieldStateCollection fieldStates, List<FlowGraph> flowGraphs, ExecutorService executorService)
+ throws ExecutionException {
+ DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, fieldStates, flowGraphs);
return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
}
@@ -199,4 +206,20 @@
methodStates.set(method, MethodState.unknown());
}
}
+
+ private void updateFieldStates(
+ FieldStateCollection fieldStates, Collection<FlowGraph> flowGraphs) {
+ for (FlowGraph flowGraph : flowGraphs) {
+ flowGraph.forEachFieldNode(
+ node -> {
+ ProgramField field = node.getField();
+ ValueState state = node.getState();
+ ValueState previousState = fieldStates.set(field, state);
+ assert state.isUnknown()
+ || state == previousState
+ || (state.isConcrete() && previousState.isBottom())
+ : "Expected current state to be >= previous state";
+ });
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 6b63d9c..1aca973 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -93,7 +93,7 @@
inFlowPropagator.run(executorService);
ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
- new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null);
+ new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null, null);
Set<ComposableCallGraphNode> optimizedComposableFunctions = Sets.newIdentityHashSet();
wave.forEach(
node ->
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
index 2952b30..c778df3 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -88,6 +88,10 @@
return remove(field.getReference());
}
+ public boolean remove(ProgramField field) {
+ return remove(field.getReference());
+ }
+
public boolean removeIf(Predicate<? super ProgramField> predicate) {
return backing.values().removeIf(predicate);
}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
new file mode 100644
index 0000000..d484c87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
@@ -0,0 +1,75 @@
+// 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;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BottomFieldStatePropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableConstantArgumentAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Builder builder = alwaysFalse() ? new Builder(args.length) : null;
+ test(builder);
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ static void test(Builder builder) {
+ // Argument propagation should prove that the field access is unreachable due to the field
+ // value being bottom.
+ unreachable(builder.f);
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ static void unreachable(int i) {
+ System.out.println(i);
+ }
+ }
+
+ static class Builder {
+
+ int f;
+
+ Builder(int f) {
+ this.f = f;
+ }
+ }
+}