blob: 919e8d78397ffe51710ba80cd2e13a593af62aaf [file] [log] [blame]
// Copyright (c) 2022, 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.analysis.framework.intraprocedural.cf;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.ControlFlowGraph;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock.MutableCfBlock;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.TraversalUtils;
import com.android.tools.r8.utils.TriFunction;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
/**
* The following provides a control flow graph for a piece of {@link CfCode}.
*
* <p>In the {@link CfControlFlowGraph}, each instruction that is the target of a jump (including
* fallthrough targets following jumps) starts a new basic block. The first instruction in {@link
* CfCode} also starts a new block.
*
* <p>Each block is identified by the first instruction of the block.
*/
public class CfControlFlowGraph implements ControlFlowGraph<CfBlock, CfInstruction> {
// Mapping from block entry instructions to cf blocks.
private final Map<CfInstruction, ? extends CfBlock> blocks;
private final CfCode code;
private CfControlFlowGraph(Map<CfInstruction, ? extends CfBlock> blocks, CfCode code) {
this.blocks = blocks;
this.code = code;
}
private static Builder builder(CfCode code) {
return new Builder(code);
}
public static CfControlFlowGraph create(CfCode code, InternalOptions options) {
return builder(code).build(options);
}
private CfBlock getBlock(CfInstruction blockEntry) {
assert blocks.containsKey(blockEntry);
return blocks.get(blockEntry);
}
@Override
public Collection<? extends CfBlock> getBlocks() {
return blocks.values();
}
@Override
public CfBlock getEntryBlock() {
return getBlock(code.getInstruction(0));
}
@Override
public <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
CfBlock block,
BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
return TraversalUtils.traverseIterable(block.getPredecessors(), fn, initialValue);
}
@Override
public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
CfBlock block,
BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
return TraversalUtils.traverseIterable(block.getExceptionalPredecessors(), fn, initialValue);
}
@Override
public <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
CfBlock block,
BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
CfInstruction blockExit = block.getLastInstruction(code);
CfInstruction fallthroughInstruction = block.getFallthroughInstruction(code);
return blockExit.traverseNormalTargets(
(target, value) -> fn.apply(getBlock(target), value), fallthroughInstruction, initialValue);
}
@Override
public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
CfBlock block,
TriFunction<? super CfBlock, DexType, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
return TraversalUtils.traverseMap(
block.getExceptionalSuccessors(),
(guard, exceptionalSuccessor, value) -> fn.apply(exceptionalSuccessor, guard, value),
initialValue);
}
@Override
public <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
CfBlock block,
BiFunction<CfInstruction, CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
TraversalContinuation<BT, CT> traversalContinuation =
TraversalContinuation.doContinue(initialValue);
for (int instructionIndex = block.getFirstInstructionIndex();
instructionIndex <= block.getLastInstructionIndex();
instructionIndex++) {
CfInstruction instruction = code.getInstruction(instructionIndex);
traversalContinuation = fn.apply(instruction, traversalContinuation.asContinue().getValue());
if (traversalContinuation.shouldBreak()) {
break;
}
}
return traversalContinuation;
}
private static class Builder {
// Mapping from block entry instructions to the block that starts at each such instruction.
private final Map<CfInstruction, MutableCfBlock> blocks = new IdentityHashMap<>();
private final CfCode code;
Builder(CfCode code) {
this.code = code;
}
CfControlFlowGraph build(InternalOptions options) {
// Perform an initial pass over the CfCode to identify all instructions that start a new
// block.
createBlocks();
// Perform a second pass over the CfCode to finalize the identified blocks. This includes
// setting the instruction index of the last instruction of each block, which relies on having
// identified all block entries up front.
processBlocks();
removeBlockForTrailingLabel();
CfControlFlowGraph cfg = new CfControlFlowGraph(blocks, code);
assert blocks.values().stream().allMatch(block -> block.validate(cfg, options));
return cfg;
}
private void createBlocks() {
List<CfInstruction> instructions = code.getInstructions();
// The first instruction starts the first block.
createBlockIfAbsent(instructions.get(0));
// Create a block for each instruction that is targeted by a jump or fallthrough.
for (int instructionIndex = 0; instructionIndex < instructions.size(); instructionIndex++) {
CfInstruction instruction = instructions.get(instructionIndex);
if (instruction.isJump()) {
int fallthroughInstructionIndex = instructionIndex + 1;
CfInstruction fallthroughInstruction =
fallthroughInstructionIndex < instructions.size()
? instructions.get(fallthroughInstructionIndex)
: null;
instruction.forEachNormalTarget(this::createBlockIfAbsent, fallthroughInstruction);
// Also create a block for the fallthrough instruction, though it may be unreachable.
if (!instruction.asJump().hasFallthrough() && fallthroughInstruction != null) {
createBlockIfAbsent(fallthroughInstruction);
}
}
}
for (CfTryCatch tryCatch : code.getTryCatchRanges()) {
// Create a new block at the beginning and end of each try range. This is needed to ensure
// that each block has the same set of catch handlers.
createBlockIfAbsent(tryCatch.getStart());
createBlockIfAbsent(tryCatch.getEnd());
// Create a new block for the beginning of each catch handler.
tryCatch.forEachTarget(this::createBlockIfAbsent);
}
}
private void processBlocks() {
// A collection of active catch handlers. The most recently added catch handlers take
// precedence over catch handlers added before them.
Deque<CfTryCatch> activeCatchHandlers = new ArrayDeque<>();
// A collection of inactive catch handlers. The catch handlers are stored in a map where the
// key is the label at which the catch handlers start.
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers = new IdentityHashMap<>();
// Initialize all catch handlers to be inactive.
for (CfTryCatch tryCatch : code.getTryCatchRanges()) {
inactiveUntilCatchHandlers
.computeIfAbsent(tryCatch.getStart(), ignoreKey(ArrayList::new))
.add(tryCatch);
}
// Process each instruction.
List<CfInstruction> instructions = code.getInstructions();
for (int instructionIndex = 0; instructionIndex < instructions.size(); instructionIndex++) {
CfInstruction instruction = instructions.get(instructionIndex);
MutableCfBlock block = getBlockOrNull(instruction);
if (block != null) {
instructionIndex =
processBlock(
instruction,
instructionIndex,
block,
activeCatchHandlers,
inactiveUntilCatchHandlers);
}
}
assert activeCatchHandlers.isEmpty();
assert inactiveUntilCatchHandlers.isEmpty();
}
private int processBlock(
CfInstruction instruction,
int instructionIndex,
MutableCfBlock block,
Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Record the index of the first instruction of the block.
block.setFirstInstructionIndex(instructionIndex);
if (instruction.isLabel()) {
updateCatchHandlers(instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
}
// Visit each instruction belonging to the current block.
Map<DexType, CfLabel> exceptionalSuccessors = new LinkedHashMap<>();
Iterator<CfTryCatch> activeCatchHandlerIterator = activeCatchHandlers.descendingIterator();
while (activeCatchHandlerIterator.hasNext()) {
CfTryCatch activeCatchHandler = activeCatchHandlerIterator.next();
activeCatchHandler.forEach(exceptionalSuccessors::putIfAbsent);
}
do {
assert !instruction.isLabel()
|| verifyCatchHandlersUnchanged(
instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
if (instruction.instructionTypeCanThrow() && !block.hasThrowingInstruction()) {
block.setFirstThrowingInstructionIndex(instructionIndex);
}
if (isBlockExit(instructionIndex)) {
break;
}
instruction = code.getInstruction(++instructionIndex);
} while (true);
// Record the index of the last instruction of the block.
block.setLastInstructionIndex(instructionIndex);
// Add the current block as a predecessor of the successor blocks.
CfInstruction fallthroughInstruction = block.getFallthroughInstruction(code);
instruction.forEachNormalTarget(
target -> getBlock(target).addPredecessor(block), fallthroughInstruction);
// Add the current block as an exceptional predecessor of the exceptional successor blocks.
exceptionalSuccessors.forEach(
(guard, exceptionalSuccessor) -> {
MutableCfBlock exceptionalSuccessorBlock = getBlock(exceptionalSuccessor);
block.addExceptionalSuccessor(exceptionalSuccessorBlock, guard);
exceptionalSuccessorBlock.addExceptionalPredecessor(block);
});
return instructionIndex;
}
private void removeBlockForTrailingLabel() {
CfInstruction lastInstruction = code.getInstruction(code.getInstructions().size() - 1);
if (lastInstruction.isLabel() && isBlockEntry(lastInstruction)) {
blocks.remove(lastInstruction);
}
}
private boolean isBlockEntry(CfInstruction instruction) {
return blocks.containsKey(instruction);
}
private boolean isBlockExit(int instructionIndex) {
int lastInstructionIndex = code.getInstructions().size() - 1;
if (instructionIndex == lastInstructionIndex) {
return true;
}
CfInstruction nextInstruction = code.getInstruction(instructionIndex + 1);
return isBlockEntry(nextInstruction);
}
private void updateCatchHandlers(
CfLabel instruction,
Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Remove active catch handlers that have expired at the current instruction.
activeCatchHandlers.removeIf(
activeCatchHandler -> activeCatchHandler.getEnd() == instruction);
// Promote inactive catch handlers that is activated at the current instruction to active.
List<CfTryCatch> newlyActiveCatchHandlers = inactiveUntilCatchHandlers.remove(instruction);
if (newlyActiveCatchHandlers != null) {
for (int i = newlyActiveCatchHandlers.size() - 1; i >= 0; i--) {
CfTryCatch newlyActiveCatchHandler = newlyActiveCatchHandlers.get(i);
assert newlyActiveCatchHandler.getEnd() != newlyActiveCatchHandler.getStart();
activeCatchHandlers.addLast(newlyActiveCatchHandler);
}
}
}
private boolean verifyCatchHandlersUnchanged(
CfLabel instruction,
Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
assert activeCatchHandlers.stream()
.allMatch(activeCatchHandler -> activeCatchHandler.getEnd() != instruction);
assert !inactiveUntilCatchHandlers.containsKey(instruction);
return true;
}
private void createBlockIfAbsent(CfInstruction blockEntry) {
blocks.computeIfAbsent(blockEntry, ignoreKey(MutableCfBlock::new));
}
private MutableCfBlock getBlock(CfInstruction blockEntry) {
assert blocks.containsKey(blockEntry);
return blocks.get(blockEntry);
}
private MutableCfBlock getBlockOrNull(CfInstruction blockEntry) {
return blocks.get(blockEntry);
}
}
}