blob: 69c4e2060b2f247e8e8412405d968423a2fe86e8 [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.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.DominatorTree;
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.Value;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import java.util.List;
public class CommonSubexpressionElimination extends CodeRewriterPass<AppInfo> {
public CommonSubexpressionElimination(AppView<?> appView) {
super(appView);
}
@Override
protected String getTimingId() {
return "CommonSubexpressionElimination";
}
@Override
boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
return true;
}
@Override
void rewriteCode(ProgramMethod method, IRCode code) {
int noCandidate = code.reserveMarkingColor();
if (hasCSECandidate(code, noCandidate)) {
final ListMultimap<Wrapper<Instruction>, Value> instructionToValue =
ArrayListMultimap.create();
final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(options);
final DominatorTree dominatorTree = new DominatorTree(code);
for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
BasicBlock block = dominatorTree.getSortedBlocks()[i];
if (block.isMarked(noCandidate)) {
continue;
}
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (isCSEInstructionCandidate(instruction)) {
List<Value> candidates = instructionToValue.get(equivalence.wrap(instruction));
boolean eliminated = false;
if (candidates.size() > 0) {
for (Value candidate : candidates) {
if (dominatorTree.dominatedBy(block, candidate.definition.getBlock())
&& shareCatchHandlers(instruction, candidate.definition)) {
instruction.outValue().replaceUsers(candidate);
candidate.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
eliminated = true;
iterator.removeOrReplaceByDebugLocalRead();
break; // Don't try any more candidates.
}
}
}
if (!eliminated) {
instructionToValue.put(equivalence.wrap(instruction), instruction.outValue());
}
}
}
}
}
code.returnMarkingColor(noCandidate);
assert code.isConsistentSSA(appView);
}
private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
private final InternalOptions options;
private CSEExpressionEquivalence(InternalOptions options) {
this.options = options;
}
@Override
protected boolean doEquivalent(Instruction a, Instruction b) {
// Some Dalvik VMs incorrectly handle Cmp instructions which leads to a requirement
// that we do not perform common subexpression elimination for them. See comment on
// canHaveCmpLongBug for details.
if (a.isCmp() && options.canHaveCmpLongBug()) {
return false;
}
// Note that we don't consider positions because CSE can at most remove an instruction.
if (!a.identicalNonValueNonPositionParts(b)) {
return false;
}
// For commutative binary operations any order of in-values are equal.
if (a.isBinop() && a.asBinop().isCommutative()) {
Value a0 = a.inValues().get(0);
Value a1 = a.inValues().get(1);
Value b0 = b.inValues().get(0);
Value b1 = b.inValues().get(1);
return (identicalValue(a0, b0) && identicalValue(a1, b1))
|| (identicalValue(a0, b1) && identicalValue(a1, b0));
} else {
// Compare all in-values.
assert a.inValues().size() == b.inValues().size();
for (int i = 0; i < a.inValues().size(); i++) {
if (!identicalValue(a.inValues().get(i), b.inValues().get(i))) {
return false;
}
}
return true;
}
}
@Override
protected int doHash(Instruction instruction) {
final int prime = 29;
int hash = instruction.getClass().hashCode();
if (instruction.isBinop()) {
Binop binop = instruction.asBinop();
Value in0 = instruction.inValues().get(0);
Value in1 = instruction.inValues().get(1);
if (binop.isCommutative()) {
hash += hash * prime + getHashCode(in0) * getHashCode(in1);
} else {
hash += hash * prime + getHashCode(in0);
hash += hash * prime + getHashCode(in1);
}
return hash;
} else {
for (Value value : instruction.inValues()) {
hash += hash * prime + getHashCode(value);
}
}
return hash;
}
private static boolean identicalValue(Value a, Value b) {
if (a.equals(b)) {
return true;
}
if (a.isConstNumber() && b.isConstNumber()) {
// Do not take assumption that constants are canonicalized.
return a.definition.identicalNonValueNonPositionParts(b.definition);
}
return false;
}
private static int getHashCode(Value a) {
if (a.isConstNumber()) {
// Do not take assumption that constants are canonicalized.
return Long.hashCode(a.definition.asConstNumber().getRawValue());
}
return a.hashCode();
}
}
private boolean shareCatchHandlers(Instruction i0, Instruction i1) {
if (!i0.instructionTypeCanThrow()) {
assert !i1.instructionTypeCanThrow();
return true;
}
assert i1.instructionTypeCanThrow();
// TODO(sgjesse): This could be even better by checking for the exceptions thrown, e.g. div
// and rem only ever throw ArithmeticException.
CatchHandlers<BasicBlock> ch0 = i0.getBlock().getCatchHandlers();
CatchHandlers<BasicBlock> ch1 = i1.getBlock().getCatchHandlers();
return ch0.equals(ch1);
}
private boolean isCSEInstructionCandidate(Instruction instruction) {
return (instruction.isBinop()
|| instruction.isUnop()
|| instruction.isInstanceOf()
|| instruction.isCheckCast())
&& instruction.getLocalInfo() == null
&& !instruction.hasInValueWithLocalInfo();
}
private boolean hasCSECandidate(IRCode code, int noCandidate) {
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
if (isCSEInstructionCandidate(instruction)) {
return true;
}
}
block.mark(noCandidate);
}
return false;
}
}