blob: c0fb1af9d68323747f57147312fcee769c66ce46 [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.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
/**
* Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
* rewrite fallthrough targets as that would require block reordering and the transformation only
* makes sense after SSA destruction where there are no phis.
*/
public class TrivialGotosCollapser extends CodeRewriterPass<AppInfo> {
public TrivialGotosCollapser(AppView<?> appView) {
super(appView);
}
@Override
protected String getRewriterId() {
return "TrivialGotosCollapser";
}
@Override
protected boolean isAcceptingSSA() {
return false;
}
@Override
protected boolean isProducingSSA() {
return false;
}
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
List<BasicBlock> blocksToRemove = new ArrayList<>();
// Rewrite all non-fallthrough targets to the end of trivial goto chains and remove
// first round of trivial goto blocks.
ListIterator<BasicBlock> iterator = code.listIterator();
assert iterator.hasNext();
BasicBlock block = iterator.next();
BasicBlock nextBlock;
do {
nextBlock = iterator.hasNext() ? iterator.next() : null;
if (block.isTrivialGoto()) {
collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
}
if (block.exit().isIf()) {
collapseIfTrueTarget(block);
}
if (block.exit().isSwitch()) {
collapseNonFallthroughSwitchTargets(block);
}
block = nextBlock;
} while (nextBlock != null);
code.removeBlocks(blocksToRemove);
// Get rid of gotos to the next block.
while (!blocksToRemove.isEmpty()) {
blocksToRemove = new ArrayList<>();
iterator = code.listIterator();
block = iterator.next();
do {
nextBlock = iterator.hasNext() ? iterator.next() : null;
if (block.isTrivialGoto()) {
collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
}
block = nextBlock;
} while (block != null);
code.removeBlocks(blocksToRemove);
}
assert removedTrivialGotos(code);
assert code.isConsistentGraph(appView);
return CodeRewriterResult.NONE;
}
@Override
protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
public static void unlinkTrivialGotoBlock(BasicBlock block, BasicBlock target) {
assert block.isTrivialGoto();
for (BasicBlock pred : block.getPredecessors()) {
pred.replaceSuccessor(block, target);
}
for (BasicBlock succ : block.getSuccessors()) {
succ.getMutablePredecessors().remove(block);
}
for (BasicBlock pred : block.getPredecessors()) {
if (!target.getPredecessors().contains(pred)) {
target.getMutablePredecessors().add(pred);
}
}
}
private boolean isFallthroughBlock(BasicBlock block) {
for (BasicBlock pred : block.getPredecessors()) {
if (pred.exit().fallthroughBlock() == block) {
return true;
}
}
return false;
}
private boolean removedTrivialGotos(IRCode code) {
ListIterator<BasicBlock> iterator = code.listIterator();
assert iterator.hasNext();
BasicBlock block = iterator.next();
BasicBlock nextBlock;
do {
nextBlock = iterator.hasNext() ? iterator.next() : null;
// Trivial goto block are only kept if they are self-targeting or are targeted by
// fallthroughs.
BasicBlock blk = block; // Additional local for lambda below.
assert !block.isTrivialGoto()
|| block.exit().asGoto().getTarget() == block
|| code.entryBlock() == block
|| block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk);
// Trivial goto blocks never target the next block (in that case there should just be a
// fallthrough).
assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock;
block = nextBlock;
} while (block != null);
return true;
}
private void collapseTrivialGoto(
IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
// This is the base case for GOTO loops.
if (block.exit().asGoto().getTarget() == block) {
return;
}
BasicBlock target = block.endOfGotoChain();
boolean needed = false;
if (target == null) {
// This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
// trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
target = block.exit().asGoto().getTarget();
}
if (target != nextBlock) {
// Not targeting the fallthrough block, determine if we need this goto. We need it if
// a fallthrough can hit this block. That is the case if the block is the entry block
// or if one of the predecessors fall through to the block.
needed = code.entryBlock() == block || isFallthroughBlock(block);
}
if (!needed) {
blocksToRemove.add(block);
unlinkTrivialGotoBlock(block, target);
}
}
private void collapseIfTrueTarget(BasicBlock block) {
If insn = block.exit().asIf();
BasicBlock target = insn.getTrueTarget();
BasicBlock newTarget = target.endOfGotoChain();
BasicBlock fallthrough = insn.fallthroughBlock();
BasicBlock newFallthrough = fallthrough.endOfGotoChain();
if (newTarget != null && target != newTarget) {
insn.getBlock().replaceSuccessor(target, newTarget);
target.getMutablePredecessors().remove(block);
if (!newTarget.getPredecessors().contains(block)) {
newTarget.getMutablePredecessors().add(block);
}
}
if (block.exit().isIf()) {
insn = block.exit().asIf();
if (insn.getTrueTarget() == newFallthrough) {
// Replace if with the same true and fallthrough target with a goto to the fallthrough.
block.replaceSuccessor(insn.getTrueTarget(), fallthrough);
assert block.exit().isGoto();
assert block.exit().asGoto().getTarget() == fallthrough;
}
}
}
private void collapseNonFallthroughSwitchTargets(BasicBlock block) {
Switch insn = block.exit().asSwitch();
BasicBlock fallthroughBlock = insn.fallthroughBlock();
Set<BasicBlock> replacedBlocks = new HashSet<>();
for (int j = 0; j < insn.targetBlockIndices().length; j++) {
BasicBlock target = insn.targetBlock(j);
if (target != fallthroughBlock) {
BasicBlock newTarget = target.endOfGotoChain();
if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) {
insn.getBlock().replaceSuccessor(target, newTarget);
target.getMutablePredecessors().remove(block);
if (!newTarget.getPredecessors().contains(block)) {
newTarget.getMutablePredecessors().add(block);
}
replacedBlocks.add(target);
}
}
}
}
}