Remove effectively trivial phis before ThrowCatchOptimizer
Fixes: b/296337518
Change-Id: I0d591447315f6fd83c42b40d6c35f1d1a1c0f6cd
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 55882f5..f1db962 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
@@ -852,6 +852,11 @@
: "Attempt to remove Phi " + phi + " which is present in currentDefinitions";
}
+ public void removePhis(Collection<Phi> phisToRemove) {
+ assert currentDefinitions == null || currentDefinitions.isEmpty();
+ phis.removeAll(phisToRemove);
+ }
+
public void add(Instruction next, IRCode code) {
add(next, code.metadata());
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 944d68e..f332682 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
diff --git a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
index d28407f..9b21078 100644
--- a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
@@ -12,6 +12,10 @@
TypeElement getOutType();
+ static TypeAndLocalInfoSupplier create(TypeElement type) {
+ return create(type, null);
+ }
+
static TypeAndLocalInfoSupplier create(TypeElement type, DebugLocalInfo local) {
return new TypeAndLocalInfoSupplier() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
index 9ef5a8b..aa44257 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
@@ -32,6 +32,7 @@
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.phis.EffectivelyTrivialPhiOptimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.BitSet;
@@ -211,12 +212,13 @@
// it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
private boolean optimizeAlwaysThrowingInstructions(IRCode code) {
+ boolean hasChanged =
+ new EffectivelyTrivialPhiOptimization(appView, code).removeEffectivelyTrivialPhis();
AffectedValues affectedValues = new AffectedValues();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
ProgramMethod context = code.context();
boolean hasUnlinkedCatchHandlers = false;
- boolean hasChanged = false;
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
new file mode 100644
index 0000000..5ced7df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
@@ -0,0 +1,237 @@
+// 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.optimize.phis;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+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.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class EffectivelyTrivialPhiOptimization {
+
+ private final AppView<?> appView;
+ private final IRCode code;
+
+ public EffectivelyTrivialPhiOptimization(AppView<?> appView, IRCode code) {
+ this.appView = appView;
+ this.code = code;
+ }
+
+ public boolean removeEffectivelyTrivialPhis() {
+ AffectedValues affectedValues = new AffectedValues();
+ Map<BasicBlock, Set<Phi>> phisToRemove = new IdentityHashMap<>();
+ for (BasicBlock block : code.getBlocks()) {
+ for (Phi phi : block.getPhis()) {
+ removeEffectivelyTrivialPhi(phi, affectedValues, phisToRemove);
+ }
+ }
+ if (phisToRemove.isEmpty()) {
+ return false;
+ }
+ phisToRemove.forEach(BasicBlock::removePhis);
+ affectedValues.narrowingWithAssumeRemoval(appView, code);
+ return true;
+ }
+
+ /**
+ * Removes this phi and all other phis that contribute to the value of this phi if the phi is
+ * effectively trivial, i.e., it always has the same value.
+ */
+ private void removeEffectivelyTrivialPhi(
+ Phi phi, AffectedValues affectedValues, Map<BasicBlock, Set<Phi>> phisToRemove) {
+ if (appView.options().debug) {
+ return;
+ }
+
+ SingleValueOrValue singleValueOrValue = computeEffectivelyTrivialPhiValue(phi);
+ if (singleValueOrValue == null) {
+ // Not effectively final.
+ return;
+ }
+
+ // If we didn't find a non-phi operand then all seen phis are effectively unused.
+ if (!singleValueOrValue.hasSingleValue() && !singleValueOrValue.hasValue()) {
+ for (Phi seenPhi : singleValueOrValue.getPhis()) {
+ addPhiToRemove(phisToRemove, seenPhi);
+ }
+ return;
+ }
+
+ // Otherwise all phis can be replaced by the operand value. If we found only a single non-phi
+ // operand value then this is guaranteed to dominate all phis. Otherwise we try to materialize
+ // the abstract value in a position that dominates all phis.
+ Value replacement;
+ if (singleValueOrValue.hasValue()) {
+ replacement = singleValueOrValue.getValue();
+ } else {
+ assert singleValueOrValue.hasSingleValue();
+ SingleValue singleValue = singleValueOrValue.getSingleValue();
+ assert singleValue.isMaterializableInContext(appView, code.context());
+ InstructionListIterator entryBlockIterator = code.entryBlock().listIterator(code);
+ entryBlockIterator.positionBeforeNextInstructionThatMatches(i -> !i.isArgument());
+
+ // Insert materializing instruction.
+ TypeElement phiType = phi.getType();
+ TypeElement materializedValueType = phiType;
+ if (singleValue.isSingleFieldValue()) {
+ SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
+ materializedValueType = singleFieldValue.getField().getTypeElement(appView);
+ } else if (phiType.isReferenceType() && singleValue.isNull()) {
+ materializedValueType = TypeElement.getNull();
+ } else {
+ assert phiType.isPrimitiveType() || phiType.isNullType() || phiType.isDefinitelyNotNull()
+ : singleValue + ": " + phiType;
+ }
+ Instruction materializingInstruction =
+ singleValue.createMaterializingInstruction(
+ appView, code, TypeAndLocalInfoSupplier.create(materializedValueType));
+ materializingInstruction.setPosition(code.getEntryPosition(), appView.options());
+ entryBlockIterator.add(materializingInstruction);
+ replacement = materializingInstruction.outValue();
+
+ // Insert assume-not-null instruction.
+ if (materializedValueType.isReferenceType()
+ && materializedValueType.nullability().isMaybeNull()
+ && phiType.nullability().isDefinitelyNotNull()) {
+ Assume assume =
+ Assume.create(
+ DynamicType.definitelyNotNull(),
+ code.createValue(phiType),
+ replacement,
+ materializingInstruction,
+ appView,
+ code.context());
+ assume.setPosition(materializingInstruction.getPosition(), appView.options());
+ entryBlockIterator.add(assume);
+ replacement = assume.outValue();
+ }
+ }
+
+ // Remove phis.
+ for (Phi seenPhi : singleValueOrValue.getPhis()) {
+ for (Value operand : seenPhi.getOperands()) {
+ operand.removePhiUser(seenPhi);
+ }
+ addPhiToRemove(phisToRemove, seenPhi);
+ }
+ // Replace all uses of the phis by the replacement. This is done after detaching the phis from
+ // the graph to make sure that we don't add one of the phis as a user of the replacement value.
+ for (Phi seenPhi : singleValueOrValue.getPhis()) {
+ seenPhi.replaceUsers(replacement, affectedValues);
+ }
+ }
+
+ private SingleValueOrValue computeEffectivelyTrivialPhiValue(Phi phi) {
+ Value representativeOperand = null;
+ AbstractValue representativeOperandAbstractValue = null;
+ boolean foundDifferentOperandValuesWithSameAbstractValue = false;
+ WorkList<Phi> worklist = WorkList.newIdentityWorkList(phi);
+ while (worklist.hasNext()) {
+ Phi currentPhi = worklist.next();
+ for (Value operand : currentPhi.getOperands()) {
+ if (operand.isPhi()) {
+ worklist.addIfNotSeen(operand.asPhi());
+ } else {
+ if (representativeOperand == null) {
+ assert representativeOperandAbstractValue == null;
+ representativeOperand = operand;
+ representativeOperandAbstractValue = operand.getAbstractValue(appView, code.context());
+ } else if (operand == representativeOperand) {
+ continue;
+ } else if (representativeOperandAbstractValue.isSingleValue()
+ && operand
+ .getAbstractValue(appView, code.context())
+ .equals(representativeOperandAbstractValue)) {
+ foundDifferentOperandValuesWithSameAbstractValue = true;
+ continue;
+ } else {
+ // Not effectively trivial.
+ return null;
+ }
+ }
+ }
+ }
+ if (representativeOperand == null) {
+ // If we didn't find a non-phi operand then all seen phis are effectively unused.
+ return new SingleValueOrValue(worklist.getSeenSet());
+ }
+ if (foundDifferentOperandValuesWithSameAbstractValue) {
+ if (representativeOperandAbstractValue.isSingleValue()) {
+ return new SingleValueOrValue(
+ worklist.getSeenSet(), representativeOperandAbstractValue.asSingleValue());
+ }
+ // The computed value is not a constant.
+ return null;
+ }
+ return new SingleValueOrValue(worklist.getSeenSet(), representativeOperand);
+ }
+
+ private void addPhiToRemove(Map<BasicBlock, Set<Phi>> phisToRemove, Phi phi) {
+ phisToRemove.computeIfAbsent(phi.getBlock(), ignoreKey(Sets::newIdentityHashSet)).add(phi);
+ }
+
+ private static class SingleValueOrValue {
+
+ private final SingleValue singleValue;
+ private final Value value;
+ private final Set<Phi> phis;
+
+ SingleValueOrValue(Set<Phi> phis) {
+ this(phis, null, null);
+ }
+
+ SingleValueOrValue(Set<Phi> phis, SingleValue singleValue) {
+ this(phis, singleValue, null);
+ }
+
+ SingleValueOrValue(Set<Phi> phis, Value value) {
+ this(phis, null, value);
+ }
+
+ SingleValueOrValue(Set<Phi> phis, SingleValue singleValue, Value value) {
+ this.singleValue = singleValue;
+ this.value = value;
+ this.phis = phis;
+ }
+
+ boolean hasSingleValue() {
+ return singleValue != null;
+ }
+
+ SingleValue getSingleValue() {
+ return singleValue;
+ }
+
+ Set<Phi> getPhis() {
+ return phis;
+ }
+
+ boolean hasValue() {
+ return value != null;
+ }
+
+ Value getValue() {
+ return value;
+ }
+ }
+}