blob: f031b5c69c8f6e010c238fe50cf5e050b2a900f3 [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.conversion.passes;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.LazyBox;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* This optimization exploits that we can sometimes learn the constant value of an SSA value that
* flows into an if-eq of if-neq instruction.
*
* <p>Consider the following example:
*
* <pre>
* 1. if (obj != null) {
* 2. return doStuff();
* 3. }
* 4. return null;
* </pre>
*
* <p>Since we know that `obj` is null in all blocks that are dominated by the false-target of the
* if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and thereby
* save a const-number instruction.
*/
public class RedundantConstNumberRemover extends CodeRewriterPass<AppInfo> {
public RedundantConstNumberRemover(AppView<?> appView) {
super(appView);
}
@Override
protected String getRewriterId() {
return "RedundantConstNumberRemover";
}
@Override
protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
&& !appView.options().testing.forceRedundantConstNumberRemoval) {
// See also b/124152497.
return false;
}
return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber();
}
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
new LazyBox<>(() -> getConstantsByValue(code));
LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
boolean changed = false;
for (BasicBlock block : code.blocks) {
Instruction lastInstruction = block.getInstructions().getLast();
if (!lastInstruction.isIf()) {
continue;
}
If ifInstruction = lastInstruction.asIf();
IfType type = ifInstruction.getType();
Value lhs = ifInstruction.inValues().get(0);
Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null;
if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) {
// We can only conclude anything from an if-instruction if it is a zero-test or if one of
// the two operands is a constant.
continue;
}
// If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values
// of the if-instruction from the outcome of the if-instruction.
if (type != IfType.EQ && type != IfType.NE) {
continue;
}
BasicBlock trueTarget, falseTarget;
if (type == IfType.EQ) {
trueTarget = ifInstruction.getTrueTarget();
falseTarget = ifInstruction.fallthroughBlock();
} else {
falseTarget = ifInstruction.getTrueTarget();
trueTarget = ifInstruction.fallthroughBlock();
}
if (ifInstruction.isZeroTest()) {
changed |=
replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree);
if (lhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
1, lhs, falseTarget, constantsByValue, code, dominatorTree);
}
} else {
assert rhs != null;
if (lhs.isConstNumber()) {
ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
changed |=
replaceDominatedConstNumbers(
lhsAsNumber.getRawValue(),
rhs,
trueTarget,
constantsByValue,
code,
dominatorTree);
if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
negateBoolean(lhsAsNumber),
rhs,
falseTarget,
constantsByValue,
code,
dominatorTree);
}
} else {
assert rhs.isConstNumber();
ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
changed |=
replaceDominatedConstNumbers(
rhsAsNumber.getRawValue(),
lhs,
trueTarget,
constantsByValue,
code,
dominatorTree);
if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
negateBoolean(rhsAsNumber),
lhs,
falseTarget,
constantsByValue,
code,
dominatorTree);
}
}
}
if (constantsByValue.computeIfAbsent().isEmpty()) {
break;
}
}
if (changed) {
code.removeAllDeadAndTrivialPhis();
}
return CodeRewriterResult.hasChanged(changed);
}
private static Long2ReferenceMap<List<ConstNumber>> getConstantsByValue(IRCode code) {
// A map from the raw value of constants in `code` to the const number instructions that define
// the given raw value (irrespective of the type of the raw value).
Long2ReferenceMap<List<ConstNumber>> constantsByValue = new Long2ReferenceOpenHashMap<>();
// Initialize `constantsByValue`.
for (Instruction instruction : code.instructions()) {
if (instruction.isConstNumber()) {
ConstNumber constNumber = instruction.asConstNumber();
if (constNumber.outValue().hasLocalInfo()) {
// Not necessarily constant, because it could be changed in the debugger.
continue;
}
long rawValue = constNumber.getRawValue();
if (constantsByValue.containsKey(rawValue)) {
constantsByValue.get(rawValue).add(constNumber);
} else {
List<ConstNumber> list = new ArrayList<>();
list.add(constNumber);
constantsByValue.put(rawValue, list);
}
}
}
return constantsByValue;
}
private static int negateBoolean(ConstNumber number) {
assert number.outValue().knownToBeBoolean();
return number.getRawValue() == 0 ? 1 : 0;
}
private boolean replaceDominatedConstNumbers(
long withValue,
Value newValue,
BasicBlock dominator,
LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
IRCode code,
LazyBox<DominatorTree> dominatorTree) {
if (newValue.hasLocalInfo()) {
// We cannot replace a constant with a value that has local info, because that could change
// debugging behavior.
return false;
}
Long2ReferenceMap<List<ConstNumber>> constantsByValue =
constantsByValueSupplier.computeIfAbsent();
List<ConstNumber> constantsWithValue = constantsByValue.get(withValue);
if (constantsWithValue == null || constantsWithValue.isEmpty()) {
return false;
}
boolean changed = false;
ListIterator<ConstNumber> constantWithValueIterator = constantsWithValue.listIterator();
while (constantWithValueIterator.hasNext()) {
ConstNumber constNumber = constantWithValueIterator.next();
Value value = constNumber.outValue();
assert !value.hasLocalInfo();
assert constNumber.getRawValue() == withValue;
BasicBlock block = constNumber.getBlock();
// If the following condition does not hold, then the if-instruction does not dominate the
// block containing the constant, although the true or false target does.
if (block == dominator && block.getPredecessors().size() != 1) {
// This should generally not happen, but it is possible to write bytecode where it does.
assert false;
continue;
}
if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) {
// We cannot replace a boolean by a none-boolean since that can lead to verification
// errors. For example, the following code fails with "register v1 has type Imprecise
// Constant: 127 but expected Boolean return-1nr".
//
// public boolean convertIntToBoolean(int v1) {
// const/4 v0, 0x1
// if-eq v1, v0, :eq_true
// const/4 v1, 0x0
// :eq_true
// return v1
// }
continue;
}
if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) {
if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
value.replaceUsers(newValue);
block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
constantWithValueIterator.remove();
changed = true;
} else if (value.getType().isNullType()) {
// TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of
// the use sites of `value` without introducing a type error.
}
}
}
if (constantsWithValue.isEmpty()) {
constantsByValue.remove(withValue);
}
return changed;
}
}