blob: bf3bc8688f52a2ef5a41c6686ec85df569cbe4da [file] [log] [blame]
// 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.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.ArrayUtils;
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 replacementValue;
if (singleValueOrValue.hasValue()) {
replacementValue = 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.isSingleBoxedPrimitive()) {
materializedValueType = singleValue.asSingleBoxedPrimitive().getBoxedPrimitiveType(appView);
} else 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[] materializingInstructions =
singleValue.createMaterializingInstructions(
appView,
code,
MaterializingInstructionsInfo.create(
materializedValueType, null, code.getEntryPosition()));
entryBlockIterator.addAll(materializingInstructions);
Instruction replacement = ArrayUtils.last(materializingInstructions);
replacementValue = replacement.outValue();
// Insert assume-not-null instruction.
if (materializedValueType.isReferenceType()
&& materializedValueType.nullability().isMaybeNull()
&& phiType.nullability().isDefinitelyNotNull()) {
Assume assume =
Assume.create(
DynamicType.definitelyNotNull(),
code.createValue(phiType),
replacementValue,
replacement,
appView,
code.context());
assume.setPosition(code.getEntryPosition(), appView.options());
entryBlockIterator.add(assume);
replacementValue = 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(replacementValue, 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;
}
}
}