blob: 93ad3ff0afb62b0f1a6dd690caf46b9cabbc6aee [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.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.And;
import com.android.tools.r8.ir.code.Binop;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.Div;
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.Mul;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Or;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Shl;
import com.android.tools.r8.ir.code.Shr;
import com.android.tools.r8.ir.code.Sub;
import com.android.tools.r8.ir.code.Ushr;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
public class BinopRewriter extends CodeRewriterPass<AppInfo> {
private static final int ALL_BITS_SET = -1;
public BinopRewriter(AppView<?> appView) {
super(appView);
}
private final Map<Class<?>, BinopDescriptor> descriptors = createBinopDescriptors();
private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
builder.put(Add.class, new BinopDescriptor(0, 0, null, null));
builder.put(Sub.class, new BinopDescriptor(null, 0, null, null));
builder.put(Mul.class, new BinopDescriptor(1, 1, 0, 0));
// The following two can be improved if we handle ZeroDivide.
builder.put(Div.class, new BinopDescriptor(null, 1, null, null));
builder.put(Rem.class, new BinopDescriptor(null, null, null, null));
builder.put(And.class, new BinopDescriptor(ALL_BITS_SET, ALL_BITS_SET, 0, 0));
builder.put(Or.class, new BinopDescriptor(0, 0, ALL_BITS_SET, ALL_BITS_SET));
builder.put(Xor.class, new BinopDescriptor(0, 0, null, null));
builder.put(Shl.class, new BinopDescriptor(null, 0, 0, null));
builder.put(Shr.class, new BinopDescriptor(null, 0, 0, null));
builder.put(Ushr.class, new BinopDescriptor(null, 0, 0, null));
return builder.build();
}
/**
* A Binop descriptor describes left and right identity and absorbing element of binop. <code>
* In a space K, for a binop *:
* - i is left identity if for each x in K, i * x = x.
* - i is right identity if for each x in K, x * i = x.
* - a is left absorbing if for each x in K, a * x = a.
* - a is right absorbing if for each x in K, x * a = a.
* </code>
*/
private static class BinopDescriptor {
final Integer leftIdentity;
final Integer rightIdentity;
final Integer leftAbsorbing;
final Integer rightAbsorbing;
private BinopDescriptor(
Integer leftIdentity,
Integer rightIdentity,
Integer leftAbsorbing,
Integer rightAbsorbing) {
this.leftIdentity = leftIdentity;
this.rightIdentity = rightIdentity;
this.leftAbsorbing = leftAbsorbing;
this.rightAbsorbing = rightAbsorbing;
}
}
@Override
String getTimingId() {
return "BinopRewriter";
}
@Override
boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
return true;
}
@Override
public void rewriteCode(ProgramMethod method, IRCode code) {
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction next = iterator.next();
if (next.isBinop() && !next.isCmp()) {
Binop binop = next.asBinop();
if (binop.getNumericType() == NumericType.INT
|| binop.getNumericType() == NumericType.LONG) {
BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
assert binopDescriptor != null;
ConstNumber constNumber = getConstNumber(binop.leftValue());
if (constNumber != null) {
if (simplify(
binop,
iterator,
constNumber,
binopDescriptor.leftIdentity,
binop.rightValue(),
binopDescriptor.leftAbsorbing,
binop.leftValue())) {
continue;
}
}
constNumber = getConstNumber(binop.rightValue());
if (constNumber != null) {
simplify(
binop,
iterator,
constNumber,
binopDescriptor.rightIdentity,
binop.leftValue(),
binopDescriptor.rightAbsorbing,
binop.rightValue());
}
}
}
}
code.removeAllDeadAndTrivialPhis();
assert code.isConsistentSSA(appView);
}
private ConstNumber getConstNumber(Value val) {
ConstNumber constNumber = getConstNumberIfConstant(val);
if (constNumber != null) {
return constNumber;
}
// phi(v1(0), v2(0)) is equivalent to ConstNumber(0) for the simplification.
if (val.isPhi() && getConstNumberIfConstant(val.asPhi().getOperands().get(0)) != null) {
ConstNumber phiConstNumber = null;
WorkList<Phi> phiWorkList = WorkList.newIdentityWorkList(val.asPhi());
while (phiWorkList.hasNext()) {
Phi next = phiWorkList.next();
for (Value operand : next.getOperands()) {
ConstNumber operandConstNumber = getConstNumberIfConstant(operand);
if (operandConstNumber != null) {
if (phiConstNumber == null) {
phiConstNumber = operandConstNumber;
} else if (operandConstNumber.getRawValue() == phiConstNumber.getRawValue()) {
assert operandConstNumber.getOutType() == phiConstNumber.getOutType();
} else {
// Different const numbers, cannot conclude a value from the phi.
return null;
}
} else if (operand.isPhi()) {
phiWorkList.addIfNotSeen(operand.asPhi());
} else {
return null;
}
}
}
return phiConstNumber;
}
return null;
}
private static ConstNumber getConstNumberIfConstant(Value val) {
if (val.isConstant() && val.getConstInstruction().isConstNumber()) {
return val.getConstInstruction().asConstNumber();
}
return null;
}
private boolean simplify(
Binop binop,
InstructionListIterator iterator,
ConstNumber constNumber,
Integer identityElement,
Value identityReplacement,
Integer absorbingElement,
Value absorbingReplacement) {
int intValue;
if (constNumber.outValue().getType().isInt()) {
intValue = constNumber.getIntValue();
} else {
assert constNumber.outValue().getType().isLong();
long longValue = constNumber.getLongValue();
intValue = (int) longValue;
if ((long) intValue != longValue) {
return false;
}
}
if (identityElement != null && identityElement == intValue) {
binop.outValue().replaceUsers(identityReplacement);
iterator.remove();
return true;
}
if (absorbingElement != null && absorbingElement == intValue) {
binop.outValue().replaceUsers(absorbingReplacement);
iterator.remove();
return true;
}
return false;
}
}