blob: bf83c7f61480ad2d95964cde429b486119f3aaa2 [file] [log] [blame]
package org.checkerframework.dataflow.cfg.builder;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.Block.BlockType;
import org.checkerframework.dataflow.cfg.block.BlockImpl;
import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
import org.checkerframework.dataflow.cfg.block.RegularBlockImpl;
import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
/* --------------------------------------------------------- */
/* Phase Three */
/* --------------------------------------------------------- */
/**
* Class that performs phase three of the translation process. In particular, the following
* degenerate cases of basic blocks are removed:
*
* <ol>
* <li>Empty regular basic blocks: These blocks will be removed and their predecessors linked
* directly to the successor.
* <li>Conditional basic blocks that have the same basic block as the 'then' and 'else' successor:
* The conditional basic block will be removed in this case.
* <li>Two consecutive, non-empty, regular basic blocks where the second block has exactly one
* predecessor (namely the other of the two blocks): In this case, the two blocks are merged.
* <li>Some basic blocks might not be reachable from the entryBlock. These basic blocks are
* removed, and the list of predecessors (in the doubly-linked structure of basic blocks) are
* adapted correctly.
* </ol>
*
* Eliminating the second type of degenerate cases might introduce cases of the third problem. These
* are also removed.
*/
public class CFGTranslationPhaseThree {
/** A simple wrapper object that holds a basic block and allows to set one of its successors. */
protected interface PredecessorHolder {
void setSuccessor(BlockImpl b);
BlockImpl getBlock();
}
/**
* Perform phase three on the control flow graph {@code cfg}.
*
* @param cfg the control flow graph. Ownership is transfered to this method and the caller is not
* allowed to read or modify {@code cfg} after the call to {@code process} any more.
* @return the resulting control flow graph
*/
@SuppressWarnings("nullness") // TODO: successors
public static ControlFlowGraph process(ControlFlowGraph cfg) {
Set<Block> worklist = cfg.getAllBlocks();
Set<Block> dontVisit = new HashSet<>();
// note: this method has to be careful when relinking basic blocks
// to not forget to adjust the predecessors, too
// fix predecessor lists by removing any unreachable predecessors
for (Block c : worklist) {
BlockImpl cur = (BlockImpl) c;
for (Block pred : new HashSet<>(cur.getPredecessors())) {
if (!worklist.contains(pred)) {
cur.removePredecessor((BlockImpl) pred);
}
}
}
// remove empty blocks
for (Block cur : worklist) {
if (dontVisit.contains(cur)) {
continue;
}
if (cur.getType() == BlockType.REGULAR_BLOCK) {
RegularBlockImpl b = (RegularBlockImpl) cur;
if (b.isEmpty()) {
Set<RegularBlockImpl> emptyBlocks = new HashSet<>();
Set<PredecessorHolder> predecessors = new LinkedHashSet<>();
BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors);
for (RegularBlockImpl e : emptyBlocks) {
succ.removePredecessor(e);
dontVisit.add(e);
}
for (PredecessorHolder p : predecessors) {
BlockImpl block = p.getBlock();
dontVisit.add(block);
succ.removePredecessor(block);
p.setSuccessor(succ);
}
}
}
}
// remove useless conditional blocks
/* Issue 3267 revealed that this is a dangerous optimization:
it merges a block that evaluates one condition onto an unrelated following block,
which can also be a condition. The then/else stores from the first block are still
set, leading to incorrect results for the then/else stores in the following block.
The correct result would be to merge the then/else stores from the previous block.
However, as this is late in the CFG construction, I didn't see how to add e.g. a
dummy variable declaration node in a dummy regular block, which would cause a merge.
So for now, let's not perform this optimization.
It would be interesting to know how large the impact of this optimization is.
worklist = cfg.getAllBlocks();
for (Block c : worklist) {
BlockImpl cur = (BlockImpl) c;
if (cur.getType() == BlockType.CONDITIONAL_BLOCK) {
ConditionalBlockImpl cb = (ConditionalBlockImpl) cur;
assert cb.getPredecessors().size() == 1;
if (cb.getThenSuccessor() == cb.getElseSuccessor()) {
BlockImpl pred = cb.getPredecessors().iterator().next();
PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb);
BlockImpl succ = (BlockImpl) cb.getThenSuccessor();
succ.removePredecessor(cb);
predecessorHolder.setSuccessor(succ);
}
}
}
*/
// merge consecutive basic blocks if possible
worklist = cfg.getAllBlocks();
for (Block cur : worklist) {
if (cur.getType() == BlockType.REGULAR_BLOCK) {
RegularBlockImpl b = (RegularBlockImpl) cur;
Block succ = b.getRegularSuccessor();
if (succ.getType() == BlockType.REGULAR_BLOCK) {
RegularBlockImpl rs = (RegularBlockImpl) succ;
if (rs.getPredecessors().size() == 1) {
b.setSuccessor(rs.getRegularSuccessor());
b.addNodes(rs.getNodes());
rs.getRegularSuccessor().removePredecessor(rs);
}
}
}
}
return cfg;
}
/**
* Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start}
* and going both forward and backwards. Furthermore, compute the predecessors of these empty
* blocks ({@code predecessors} ), and their single successor (return value).
*
* @param start the starting point of the search (an empty, regular basic block)
* @param emptyBlocks a set to be filled by this method with all empty basic blocks found
* (including {@code start}).
* @param predecessors a set to be filled by this method with all predecessors
* @return the single successor of the set of the empty basic blocks
*/
@SuppressWarnings({
"interning:not.interned", // AST node comparisons
"nullness" // successors
})
protected static BlockImpl computeNeighborhoodOfEmptyBlock(
RegularBlockImpl start,
Set<RegularBlockImpl> emptyBlocks,
Set<PredecessorHolder> predecessors) {
// get empty neighborhood that come before 'start'
computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors);
// go forward
BlockImpl succ = (BlockImpl) start.getSuccessor();
while (succ.getType() == BlockType.REGULAR_BLOCK) {
RegularBlockImpl cur = (RegularBlockImpl) succ;
if (cur.isEmpty()) {
computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors);
assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks";
succ = (BlockImpl) cur.getSuccessor();
if (succ == cur) {
// An infinite loop, making exit block unreachable
break;
}
} else {
break;
}
}
return succ;
}
/**
* Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start}
* and looking only backwards in the control flow graph. Furthermore, compute the predecessors of
* these empty blocks ({@code predecessors}).
*
* @param start the starting point of the search (an empty, regular basic block)
* @param emptyBlocks a set to be filled by this method with all empty basic blocks found
* (including {@code start}).
* @param predecessors a set to be filled by this method with all predecessors
*/
protected static void computeNeighborhoodOfEmptyBlockBackwards(
RegularBlockImpl start,
Set<RegularBlockImpl> emptyBlocks,
Set<PredecessorHolder> predecessors) {
RegularBlockImpl cur = start;
emptyBlocks.add(cur);
for (final Block p : cur.getPredecessors()) {
BlockImpl pred = (BlockImpl) p;
switch (pred.getType()) {
case SPECIAL_BLOCK:
// add pred correctly to predecessor list
predecessors.add(getPredecessorHolder(pred, cur));
break;
case CONDITIONAL_BLOCK:
// add pred correctly to predecessor list
predecessors.add(getPredecessorHolder(pred, cur));
break;
case EXCEPTION_BLOCK:
// add pred correctly to predecessor list
predecessors.add(getPredecessorHolder(pred, cur));
break;
case REGULAR_BLOCK:
RegularBlockImpl r = (RegularBlockImpl) pred;
if (r.isEmpty()) {
// recursively look backwards
if (!emptyBlocks.contains(r)) {
computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors);
}
} else {
// add pred correctly to predecessor list
predecessors.add(getPredecessorHolder(pred, cur));
}
break;
}
}
}
/**
* Return a predecessor holder that can be used to set the successor of {@code pred} in the place
* where previously the edge pointed to {@code cur}. Additionally, the predecessor holder also
* takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} predecessors).
*
* @param pred a block whose successor should be set
* @param cur the previous successor of {@code pred}
* @return a predecessor holder to set the successor of {@code pred}
*/
@SuppressWarnings("interning:not.interned") // AST node comparisons
protected static PredecessorHolder getPredecessorHolder(
final BlockImpl pred, final BlockImpl cur) {
switch (pred.getType()) {
case SPECIAL_BLOCK:
SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred;
return singleSuccessorHolder(s, cur);
case CONDITIONAL_BLOCK:
// add pred correctly to predecessor list
final ConditionalBlockImpl c = (ConditionalBlockImpl) pred;
if (c.getThenSuccessor() == cur) {
return new PredecessorHolder() {
@Override
public void setSuccessor(BlockImpl b) {
c.setThenSuccessor(b);
cur.removePredecessor(pred);
}
@Override
public BlockImpl getBlock() {
return c;
}
};
} else {
assert c.getElseSuccessor() == cur;
return new PredecessorHolder() {
@Override
public void setSuccessor(BlockImpl b) {
c.setElseSuccessor(b);
cur.removePredecessor(pred);
}
@Override
public BlockImpl getBlock() {
return c;
}
};
}
case EXCEPTION_BLOCK:
// add pred correctly to predecessor list
final ExceptionBlockImpl e = (ExceptionBlockImpl) pred;
if (e.getSuccessor() == cur) {
return singleSuccessorHolder(e, cur);
} else {
@SuppressWarnings("keyfor:assignment") // ignore keyfor type
Set<Map.Entry<TypeMirror, Set<Block>>> entrySet = e.getExceptionalSuccessors().entrySet();
for (final Map.Entry<TypeMirror, Set<Block>> entry : entrySet) {
if (entry.getValue().contains(cur)) {
return new PredecessorHolder() {
@Override
public void setSuccessor(BlockImpl b) {
e.addExceptionalSuccessor(b, entry.getKey());
cur.removePredecessor(pred);
}
@Override
public BlockImpl getBlock() {
return e;
}
};
}
}
}
throw new Error("Unreachable");
case REGULAR_BLOCK:
RegularBlockImpl r = (RegularBlockImpl) pred;
return singleSuccessorHolder(r, cur);
default:
throw new Error("Unexpected block type " + pred.getType());
}
}
/**
* Returns a {@link PredecessorHolder} that sets the successor of a single successor block {@code
* s}.
*
* @return a {@link PredecessorHolder} that sets the successor of a single successor block {@code
* s}
*/
protected static PredecessorHolder singleSuccessorHolder(
final SingleSuccessorBlockImpl s, final BlockImpl old) {
return new PredecessorHolder() {
@Override
public void setSuccessor(BlockImpl b) {
s.setSuccessor(b);
old.removePredecessor(s);
}
@Override
public BlockImpl getBlock() {
return s;
}
};
}
}