Refactor value-is-dead analysis to separate analysis class
Bug: b/267990059
Change-Id: I8b881203ebe21395f48c8eeb6434faa03f7f9d46
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 0a79887..2daa545 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -662,15 +662,6 @@
return !phis.isEmpty();
}
- public boolean hasDeadPhi(AppView<?> appView, IRCode code) {
- for (Phi phi : phis) {
- if (phi.isDead(appView, code)) {
- return true;
- }
- }
- return false;
- }
-
public List<Phi> getPhis() {
return phis;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 54710df..b0eb2bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -464,6 +463,10 @@
return !users.isEmpty() || !phiUsers.isEmpty() || numberOfDebugUsers() > 0;
}
+ public boolean isUnused() {
+ return !isUsed();
+ }
+
public boolean isAlwaysNull(AppView<?> appView) {
if (hasLocalInfo()) {
// Not always null as the value can be changed via the debugger.
@@ -972,73 +975,6 @@
}
}
- public boolean isDead(AppView<?> appView, IRCode code) {
- // Totally unused values are trivially dead.
- return !isUsed() || isDead(appView, code, Predicates.alwaysFalse());
- }
-
- public boolean isDead(AppView<?> appView, IRCode code, Predicate<Instruction> ignoreUser) {
- // Totally unused values are trivially dead.
- return !isUsed() || isDead(appView, code, ignoreUser, Sets.newIdentityHashSet());
- }
-
- /**
- * Used to determine if a given value is dead.
- *
- * <p>The predicate `ignoreUser` can be used to determine if a given value is dead under the
- * assumption that the instructions for which `ignoreUser` returns true are also dead.
- *
- * <p>One use case of this is when we attempt to determine if a call to {@code <init>()} can be
- * removed: calls to {@code <init>()} can only be removed if the receiver is dead except for the
- * constructor call.
- */
- protected boolean isDead(
- AppView<?> appView, IRCode code, Predicate<Instruction> ignoreUser, Set<Value> active) {
- // Give up when the dependent set of values reach a given threshold (otherwise this fails with
- // a StackOverflowError on Art003_omnibus_opcodesTest).
- if (active.size() > 100) {
- return false;
- }
-
- // If the value has debug users we cannot eliminate it since it represents a value in a local
- // variable that should be visible in the debugger.
- if (hasDebugUsers()) {
- return false;
- }
- // This is a candidate for a dead value. Guard against looping by adding it to the set of
- // currently active values.
- active.add(this);
- for (Instruction instruction : uniqueUsers()) {
- if (ignoreUser.test(instruction)) {
- continue;
- }
- DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
- if (result.isNotDead()) {
- return false;
- }
- if (result.isMaybeDead()) {
- for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) {
- if (!active.contains(valueRequiredToBeDead)
- && !valueRequiredToBeDead.isDead(appView, code, ignoreUser, active)) {
- return false;
- }
- }
- }
- Value outValue = instruction.outValue();
- if (outValue != null
- && !active.contains(outValue)
- && !outValue.isDead(appView, code, ignoreUser, active)) {
- return false;
- }
- }
- for (Phi phi : uniquePhiUsers()) {
- if (!active.contains(phi) && !phi.isDead(appView, code, ignoreUser, active)) {
- return false;
- }
- }
- return true;
- }
-
public boolean isZero() {
return isConstant()
&& getConstInstruction().isConstNumber()
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
new file mode 100644
index 0000000..f91a569
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2023, 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.ir.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ValueIsDeadAnalysis {
+
+ private final AppView<?> appView;
+ private final IRCode code;
+
+ public ValueIsDeadAnalysis(AppView<?> appView, IRCode code) {
+ this.appView = appView;
+ this.code = code;
+ }
+
+ public boolean isDead(Value value) {
+ // Totally unused values are trivially dead.
+ if (value.isUnused()) {
+ return true;
+ }
+ return isDead(value, Sets.newIdentityHashSet());
+ }
+
+ public boolean hasDeadPhi(BasicBlock block) {
+ return Iterables.any(block.getPhis(), this::isDead);
+ }
+
+ private boolean isDead(Value value, Set<Value> active) {
+ // Give up when the dependent set of values reach a given threshold (otherwise this fails with
+ // a StackOverflowError on Art003_omnibus_opcodesTest).
+ if (active.size() > 100) {
+ return false;
+ }
+
+ // If the value has debug users we cannot eliminate it since it represents a value in a local
+ // variable that should be visible in the debugger.
+ if (value.hasDebugUsers()) {
+ return false;
+ }
+ // This is a candidate for a dead value. Guard against looping by adding it to the set of
+ // currently active values.
+ active.add(value);
+ for (Instruction instruction : value.uniqueUsers()) {
+ DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
+ if (result.isNotDead()) {
+ return false;
+ }
+ if (result.isMaybeDead()) {
+ for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) {
+ if (!active.contains(valueRequiredToBeDead) && !isDead(valueRequiredToBeDead, active)) {
+ return false;
+ }
+ }
+ }
+ Value outValue = instruction.outValue();
+ if (outValue != null && !active.contains(outValue) && !isDead(outValue, active)) {
+ return false;
+ }
+ }
+ for (Phi phi : value.uniquePhiUsers()) {
+ if (!active.contains(phi) && !isDead(phi, active)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
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 92e653d..16cd748 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
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueIsDeadAnalysis;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.Timing;
@@ -52,13 +53,14 @@
// 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 {
worklist.addAll(code.topologicallySortedBlocks());
while (!worklist.isEmpty()) {
BasicBlock block = worklist.removeLast();
- removeDeadInstructions(worklist, code, block);
- removeDeadPhis(worklist, code, block);
+ removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis);
+ removeDeadPhis(worklist, block, valueIsDeadAnalysis);
}
} while (codeRewriter.simplifyIf(code).anySimplifications()
|| removeUnneededCatchHandlers(code));
@@ -70,8 +72,9 @@
public boolean verifyNoDeadCode(IRCode code) {
assert !codeRewriter.rewriteMoveResult(code);
assert !removeUnneededCatchHandlers(code);
+ ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
for (BasicBlock block : code.blocks) {
- assert !block.hasDeadPhi(appView, code);
+ assert !valueIsDeadAnalysis.hasDeadPhi(block);
for (Instruction instruction : block.getInstructions()) {
// No unused move-result instructions.
assert !instruction.isInvoke()
@@ -79,7 +82,7 @@
|| instruction.outValue().hasAnyUsers();
// No dead instructions.
assert !instruction.canBeDeadCode(appView, code).isDeadIfOutValueIsDead()
- || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
+ || (instruction.hasOutValue() && !valueIsDeadAnalysis.isDead(instruction.outValue()));
}
}
return true;
@@ -108,11 +111,12 @@
}
}
- private void removeDeadPhis(Queue<BasicBlock> worklist, IRCode code, BasicBlock block) {
+ private void removeDeadPhis(
+ Queue<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) {
Iterator<Phi> phiIt = block.getPhis().iterator();
while (phiIt.hasNext()) {
Phi phi = phiIt.next();
- if (phi.isDead(appView, code)) {
+ if (valueIsDeadAnalysis.isDead(phi)) {
phiIt.remove();
for (Value operand : phi.getOperands()) {
operand.removePhiUser(phi);
@@ -122,7 +126,11 @@
}
}
- private void removeDeadInstructions(Queue<BasicBlock> worklist, IRCode code, BasicBlock block) {
+ private void removeDeadInstructions(
+ Queue<BasicBlock> worklist,
+ IRCode code,
+ BasicBlock block,
+ ValueIsDeadAnalysis valueIsDeadAnalysis) {
InstructionListIterator iterator = block.listIterator(code, block.getInstructions().size());
while (iterator.hasPrevious()) {
Instruction current = iterator.previous();
@@ -165,7 +173,7 @@
if (deadInstructionResult.isMaybeDead()) {
boolean satisfied = true;
for (Value valueRequiredToBeDead : deadInstructionResult.getValuesRequiredToBeDead()) {
- if (!valueRequiredToBeDead.isDead(appView, code)) {
+ if (!valueIsDeadAnalysis.isDead(valueRequiredToBeDead)) {
satisfied = false;
break;
}
@@ -175,7 +183,7 @@
}
}
Value outValue = current.outValue();
- if (outValue != null && !outValue.isDead(appView, code)) {
+ if (outValue != null && !valueIsDeadAnalysis.isDead(outValue)) {
continue;
}
updateWorklist(worklist, current);