blob: ff6906537a3ce1bc3a09490fd978e3c356fef845 [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.ir.analysis.framework.intraprocedural.ControlFlowGraph;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock.MutableCfBlock;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.TraversalUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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) {
return builder(code).build();
}
private CfBlock getBlock(CfInstruction blockEntry) {
assert blocks.containsKey(blockEntry);
return blocks.get(blockEntry);
}
@Override
public CfBlock getEntryBlock() {
return getBlock(code.getInstructions().get(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,
BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
return TraversalUtils.traverseIterable(block.getExceptionalSuccessors(), fn, 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.getInstructions().get(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() {
// 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();
assert blocks.values().stream().allMatch(MutableCfBlock::validate);
return new CfControlFlowGraph(blocks, code);
}
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);
}
}
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 catch handlers are stored in a map where the key
// is the label at which the catch handlers end.
Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers = new IdentityHashMap<>();
// 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,
activeUntilCatchHandlers,
inactiveUntilCatchHandlers);
}
}
assert activeUntilCatchHandlers.isEmpty();
assert inactiveUntilCatchHandlers.isEmpty();
}
private int processBlock(
CfInstruction instruction,
int instructionIndex,
MutableCfBlock block,
Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Record the index of the first instruction of the block.
block.setFirstInstructionIndex(instructionIndex);
if (instruction.isLabel()) {
updateCatchHandlers(
instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
}
// Visit each instruction belonging to the current block.
Set<CfLabel> exceptionalSuccessors = new LinkedHashSet<>();
do {
assert !instruction.isLabel()
|| verifyCatchHandlersUnchanged(
instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
if (instruction.canThrow()) {
for (CfTryCatch tryCatch : IterableUtils.flatten(activeUntilCatchHandlers.values())) {
exceptionalSuccessors.addAll(tryCatch.getTargets());
}
}
if (isBlockExit(instructionIndex)) {
break;
}
instruction = code.getInstructions().get(++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(
exceptionalSuccessor -> {
MutableCfBlock exceptionalSuccessorBlock = getBlock(exceptionalSuccessor);
block.addExceptionalSuccessor(exceptionalSuccessorBlock);
exceptionalSuccessorBlock.addExceptionalPredecessor(block);
});
return instructionIndex;
}
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.getInstructions().get(instructionIndex + 1);
return isBlockEntry(nextInstruction);
}
private void updateCatchHandlers(
CfLabel instruction,
Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Remove active catch handlers that have expired at the current instruction.
activeUntilCatchHandlers.remove(instruction);
// Promote inactive catch handlers that is activated at the current instruction to active.
for (CfTryCatch tryCatch :
inactiveUntilCatchHandlers.getOrDefault(instruction, Collections.emptyList())) {
assert tryCatch.getEnd() != tryCatch.getStart();
activeUntilCatchHandlers
.computeIfAbsent(tryCatch.getEnd(), ignoreKey(ArrayList::new))
.add(tryCatch);
}
}
private boolean verifyCatchHandlersUnchanged(
CfLabel instruction,
Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
assert !activeUntilCatchHandlers.containsKey(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);
}
}
}