Refinements to value-is-dead analysis
Bug: b/267990059
Fixes: b/268335684
Change-Id: I8007b13e2a4e0c32dffc181a827867ff4ab0373e
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java b/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
index 2bcbae6..73d0a99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
@@ -8,12 +8,14 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
@@ -60,11 +62,17 @@
// (as it is necessarily a leaf), and repeatedly mark direct and indirect predecessors of `u`
// that have now become leaves as being dead in the analysis cache.
WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
- Value notDeadWitness = findNotDeadWitness(worklist);
+ BooleanBox foundCycle = new BooleanBox();
+ Value notDeadWitness = findNotDeadWitness(worklist, foundCycle);
boolean isDead = Objects.isNull(notDeadWitness);
if (isDead) {
- for (Value deadValue : worklist.getSeenSet()) {
- recordValueIsDead(deadValue);
+ if (foundCycle.isTrue()) {
+ for (Value deadValue : worklist.getSeenSet()) {
+ recordValueIsDead(deadValue);
+ }
+ } else {
+ assert worklist.getSeenSet().stream()
+ .allMatch(deadValue -> analysisCache.get(deadValue) == ValueIsDeadResult.DEAD);
}
}
return isDead;
@@ -74,7 +82,7 @@
return Iterables.any(block.getPhis(), this::isDead);
}
- private Value findNotDeadWitness(WorkList<Value> worklist) {
+ private Value findNotDeadWitness(WorkList<Value> worklist, BooleanBox foundCycle) {
DependenceGraph dependenceGraph = new DependenceGraph();
while (worklist.hasNext()) {
Value value = worklist.next();
@@ -118,11 +126,17 @@
}
}
- // No need to record that the deadness of a values relies on its own removal, nor known to be
- // dead values.
- valuesRequiredToBeDead.remove(value);
- valuesRequiredToBeDead.removeIf(
- valueRequiredToBeDead -> analysisCache.get(value) == ValueIsDeadResult.DEAD);
+ Iterator<Value> valuesRequiredToBeDeadIterator = valuesRequiredToBeDead.iterator();
+ while (valuesRequiredToBeDeadIterator.hasNext()) {
+ Value valueRequiredToBeDead = valuesRequiredToBeDeadIterator.next();
+ if (hasProvenThatValueIsNotDead(valueRequiredToBeDead)) {
+ recordValueAndDependentsAreNotDead(value, dependenceGraph);
+ return value;
+ }
+ if (!needsToProveThatValueIsDead(value, valueRequiredToBeDead)) {
+ valuesRequiredToBeDeadIterator.remove();
+ }
+ }
if (valuesRequiredToBeDead.isEmpty()) {
// We have now proven that this value is dead.
@@ -131,6 +145,7 @@
// Record the current value as a dependent of each value required to be dead.
for (Value valueRequiredToBeDead : valuesRequiredToBeDead) {
dependenceGraph.addDependenceEdge(value, valueRequiredToBeDead);
+ foundCycle.or(worklist.isSeen(valueRequiredToBeDead));
}
// Continue the analysis of the dependents.
@@ -140,6 +155,16 @@
return null;
}
+ private boolean hasProvenThatValueIsNotDead(Value valueRequiredToBeDead) {
+ return analysisCache.get(valueRequiredToBeDead) == ValueIsDeadResult.NOT_DEAD;
+ }
+
+ private boolean needsToProveThatValueIsDead(Value value, Value valueRequiredToBeDead) {
+ // No need to record that the deadness of a values relies on its own removal.
+ assert !hasProvenThatValueIsNotDead(valueRequiredToBeDead);
+ return valueRequiredToBeDead != value && !analysisCache.containsKey(valueRequiredToBeDead);
+ }
+
private void recordValueIsDeadAndPropagateToDependents(
Value value, DependenceGraph dependenceGraph) {
WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
@@ -161,7 +186,7 @@
private void recordValueIsDead(Value value) {
ValueIsDeadResult existingResult = analysisCache.put(value, ValueIsDeadResult.DEAD);
- assert existingResult == null || existingResult == ValueIsDeadResult.DEAD;
+ assert existingResult == null || existingResult.isDead();
}
private void recordValueAndDependentsAreNotDead(Value value, DependenceGraph dependenceGraph) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index dce99b1..b7d7b10 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -53,9 +53,9 @@
// We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction
// is the only throwing instruction in a block. Removing unneeded catch handlers can lead to
// more dead instructions.
- ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
Deque<BasicBlock> worklist = new ArrayDeque<>();
do {
+ ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
worklist.addAll(code.topologicallySortedBlocks());
while (!worklist.isEmpty()) {
BasicBlock block = worklist.removeLast();