Mirror of the BLOAT repository
https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3491 lines
92 KiB
3491 lines
92 KiB
/**
|
|
* All files in the distribution of BLOAT (Bytecode Level Optimization and
|
|
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
|
|
* Research Foundation of Purdue University. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package EDU.purdue.cs.bloat.cfg;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
import EDU.purdue.cs.bloat.codegen.*;
|
|
import EDU.purdue.cs.bloat.editor.*;
|
|
import EDU.purdue.cs.bloat.trans.*;
|
|
import EDU.purdue.cs.bloat.tree.*;
|
|
import EDU.purdue.cs.bloat.util.*;
|
|
|
|
/**
|
|
* FlowGraph constructs and represents a Control Flow Graph (CFG) used for
|
|
* analyzing a method. It consists of the basic blocks of a method.
|
|
* <p>
|
|
*
|
|
*
|
|
* @see MethodEditor
|
|
* @see Block
|
|
*/
|
|
public class FlowGraph extends Graph {
|
|
public static final int PEEL_NO_LOOPS = 0;
|
|
|
|
public static final int PEEL_ALL_LOOPS = -1;
|
|
|
|
public static int PEEL_LOOPS_LEVEL = 1;
|
|
|
|
public static boolean DEBUG = false;
|
|
|
|
public static boolean DB_GRAPHS = false;
|
|
|
|
public static boolean PRINT_GRAPH = false;
|
|
|
|
MethodEditor method; // The method that we create a CFG for.
|
|
|
|
Map subroutines; // Mapping between a Subroutine and its
|
|
|
|
// entry Block
|
|
List catchBlocks; // The Blocks that begin exception handlers
|
|
|
|
Map handlers; // Maps first block of exception handler to
|
|
|
|
// its Handler object.
|
|
|
|
Block srcBlock; // The first (source) basic Block in this method
|
|
|
|
Block snkBlock; // The "last" Block (where throw and return go)
|
|
|
|
Block iniBlock; // Block that handles initialization of parameters
|
|
|
|
// A trace is a series of basic blocks that have the following properties:
|
|
// 1) Blocks that end with a conditional jump are followed by the block
|
|
// that is executed when the conditon is false.
|
|
// 2) Where possible, blocks ending with a unconditional jump are
|
|
// followed by the block that is the target of that unconditional jump.
|
|
// Property 1) allows conditionals that resolve to false to "fall through"
|
|
// and property 2) allows for the removal of labels. Typically,
|
|
// bytecode will already be in trace form.
|
|
|
|
List trace; // All of the basic Blocks except source and sink
|
|
|
|
Graph loopTree; // A graph representing the loop nesting in
|
|
|
|
// the method.
|
|
|
|
// Modification counts for the dominator tree and the loop tree.
|
|
// Recall that superclass Graph maintains the modifications counts
|
|
// on nodes and edges.
|
|
int domEdgeModCount;
|
|
|
|
int loopEdgeModCount;
|
|
|
|
// The maximum (greatest) loop depth/level
|
|
int maxLoopDepth = 0;
|
|
|
|
private void db(final String s) {
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
System.out.println(s);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param method
|
|
* The method to create the CFG for.
|
|
*/
|
|
public FlowGraph(final MethodEditor method) {
|
|
this.method = method;
|
|
|
|
subroutines = new HashMap();
|
|
catchBlocks = new ArrayList(method.tryCatches().size());
|
|
handlers = new HashMap(method.tryCatches().size() * 2 + 1);
|
|
trace = new LinkedList();
|
|
|
|
srcBlock = newBlock();
|
|
iniBlock = newBlock();
|
|
snkBlock = newBlock();
|
|
|
|
trace.add(iniBlock);
|
|
|
|
// If this method is empty(!) just make some default cfg edges
|
|
if (method.codeLength() == 0) {
|
|
addEdge(srcBlock, iniBlock);
|
|
addEdge(iniBlock, snkBlock);
|
|
addEdge(srcBlock, snkBlock);
|
|
|
|
buildSpecialTrees(null, null);
|
|
|
|
return;
|
|
}
|
|
|
|
final Map labelPos = new HashMap();
|
|
buildBlocks(labelPos);
|
|
removeUnreachable();
|
|
|
|
// Make sure any labels in the removed blocks are saved.
|
|
saveLabels();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
System.out.println("---------- After building tree:");
|
|
print(System.out);
|
|
System.out.println("---------- end print after building tree");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum loop depth (also the maximum loop height) in the
|
|
* control flow graph.
|
|
*/
|
|
public int maxLoopDepth() {
|
|
return (maxLoopDepth);
|
|
}
|
|
|
|
/**
|
|
* Sets up the control flow graph. Computes the dominators and the dominance
|
|
* frontier, cleans up the tree, works with the loops, inserts stores to aid
|
|
* copy and constant propagation as well as code generation.
|
|
*/
|
|
public void initialize() {
|
|
if (method.codeLength() == 0) {
|
|
computeDominators();
|
|
buildLoopTree();
|
|
return;
|
|
}
|
|
|
|
// Determine which vertices dominate which vertices, update the blocks
|
|
// in the cfg appropriately
|
|
computeDominators();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After computing dominators (Begin)");
|
|
this.print(System.out);
|
|
db("------ After computing dominators (End)");
|
|
}
|
|
|
|
// Make sure that no block is both an entry block and a return target.
|
|
splitPhiBlocks();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After splitting phi blocks (Begin)");
|
|
this.print(System.out);
|
|
db("------ After splitting phi blocks (End)");
|
|
}
|
|
|
|
removeUnreachable();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After removing unreachable 1 (Begin)");
|
|
this.print(System.out);
|
|
db("------ After removing unreachable 1 (End)");
|
|
}
|
|
|
|
splitIrreducibleLoops();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After splitting irreduciable loops (Begin)");
|
|
this.print(System.out);
|
|
db("------ After splitting irreducible loops (End)");
|
|
}
|
|
|
|
removeUnreachable();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After removing unreachable 2 (Begin)");
|
|
this.print(System.out);
|
|
db("------ After removing unreachable 2 (End)");
|
|
}
|
|
|
|
splitReducibleLoops();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After splitting reducible loops (Begin)");
|
|
this.print(System.out);
|
|
db("------ After splitting reducible loops (End)");
|
|
}
|
|
|
|
removeUnreachable();
|
|
|
|
if (FlowGraph.DEBUG || FlowGraph.DB_GRAPHS) {
|
|
db("------ After removing unreachable 3 (Begin)");
|
|
this.print(System.out);
|
|
db("------ After removing unreachable 3 (End)");
|
|
}
|
|
|
|
buildLoopTree();
|
|
|
|
peelLoops(FlowGraph.PEEL_LOOPS_LEVEL);
|
|
|
|
removeCriticalEdges();
|
|
removeUnreachable();
|
|
|
|
// Insert stores after conditional branches to aid copy and constant
|
|
// propagation.
|
|
insertConditionalStores();
|
|
|
|
// Insert stores at the beginnings of protected regions to aid
|
|
// code generation for PhiCatchStmts.
|
|
insertProtectedRegionStores();
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("---------- After splitting loops:");
|
|
print(System.out);
|
|
System.out.println("---------- end print after splitting loops");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the loop tree for the method modeled by this flow graph. The loop
|
|
* tree represents the nesting of the loops in a method. The procedure is at
|
|
* the root of the loop tree. Nested loops are represented by a parent and
|
|
* child relationship.
|
|
*/
|
|
public Graph loopTree() {
|
|
if (loopEdgeModCount != edgeModCount) {
|
|
buildLoopTree();
|
|
}
|
|
|
|
return loopTree;
|
|
}
|
|
|
|
/**
|
|
* Builds the loop tree.
|
|
*
|
|
* @see #loopTree
|
|
* @see LoopNode
|
|
*/
|
|
private void buildLoopTree() {
|
|
db(" Building loop tree");
|
|
|
|
loopEdgeModCount = edgeModCount;
|
|
|
|
removeUnreachable();
|
|
|
|
setBlockTypes();
|
|
|
|
final LoopNode root = new LoopNode(srcBlock);
|
|
|
|
// loopTree has one root, the node containing the srcBlock.
|
|
loopTree = new Graph() {
|
|
public Collection roots() {
|
|
final ArrayList r = new ArrayList(1);
|
|
r.add(root);
|
|
return r;
|
|
}
|
|
};
|
|
|
|
loopTree.addNode(srcBlock, root);
|
|
|
|
Iterator iter = nodes().iterator();
|
|
|
|
// Iterate over the blocks in the control flow graph. Add blocks
|
|
// to the loop tree node corresponding to their loop header. If
|
|
// the block itself is a header block, make a new loop tree node
|
|
// for it. An edge in the loop tree is added from the outer loop
|
|
// tree node to the inner loop tree node.
|
|
while (iter.hasNext()) {
|
|
final Block block = (Block) iter.next();
|
|
final Block header = block.header();
|
|
|
|
if (header != null) {
|
|
LoopNode headerLoop = (LoopNode) loopTree.getNode(header);
|
|
|
|
if (headerLoop == null) {
|
|
headerLoop = new LoopNode(header);
|
|
loopTree.addNode(header, headerLoop);
|
|
}
|
|
|
|
headerLoop.elements.add(block);
|
|
|
|
if (block.blockType() != Block.NON_HEADER) {
|
|
LoopNode loop = (LoopNode) loopTree.getNode(block);
|
|
|
|
if (loop == null) {
|
|
loop = new LoopNode(block);
|
|
loopTree.addNode(block, loop);
|
|
}
|
|
|
|
// Edges go from outer loops in.
|
|
loopTree.addEdge(headerLoop, loop);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Iterate over the loop tree from the bottom up and determine
|
|
// each node's level. Level 0 occurs at the leaf nodes.
|
|
iter = loopTree.postOrder().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final LoopNode loop = (LoopNode) iter.next();
|
|
|
|
// The level of the node is max(level(succs)) + 1.
|
|
int level = 0;
|
|
|
|
final Iterator succs = loopTree.succs(loop).iterator();
|
|
|
|
while (succs.hasNext()) {
|
|
final LoopNode inner = (LoopNode) succs.next();
|
|
|
|
if (level < inner.level) {
|
|
level = inner.level;
|
|
}
|
|
}
|
|
|
|
loop.level = level + 1;
|
|
}
|
|
|
|
// Iterate over the loop tree from the top down and determine each
|
|
// node's depth. Depth 0 occurs at the root node.
|
|
iter = loopTree.preOrder().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final LoopNode loop = (LoopNode) iter.next();
|
|
|
|
// The depth of the node is depth(pred) + 1.
|
|
final Iterator preds = loopTree.preds(loop).iterator();
|
|
|
|
if (preds.hasNext()) {
|
|
final LoopNode outer = (LoopNode) preds.next();
|
|
loop.depth = outer.depth + 1;
|
|
|
|
} else {
|
|
loop.depth = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the basic blocks for the method that is the cfg.
|
|
*
|
|
* @param labelPos
|
|
* A mapping between the Labels in the code that start a basic
|
|
* block and their offset in the code (an Integer).
|
|
*/
|
|
private void buildBlocks(final Map labelPos) {
|
|
db(" Building blocks");
|
|
|
|
// Get the Labels and Instructions of this method
|
|
ListIterator iter = method.code().listIterator();
|
|
|
|
// Go through the code, find each Label that starts a block, create
|
|
// a Block for that Label, and add it to the trace.
|
|
while (iter.hasNext()) {
|
|
final Object obj = iter.next();
|
|
|
|
if (obj instanceof Label) {
|
|
final Label label = (Label) obj;
|
|
|
|
if (label.startsBlock()) {
|
|
trace.add(newBlock(label));
|
|
}
|
|
}
|
|
}
|
|
|
|
Instruction lastInst = null;
|
|
|
|
Block currBlock = iniBlock;
|
|
Block firstBlock = null;
|
|
|
|
int i = 0;
|
|
iter = method.code().listIterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Object curr = iter.next();
|
|
|
|
if (curr instanceof Label) {
|
|
final Label label = (Label) curr;
|
|
|
|
if (label.startsBlock()) {
|
|
labelPos.put(label, new Integer(i));
|
|
|
|
final Block nextBlock = (Block) getNode(label);
|
|
|
|
// If the last instruction we saw was a jsr, establish a
|
|
// path
|
|
// between the current block and the block that contains the
|
|
// subroutine (operand to the jsr).
|
|
if ((lastInst != null) && lastInst.isJsr()) {
|
|
final Block target = (Block) getNode(lastInst.operand());
|
|
final Subroutine sub = (Subroutine) subroutines
|
|
.get(target);
|
|
sub.addPath(currBlock, nextBlock);
|
|
}
|
|
|
|
currBlock = nextBlock; // Go on to next block
|
|
|
|
if (firstBlock == null) {
|
|
firstBlock = currBlock;
|
|
}
|
|
}
|
|
|
|
} else if (curr instanceof Instruction) {
|
|
final Instruction currInst = (Instruction) curr;
|
|
|
|
lastInst = currInst;
|
|
|
|
// Call setsubEntry to maintain a mapping between the entry
|
|
// block of a Subroutine and the Subroutine itself
|
|
if (currInst.isJsr()) {
|
|
final Label label = (Label) currInst.operand();
|
|
final Block target = (Block) getNode(label);
|
|
|
|
if (!subroutines.containsKey(target)) {
|
|
final Subroutine sub = new Subroutine(this);
|
|
setSubEntry(sub, target);
|
|
}
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
i++; // Go to next instruction
|
|
}
|
|
|
|
// Start the tedious process of building the expression trees for
|
|
// the basic blocks
|
|
buildTrees(firstBlock, labelPos);
|
|
}
|
|
|
|
/**
|
|
* Build the trees for the blocks, construct subroutines and add edges in
|
|
* the flow graph.
|
|
*
|
|
* There is a edge from the source block to the init block, to the entry of
|
|
* each subroutine and to the catch block of each exception handler.
|
|
*
|
|
* After building trees for all nodes reachable from the source, blocks with
|
|
* null trees are removed since they are unreachable.
|
|
*
|
|
* Edges are added to the sink block from each node ending in a return, a
|
|
* throw, or a ret.
|
|
*
|
|
* In addition, an edge is added to the sink block from each node ending in
|
|
* unconditional branch to an ancestor. These edges are used to allow the
|
|
* post dominator tree to be contructed in the presence of loops.
|
|
*
|
|
* @param firstBlock
|
|
* The first block of code in this method.
|
|
* @param labelPos
|
|
* A mapping between Labels and their instruction number (offset
|
|
* into the code).
|
|
*/
|
|
private void buildTrees(final Block firstBlock, final Map labelPos) {
|
|
db(" Building trees for " + firstBlock);
|
|
|
|
// Maps a "catch block" (beginning of exception handler that
|
|
// stores the exception) to a "catch body" (the code immediately
|
|
// follows the "catch block" -- the rest of the handler).
|
|
final HashMap catchBodies = new HashMap(
|
|
method.tryCatches().size() * 2 + 1);
|
|
|
|
final Iterator tryCatches = method.tryCatches().iterator();
|
|
|
|
while (tryCatches.hasNext()) {
|
|
final TryCatch tc = (TryCatch) tryCatches.next();
|
|
|
|
// We create two blocks for each handler.
|
|
// catchBlock is the handler target. It contains the code
|
|
// which saves the exception on the operand stack.
|
|
// catchBody is the block following the handler target.
|
|
// It contains the code for the exception handler body.
|
|
// We need to split these two blocks so that the handler target
|
|
// cannot possibly be a loop header.
|
|
|
|
// This block will be the target of the exception handler.
|
|
final Block catchBlock = newBlock();
|
|
|
|
// This block will hold the instructions in the catch body.
|
|
final Block catchBody = (Block) getNode(tc.handler());
|
|
|
|
catchBodies.put(catchBlock, catchBody);
|
|
|
|
// Make sure we include the new block in any protected area
|
|
// containing the catch body.
|
|
Integer pos = (Integer) labelPos.get(tc.handler());
|
|
labelPos.put(catchBlock.label(), pos);
|
|
|
|
addEdge(catchBlock, catchBody);
|
|
trace.add(trace.indexOf(catchBody), catchBlock);
|
|
|
|
Type type = tc.type();
|
|
|
|
if (type == null) {
|
|
type = Type.NULL;
|
|
}
|
|
|
|
catchBlocks.add(catchBlock);
|
|
|
|
// Save the exception to the stack.
|
|
final StackExpr lhs = new StackExpr(0, Type.THROWABLE);
|
|
final CatchExpr rhs = new CatchExpr(type, Type.THROWABLE);
|
|
final StoreExpr store = new StoreExpr(lhs, rhs, Type.THROWABLE);
|
|
|
|
// Build the tree for the exception handler target block.
|
|
final Tree tree = new Tree(catchBlock, new OperandStack());
|
|
catchBlock.setTree(tree);
|
|
tree.addStmt(new ExprStmt(store));
|
|
tree.addStmt(new GotoStmt(catchBody));
|
|
|
|
// Create the Handler.
|
|
final Integer start = (Integer) labelPos.get(tc.start());
|
|
final Integer end = (Integer) labelPos.get(tc.end());
|
|
|
|
final Handler handler = new Handler(catchBlock, type);
|
|
handlers.put(catchBlock, handler);
|
|
|
|
final Iterator blocks = nodes().iterator();
|
|
|
|
// Examine all of the basic blocks in this CFG. If the block's
|
|
// offset into the code is between the start and end points of
|
|
// the TryCatch, then it is a protected block. So, the block
|
|
// should be added to the Handler's list of protected blocks.
|
|
while (blocks.hasNext()) {
|
|
final Block block = (Block) blocks.next();
|
|
|
|
pos = (Integer) labelPos.get(block.label());
|
|
|
|
if (pos == null) {
|
|
// This is one of the special blocks such as the source,
|
|
// sink, and init block.
|
|
continue;
|
|
}
|
|
|
|
if (start.intValue() <= pos.intValue()) {
|
|
if ((end == null) || (pos.intValue() < end.intValue())) {
|
|
handler.protectedBlocks().add(block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
addEdge(srcBlock, iniBlock);
|
|
addEdge(srcBlock, snkBlock);
|
|
addEdge(iniBlock, firstBlock);
|
|
|
|
buildSpecialTrees(catchBodies, labelPos);
|
|
|
|
// Build the trees for the blocks reachable from the firstBlock.
|
|
buildTreeForBlock(firstBlock, iniBlock.tree().stack(), null, labelPos,
|
|
catchBodies);
|
|
}
|
|
|
|
/**
|
|
* Insert stores after conditional branches to aid copy and constant
|
|
* propagation.
|
|
*
|
|
* If a+b and c+d are non-leaf expressions, we convert:
|
|
*
|
|
* if (a+b == c+d) X else Y
|
|
*
|
|
* to:
|
|
*
|
|
* if ((e = a+b) == (f = c+d)) e = f X else Y
|
|
*
|
|
* We can't do this for reference types since we may loose type information.
|
|
* Consider:
|
|
*
|
|
* class A {} class B extends A { void foo(); }
|
|
*
|
|
* L := someA(); M := someB();
|
|
*
|
|
* if (L == M) { M.foo(); }
|
|
*
|
|
* -->
|
|
*
|
|
* if (L == M) { M = L; // M now has type A, not B M.foo(); }
|
|
*
|
|
*/
|
|
private void insertConditionalStores() {
|
|
db(" Inserting conditional stores");
|
|
|
|
final Iterator blocks = new ImmutableIterator(nodes());
|
|
|
|
while (blocks.hasNext()) {
|
|
final Block block = (Block) blocks.next();
|
|
|
|
final Stmt last = block.tree().lastStmt();
|
|
|
|
if (last instanceof IfCmpStmt) {
|
|
final IfCmpStmt stmt = (IfCmpStmt) last;
|
|
|
|
// Where do we insert the conditional? (The target of the true
|
|
// or false branch.)
|
|
Block target = null;
|
|
|
|
// Exclude targets which are mentioned more than once.
|
|
// This prevents:
|
|
//
|
|
// if (i == j) goto L
|
|
// else goto L
|
|
// L:
|
|
// i = j;
|
|
//
|
|
// Note: this shouldn't happen with IfStmts after critical.
|
|
// edges are removed.
|
|
//
|
|
if (stmt.trueTarget() == stmt.falseTarget()) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore all comparison operations except EQ and NE
|
|
if (stmt.comparison() == IfStmt.EQ) {
|
|
target = stmt.trueTarget();
|
|
|
|
} else if (stmt.comparison() == IfStmt.NE) {
|
|
target = stmt.falseTarget();
|
|
}
|
|
|
|
if (target != null) {
|
|
Expr left = stmt.left();
|
|
Expr right = stmt.right();
|
|
|
|
// If either the left expression or the right expresion is a
|
|
// reference, then we can't do anything. See above.
|
|
if (!left.type().isReference()
|
|
&& !right.type().isReference()) {
|
|
|
|
// If either of the expression is a leaf expression
|
|
// (meaning that it has no children expressions), make a
|
|
// new local variable to store the result of the
|
|
// expression. Replace the expression with a StoreExpr
|
|
// that stores the result of the expression into the
|
|
// local
|
|
// variable.
|
|
|
|
if (!(left instanceof LeafExpr)) {
|
|
final LocalVariable v = method
|
|
.newLocal(left.type());
|
|
final LocalExpr tmp = new LocalExpr(v.index(), left
|
|
.type());
|
|
final Expr copy = (Expr) left.clone();
|
|
copy.setDef(null);
|
|
left.replaceWith(new StoreExpr(tmp, copy, left
|
|
.type()));
|
|
left = tmp;
|
|
}
|
|
|
|
if (!(right instanceof LeafExpr)) {
|
|
final LocalVariable v = method.newLocal(right
|
|
.type());
|
|
final LocalExpr tmp = new LocalExpr(v.index(),
|
|
right.type());
|
|
final Expr copy = (Expr) right.clone();
|
|
copy.setDef(null);
|
|
right.replaceWith(new StoreExpr(tmp, copy, right
|
|
.type()));
|
|
right = tmp;
|
|
}
|
|
|
|
// If either the left expression or the right expression
|
|
// is a LocalExpr (meaning that it used to be a non-leaf
|
|
// expression and was replaced with a LocalExpr above),
|
|
// then prepend an assignment to the LocalExpr to the
|
|
// target block.
|
|
|
|
if (left instanceof LocalExpr) {
|
|
final LocalExpr tmp = (LocalExpr) left.clone();
|
|
tmp.setDef(null);
|
|
final Expr copy = (Expr) right.clone();
|
|
copy.setDef(null);
|
|
final Stmt insert = new ExprStmt(new StoreExpr(tmp,
|
|
copy, left.type()));
|
|
|
|
target.tree().prependStmt(insert);
|
|
|
|
} else if (right instanceof LocalExpr) {
|
|
final LocalExpr tmp = (LocalExpr) right.clone();
|
|
tmp.setDef(null);
|
|
final Expr copy = (Expr) left.clone();
|
|
copy.setDef(null);
|
|
final Stmt insert = new ExprStmt(new StoreExpr(tmp,
|
|
copy, right.type()));
|
|
|
|
target.tree().prependStmt(insert);
|
|
|
|
} else {
|
|
Assert.isTrue((left instanceof ConstantExpr)
|
|
&& (right instanceof ConstantExpr));
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (last instanceof IfZeroStmt) {
|
|
final IfZeroStmt stmt = (IfZeroStmt) last;
|
|
Block target = null;
|
|
|
|
// Exclude targets which are mentioned more than once.
|
|
// This prevents:
|
|
//
|
|
// if (i == j) goto L
|
|
// else goto L
|
|
// L:
|
|
// i = j;
|
|
//
|
|
// Note: this shouldn't happen with IfStmts after critical.
|
|
// edges are removed.
|
|
//
|
|
if (stmt.trueTarget() == stmt.falseTarget()) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore all comparisons except for EQ and NE
|
|
if (stmt.comparison() == IfStmt.EQ) {
|
|
target = stmt.trueTarget();
|
|
|
|
} else if (stmt.comparison() == IfStmt.NE) {
|
|
target = stmt.falseTarget();
|
|
}
|
|
|
|
if (target != null) {
|
|
Expr left = stmt.expr();
|
|
|
|
if (!left.type().isReference()) {
|
|
// If left is not a leaf expression, make a new local
|
|
// variable and replace left with an assignment from
|
|
// left
|
|
// to the local variable.
|
|
|
|
if (!(left instanceof LeafExpr)) {
|
|
final LocalVariable v = method
|
|
.newLocal(left.type());
|
|
final LocalExpr tmp = new LocalExpr(v.index(), left
|
|
.type());
|
|
final Expr copy = (Expr) left.clone();
|
|
copy.setDef(null);
|
|
left.replaceWith(new StoreExpr(tmp, copy, left
|
|
.type()));
|
|
left = tmp;
|
|
}
|
|
|
|
// Value of the right hand side. 0 if left is an
|
|
// integer,
|
|
// null otherwise (left is a reference type).
|
|
Object value = null;
|
|
|
|
final Type type = left.type();
|
|
|
|
if (left.type().isIntegral()) {
|
|
value = new Integer(0);
|
|
|
|
} else {
|
|
Assert.isTrue(left.type().isReference());
|
|
}
|
|
|
|
if (left instanceof LocalExpr) {
|
|
// Prepend the target block with an assignment from
|
|
// the
|
|
// value of the right hand side to the left
|
|
// expression.
|
|
|
|
final LocalExpr copy = (LocalExpr) left.clone();
|
|
copy.setDef(null);
|
|
final Stmt insert = new ExprStmt(new StoreExpr(
|
|
copy, new ConstantExpr(value, type), left
|
|
.type()));
|
|
|
|
target.tree().prependStmt(insert);
|
|
|
|
} else {
|
|
Assert.isTrue(left instanceof ConstantExpr);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (last instanceof SwitchStmt) {
|
|
final SwitchStmt stmt = (SwitchStmt) last;
|
|
|
|
Expr index = stmt.index();
|
|
|
|
if (!(index instanceof LeafExpr)) {
|
|
// Replace index with a store into a new local variable
|
|
|
|
final LocalVariable v = method.newLocal(index.type());
|
|
final LocalExpr tmp = new LocalExpr(v.index(), index.type());
|
|
final Expr copy = (Expr) index.clone();
|
|
copy.setDef(null);
|
|
index.replaceWith(new StoreExpr(tmp, copy, index.type()));
|
|
index = tmp;
|
|
}
|
|
|
|
if (index instanceof LocalExpr) {
|
|
final Block[] targets = stmt.targets();
|
|
final int[] values = stmt.values();
|
|
|
|
// Exclude targets which are mentioned more than once.
|
|
// This prevents:
|
|
//
|
|
// switch (i) {
|
|
// case 0:
|
|
// case 1:
|
|
// case 2:
|
|
// i = 0;
|
|
// i = 1;
|
|
// i = 2;
|
|
// use(i);
|
|
// break;
|
|
// case 3:
|
|
// break;
|
|
// }
|
|
//
|
|
final HashSet seen = new HashSet();
|
|
|
|
// Targets that are branched to more than once
|
|
final HashSet duplicate = new HashSet();
|
|
|
|
for (int i = 0; i < targets.length; i++) {
|
|
if (seen.contains(targets[i])) {
|
|
duplicate.add(targets[i]);
|
|
} else {
|
|
seen.add(targets[i]);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < targets.length; i++) {
|
|
final Block target = targets[i];
|
|
|
|
// Skip targets that can be branched to in multiple
|
|
// places
|
|
if (duplicate.contains(target)) {
|
|
continue;
|
|
}
|
|
|
|
// Why do we split the edge?
|
|
splitEdge(block, targets[i]);
|
|
|
|
// Make sure the edge was split.
|
|
Assert.isTrue(targets[i] != target);
|
|
|
|
// Insert a store to the index on the new empty block.
|
|
final LocalExpr copy = (LocalExpr) index.clone();
|
|
copy.setDef(null);
|
|
final Stmt insert = new ExprStmt(new StoreExpr(copy,
|
|
new ConstantExpr(new Integer(values[i]), index
|
|
.type()), index.type()));
|
|
|
|
targets[i].tree().prependStmt(insert);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert stores at the beginnings of protected regions to aid code
|
|
* generation for PhiCatchStmts.
|
|
*/
|
|
private void insertProtectedRegionStores() {
|
|
db(" Inserting protected region stores");
|
|
|
|
final HashSet tryPreds = new HashSet();
|
|
|
|
final Iterator blocks = catchBlocks.iterator();
|
|
|
|
// Iterate over the blocks in this control flow graph. Build a
|
|
// set of predacessors of all protected blocks that themselves are
|
|
// not in the protected block. These blocks end with a jump into
|
|
// a protected region.
|
|
while (blocks.hasNext()) {
|
|
final Block block = (Block) blocks.next();
|
|
|
|
final Handler handler = (Handler) handlers.get(block);
|
|
|
|
if (handler != null) {
|
|
final HashSet p = new HashSet();
|
|
|
|
final Iterator prots = handler.protectedBlocks().iterator();
|
|
|
|
while (prots.hasNext()) {
|
|
final Block prot = (Block) prots.next();
|
|
p.addAll(preds(prot));
|
|
}
|
|
|
|
p.removeAll(handler.protectedBlocks());
|
|
|
|
tryPreds.addAll(p);
|
|
}
|
|
}
|
|
|
|
// Starting with the source block,
|
|
insertProtStores(srcBlock, tryPreds, new ResizeableArrayList());
|
|
}
|
|
|
|
/**
|
|
* Insters copy statements into blocks whose successors lie in a protected
|
|
* region.
|
|
*
|
|
* @param block
|
|
* A block we are considering.
|
|
* @param tryPreds
|
|
* All blocks whose successor blocks lie in protected regions.
|
|
* @param defs
|
|
* Stores the expressions (LocalExprs) that define a given local
|
|
* variable (index into array) in block. Basically, it contains
|
|
* the LocalExpr that defines each local variable at a given
|
|
* block.
|
|
*/
|
|
private void insertProtStores(final Block block, final HashSet tryPreds,
|
|
final ResizeableArrayList defs) {
|
|
final Tree tree = block.tree();
|
|
|
|
// Visit all LocalExprs in block. Recall that LocalExpr
|
|
// represents a reference to a local variable. If the LocalExpr
|
|
// defines the variable, then added it to the defs array. defs is
|
|
// indexed by local variable.
|
|
tree.visitChildren(new TreeVisitor() {
|
|
public void visitLocalExpr(final LocalExpr expr) {
|
|
if (expr.isDef()) {
|
|
final int index = expr.index();
|
|
|
|
if (expr.type().isWide()) {
|
|
defs.ensureSize(index + 2);
|
|
defs.set(index, expr);
|
|
defs.set(index + 1, null);
|
|
|
|
} else {
|
|
defs.ensureSize(index + 1);
|
|
defs.set(index, expr);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// If block ends in a jump to a block in a protected region, add
|
|
// statements that make a copy of each local variable. This is
|
|
// done to avoid redefining local variables used by the jump
|
|
// statement. I'm not too sure about all of this.
|
|
|
|
if (tryPreds.contains(block)) {
|
|
// Examine all of the definitions of all the local variables
|
|
for (int i = 0; i < defs.size(); i++) {
|
|
final LocalExpr expr = (LocalExpr) defs.get(i);
|
|
|
|
if (expr != null) {
|
|
// Insert stores before the last stmt to ensure we don't
|
|
// redefine locals used by(?) the branch stmt.
|
|
final Stmt last = tree.lastStmt();
|
|
|
|
// Visit the Exprs in the last statement block. Remember
|
|
// that this statement ends in a jump to a protected block.
|
|
// Insert a store of the a copy of all Expr into a stack
|
|
// variable right before the jump. I think this saves all
|
|
// of the expressions in the jump statement to the stack.
|
|
// Why? I don't know.
|
|
|
|
last.visitChildren(new TreeVisitor() {
|
|
public void visitExpr(final Expr expr) {
|
|
StackExpr var = tree.newStack(expr.type());
|
|
var.setValueNumber(expr.valueNumber());
|
|
|
|
final Node p = expr.parent();
|
|
expr.setParent(null);
|
|
p.visit(new ReplaceVisitor(expr, var));
|
|
|
|
var = (StackExpr) var.clone();
|
|
var.setDef(null);
|
|
final StoreExpr store = new StoreExpr(var, expr,
|
|
expr.type());
|
|
store.setValueNumber(expr.valueNumber());
|
|
|
|
final Stmt storeStmt = new ExprStmt(store);
|
|
storeStmt.setValueNumber(expr.valueNumber());
|
|
|
|
tree.addStmtBeforeJump(storeStmt);
|
|
}
|
|
|
|
public void visitStackExpr(final StackExpr expr) {
|
|
}
|
|
});
|
|
|
|
// Add assignment statements (StoreExpr) that store a copy
|
|
// of expr (a defining instance of LocalExpr) into itself.
|
|
|
|
final LocalExpr copy1 = (LocalExpr) expr.clone();
|
|
final LocalExpr copy2 = (LocalExpr) expr.clone();
|
|
copy1.setDef(null);
|
|
copy2.setDef(null);
|
|
|
|
final StoreExpr store = new StoreExpr(copy1, copy2, expr
|
|
.type());
|
|
|
|
tree.addStmtBeforeJump(new ExprStmt(store));
|
|
}
|
|
}
|
|
}
|
|
|
|
final Iterator children = domChildren(block).iterator();
|
|
|
|
// Examine all of the blocks that block dominates. Note that
|
|
// local variables will have the same definitions as in block
|
|
// unless they are overriden in the child.
|
|
while (children.hasNext()) {
|
|
final Block child = (Block) children.next();
|
|
insertProtStores(child, tryPreds, new ResizeableArrayList(defs));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removing unreachable Blocks means that there are Labels that are no
|
|
* longer label valid blocks (e.g. start basic blocks) in the CFG. However,
|
|
* we still want the Labels to point to something meaningful. So, we hoist
|
|
* them out of CFG and place them into the init block as LabelStmts.
|
|
*/
|
|
private void saveLabels() {
|
|
// Make sure any labels in the removed blocks are saved.
|
|
boolean save = false;
|
|
|
|
final Iterator iter = method.code().listIterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Object obj = iter.next();
|
|
|
|
if (obj instanceof Label) {
|
|
final Label label = (Label) obj;
|
|
|
|
if (label.startsBlock()) {
|
|
if (getNode(label) == null) {
|
|
save = true;
|
|
} else {
|
|
save = false;
|
|
}
|
|
}
|
|
|
|
if (save) {
|
|
label.setStartsBlock(false);
|
|
iniBlock.tree().addStmt(new LabelStmt(label));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a subroutine from this method.
|
|
*
|
|
* @param sub
|
|
* The subroutine to remove.
|
|
*/
|
|
public void removeSub(final Subroutine sub) {
|
|
subroutines.remove(sub.entry());
|
|
}
|
|
|
|
/**
|
|
* Adds an edge between two nodes in this graph.
|
|
*
|
|
* @param src
|
|
* Node at which the edge originates.
|
|
* @param dst
|
|
* Node at which the edge terminates.
|
|
*/
|
|
public void addEdge(final GraphNode src, final GraphNode dst) {
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println(" ADDING EDGE " + src + " -> " + dst);
|
|
}
|
|
|
|
super.addEdge(src, dst);
|
|
}
|
|
|
|
/**
|
|
* Removes an edge from the graph and performs the necessary cleanup.
|
|
*
|
|
* @param v
|
|
* Node at which edge to be removed originates.
|
|
* @param w
|
|
* Node at which edge to be removed terminates.
|
|
*/
|
|
public void removeEdge(final GraphNode v, final GraphNode w) {
|
|
final Block src = (Block) v;
|
|
final Block dst = (Block) w;
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println(" REMOVING EDGE " + src + " -> " + dst);
|
|
}
|
|
|
|
super.removeEdge(src, dst);
|
|
|
|
cleanupEdge(src, dst);
|
|
}
|
|
|
|
/**
|
|
* Visit the tree starting at the destination node.
|
|
*/
|
|
private void cleanupEdge(final Block src, final Block dst) {
|
|
dst.visit(new TreeVisitor() {
|
|
public void visitPhiJoinStmt(final PhiJoinStmt stmt) {
|
|
final Expr operand = stmt.operandAt(src);
|
|
|
|
if (operand != null) {
|
|
operand.cleanup();
|
|
|
|
// Remove the operand associated with src
|
|
// from a PhiJoinStmt
|
|
stmt.setOperandAt(src, null);
|
|
}
|
|
}
|
|
|
|
public void visitStmt(final Stmt stmt) {
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a new <tt>Block</tt> with the next available <tt>Label</tt>.
|
|
*/
|
|
public Block newBlock() {
|
|
return newBlock(method.newLabel());
|
|
}
|
|
|
|
/**
|
|
* Creates a new Block starting with the specified Label. The Block is added
|
|
* to this FlowGraph using its label as its key.
|
|
*
|
|
* @param label
|
|
* The new Block's Label
|
|
*/
|
|
Block newBlock(final Label label) {
|
|
final Block block = new Block(label, this);
|
|
addNode(label, block);
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println(" new block " + block);
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* Uses classes DominatorTree and DominaceFrontier to calculate which blocks
|
|
* dominate which blocks.
|
|
*
|
|
* @see DominatorTree
|
|
* @see DominanceFrontier
|
|
*/
|
|
private void computeDominators() {
|
|
db(" Computing Dominators");
|
|
|
|
domEdgeModCount = edgeModCount;
|
|
|
|
removeUnreachable();
|
|
|
|
// Forward
|
|
DominatorTree.buildTree(this, false);
|
|
DominanceFrontier.buildFrontier(this, false);
|
|
|
|
// Reverse
|
|
DominatorTree.buildTree(this, true);
|
|
DominanceFrontier.buildFrontier(this, true);
|
|
}
|
|
|
|
/**
|
|
* Locate the reducible loops. We get better results if we call
|
|
* splitIrreducibleLoops first to split reducible loop headers from
|
|
* irreducible loops. This method is based on the analyze_loops algorithm
|
|
* in:
|
|
*
|
|
* Paul Havlak, "Nesting of Reducible and Irreducible Loops", TOPLAS, 19(4):
|
|
* 557-567, July 1997.
|
|
*/
|
|
private void setBlockTypes() {
|
|
db(" Setting block types");
|
|
|
|
final List blocks = preOrder();
|
|
|
|
// A block's predacessors that do not occur along back edges
|
|
final Set[] nonBackPreds = new Set[blocks.size()];
|
|
|
|
// A block's predacessors that DO occur along back edges
|
|
final Set[] backPreds = new Set[blocks.size()];
|
|
|
|
ListIterator iter = blocks.listIterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block w = (Block) iter.next();
|
|
final int wn = preOrderIndex(w);
|
|
|
|
final Set nonBack = new HashSet();
|
|
nonBackPreds[wn] = nonBack;
|
|
|
|
final Set back = new HashSet();
|
|
backPreds[wn] = back;
|
|
|
|
w.setHeader(srcBlock);
|
|
w.setBlockType(Block.NON_HEADER);
|
|
|
|
final Iterator preds = preds(w).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
|
|
// If w is an ancestor of v, (v,w) is a back edge.
|
|
if (isAncestorToDescendent(w, v)) {
|
|
back.add(v);
|
|
} else {
|
|
nonBack.add(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
srcBlock.setHeader(null);
|
|
|
|
final UnionFind uf = new UnionFind(blocks.size());
|
|
|
|
iter = blocks.listIterator(blocks.size());
|
|
|
|
while (iter.hasPrevious()) {
|
|
final Block w = (Block) iter.previous();
|
|
final int wn = preOrderIndex(w);
|
|
|
|
final Set nonBack = nonBackPreds[wn];
|
|
final Set back = backPreds[wn];
|
|
|
|
final Set body = new HashSet(); // The body of a loop
|
|
|
|
final Iterator preds = back.iterator();
|
|
|
|
// For each loop header, follow the back edges to construct the
|
|
// body of the loop
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
|
|
if (v != w) {
|
|
final int vn = preOrderIndex(v);
|
|
final Block f = (Block) blocks.get(uf.find(vn));
|
|
body.add(f);
|
|
|
|
} else {
|
|
// Self loop
|
|
w.setBlockType(Block.REDUCIBLE);
|
|
}
|
|
}
|
|
|
|
if (body.size() == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Initially assume the block is reducible
|
|
w.setBlockType(Block.REDUCIBLE);
|
|
|
|
final LinkedList worklist = new LinkedList(body);
|
|
|
|
while (!worklist.isEmpty()) {
|
|
final Block x = (Block) worklist.removeFirst();
|
|
final int xn = preOrderIndex(x);
|
|
|
|
final Iterator e = nonBackPreds[xn].iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Block y = (Block) e.next(); // a block in the loop
|
|
final int yn = preOrderIndex(y);
|
|
final Block z = (Block) blocks.get(uf.find(yn)); // loop
|
|
// header
|
|
// of y
|
|
|
|
if (!isAncestorToDescendent(w, z)) {
|
|
// If a block in the loop is not a descendent of the
|
|
// loop
|
|
// header, then there must be another entry path into
|
|
// the
|
|
// loop. Thus, the loop (and its header) are
|
|
// IRREDUCIBLE.
|
|
w.setBlockType(Block.IRREDUCIBLE);
|
|
nonBack.add(z);
|
|
|
|
} else {
|
|
if (!body.contains(z) && (z != w)) {
|
|
// If we haven't seen z yet, add it to the worklist
|
|
body.add(z);
|
|
worklist.add(z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final Iterator e = body.iterator();
|
|
|
|
// Merge all the blocks in the loop into the UnionFind set
|
|
while (e.hasNext()) {
|
|
final Block x = (Block) e.next();
|
|
final int xn = preOrderIndex(x);
|
|
x.setHeader(w);
|
|
uf.union(xn, wn);
|
|
}
|
|
}
|
|
|
|
// Say all loops containing jsrs or catch blocks are irreducible.
|
|
// This prevents some problems with peeling.
|
|
Iterator e = subroutines.values().iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Subroutine sub = (Subroutine) e.next();
|
|
|
|
final Iterator paths = sub.paths().iterator();
|
|
|
|
while (paths.hasNext()) {
|
|
final Block[] path = (Block[]) paths.next();
|
|
|
|
if (path[0].blockType() != Block.NON_HEADER) {
|
|
path[0].setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
|
|
if (path[1].blockType() != Block.NON_HEADER) {
|
|
path[1].setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
|
|
Block h;
|
|
|
|
h = path[0].header();
|
|
|
|
if (h != null) {
|
|
h.setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
|
|
h = path[1].header();
|
|
|
|
if (h != null) {
|
|
h.setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
e = catchBlocks.iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Block catchBlock = (Block) e.next();
|
|
|
|
if (catchBlock.blockType() != Block.NON_HEADER) {
|
|
catchBlock.setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
|
|
final Block h = catchBlock.header();
|
|
|
|
if (h != null) {
|
|
h.setBlockType(Block.IRREDUCIBLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that no reducible back edge shares a destination with a
|
|
* irreducible back edge by splitting reducible loop headers from
|
|
* irredicible loops. This is based on the fix_loops algorithm in:
|
|
*
|
|
* Paul Havlak, "Nesting of Reducible and Irreducible Loops", TOPLAS, 19(4):
|
|
* 557-567, July 1997.
|
|
*/
|
|
private void splitIrreducibleLoops() {
|
|
db(" Splitting irreducible loops");
|
|
|
|
final List removeEdges = new LinkedList();
|
|
|
|
Iterator iter = nodes().iterator();
|
|
|
|
// Iterate over all the blocks in this cfg. If a block could be
|
|
// the header of a reducible loop (i.e. it is the target of a
|
|
// "reducible backedge", a backedge for which its destination
|
|
// dominates its source), the block is to be split. All
|
|
// "irreducible backedges" are placed in a list and will be used
|
|
// to insert an empty block so that the number of reducible loop
|
|
// headers is maximize.
|
|
while (iter.hasNext()) {
|
|
final Block w = (Block) iter.next();
|
|
|
|
boolean hasReducibleBackIn = false;
|
|
final Set otherIn = new HashSet();
|
|
|
|
final Iterator preds = preds(w).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
|
|
if (w.dominates(v)) {
|
|
// (v,w) is a reducible back edge.
|
|
hasReducibleBackIn = true;
|
|
|
|
} else {
|
|
otherIn.add(v);
|
|
}
|
|
}
|
|
|
|
if (hasReducibleBackIn && (otherIn.size() > 1)) {
|
|
final Iterator e = otherIn.iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Block v = (Block) e.next();
|
|
removeEdges.add(new Block[] { v, w });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Split the irreducible back edges.
|
|
iter = removeEdges.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block[] edge = (Block[]) iter.next();
|
|
splitEdge(edge[0], edge[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that no reducible back edge shares a destination with another
|
|
* reducible back edge by splitting reducible loop headers. This makes loop
|
|
* nesting easier to detect since each loop has a unique header.
|
|
*/
|
|
private void splitReducibleLoops() {
|
|
db(" Splitting reducible loops");
|
|
|
|
final Map reducibleBackIn = new HashMap();
|
|
|
|
final Stack stack = new Stack();
|
|
|
|
final Iterator iter = nodes().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block w = (Block) iter.next();
|
|
|
|
final Set edges = new HashSet(); // reducible back edges
|
|
|
|
final Iterator preds = preds(w).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
|
|
if (w.dominates(v)) {
|
|
// (v,w) is a reducible back edge.
|
|
edges.add(v);
|
|
}
|
|
}
|
|
|
|
// There are strange cases in which a handler block may be the
|
|
// target of a reducible backedge. Ignore it.
|
|
if ((edges.size() > 1) && !handlers.containsKey(w)) {
|
|
stack.push(w);
|
|
reducibleBackIn.put(w, edges);
|
|
}
|
|
}
|
|
|
|
while (!stack.isEmpty()) {
|
|
final Block w = (Block) stack.pop();
|
|
final Set edges = (Set) reducibleBackIn.get(w);
|
|
|
|
// Find the back predecessor with the lowest pre-order index.
|
|
Block min = null;
|
|
|
|
Iterator preds = edges.iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
final int vn = preOrderIndex(v);
|
|
|
|
if ((min == null) || (vn < preOrderIndex(min))) {
|
|
min = v;
|
|
}
|
|
}
|
|
|
|
Assert.isTrue(min != null);
|
|
|
|
Assert.isFalse(handlers.containsKey(w));
|
|
Assert.isFalse(subroutines.containsKey(w));
|
|
|
|
// Split the edge (min, w) from w.
|
|
|
|
// Create a new block immediately before the header.
|
|
final Block newBlock = newBlock();
|
|
|
|
trace.add(trace.indexOf(w), newBlock);
|
|
|
|
final Tree tree = new Tree(newBlock, min.tree().stack());
|
|
newBlock.setTree(tree);
|
|
|
|
tree.addInstruction(new Instruction(Opcode.opcx_goto, w.label()));
|
|
|
|
// If the header is a protected block, the new block must be
|
|
// also since code can be moved from the header up.
|
|
final JumpStmt newJump = (JumpStmt) tree.lastStmt();
|
|
|
|
final Iterator e = handlers.values().iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Handler handler = (Handler) e.next();
|
|
|
|
if (handler.protectedBlocks().contains(w)) {
|
|
Assert.isTrue(succs(w).contains(handler.catchBlock()));
|
|
handler.protectedBlocks().add(newBlock);
|
|
addEdge(newBlock, handler.catchBlock());
|
|
newJump.catchTargets().add(handler.catchBlock());
|
|
}
|
|
}
|
|
|
|
// Change all preds of the header, except min, to have an edge
|
|
// to the new block instead.
|
|
preds = new ImmutableIterator(preds(w));
|
|
|
|
while (preds.hasNext()) {
|
|
final Block v = (Block) preds.next();
|
|
|
|
if (v != min) {
|
|
addEdge(v, newBlock);
|
|
removeEdge(v, w);
|
|
v.visit(new ReplaceTarget(w, newBlock));
|
|
}
|
|
}
|
|
|
|
// Add an edge from the new block to the header.
|
|
addEdge(newBlock, w);
|
|
|
|
// If the new block has more than one back edge, push it on the
|
|
// stack to handle it next.
|
|
edges.remove(min);
|
|
|
|
if (edges.size() > 1) {
|
|
stack.push(newBlock);
|
|
reducibleBackIn.put(newBlock, edges);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loop peeling is a process by which the first iteration of a loop is
|
|
* duplicated in the control flow graph. An code that has side effects (such
|
|
* as throwing an exception) will be tested in the first iteration. This
|
|
* allows us to make assumptions about the code in the second iteration.
|
|
*
|
|
* To detect loop nesting more easily we require that each loop header have
|
|
* at most one incoming back edge.
|
|
*
|
|
* For each loop, peel up to the last exit if: 1. There is more than one
|
|
* exit, or, 2. The last exit has a successor in the loop body (not the
|
|
* header).
|
|
*/
|
|
private void peelLoops(final int level) {
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("Peeling loops");
|
|
System.out.println(" loop tree = " + loopTree);
|
|
}
|
|
|
|
// Find the blocks that have expressions that can throw exceptions
|
|
// and on which we can perform PRE.
|
|
final Set hoistable = new HashSet();
|
|
|
|
visit(new TreeVisitor() {
|
|
public void visitNode(final Node node) {
|
|
if (!hoistable.contains(node.block())) {
|
|
node.visitChildren(this);
|
|
}
|
|
}
|
|
|
|
public void visitCastExpr(final CastExpr expr) {
|
|
if (expr.castType().isReference()) {
|
|
if (expr.expr() instanceof LeafExpr) {
|
|
hoistable.add(expr.block());
|
|
}
|
|
}
|
|
|
|
visitNode(expr);
|
|
}
|
|
|
|
public void visitArithExpr(final ArithExpr expr) {
|
|
if ((expr.operation() == ArithExpr.DIV)
|
|
|| (expr.operation() == ArithExpr.REM)) {
|
|
if (expr.type().isIntegral()
|
|
&& (expr.left() instanceof LeafExpr)
|
|
&& (expr.right() instanceof LeafExpr)) {
|
|
hoistable.add(expr.block());
|
|
}
|
|
}
|
|
|
|
visitNode(expr);
|
|
}
|
|
|
|
public void visitArrayLengthExpr(final ArrayLengthExpr expr) {
|
|
if (expr.array() instanceof LeafExpr) {
|
|
hoistable.add(expr.block());
|
|
}
|
|
|
|
visitNode(expr);
|
|
}
|
|
|
|
public void visitArrayRefExpr(final ArrayRefExpr expr) {
|
|
if ((expr.array() instanceof LeafExpr)
|
|
&& (expr.index() instanceof LeafExpr)) {
|
|
hoistable.add(expr.block());
|
|
}
|
|
|
|
visitNode(expr);
|
|
}
|
|
|
|
public void visitFieldExpr(final FieldExpr expr) {
|
|
if (expr.object() instanceof LeafExpr) {
|
|
hoistable.add(expr.block());
|
|
}
|
|
|
|
visitNode(expr);
|
|
}
|
|
});
|
|
|
|
// For each loop, from the innermost loop out, unroll the loop body
|
|
// up to the last block which exits the loop.
|
|
|
|
// The (pre-order indices of the headers) loops that should be
|
|
// peeled
|
|
final List peel = new ArrayList(loopTree.size());
|
|
|
|
// The header blocks of loops to be peeled
|
|
final List headers = new ArrayList(loopTree.size());
|
|
|
|
// The outer loop of the loops to be peeled (i.e. parent in the
|
|
// loop tree)
|
|
final List outer = new ArrayList(loopTree.size());
|
|
|
|
// All the loops in a tree in post-order
|
|
final List loops = new ArrayList(loopTree.postOrder());
|
|
|
|
for (int i = 0; i < loops.size(); i++) {
|
|
final LoopNode loop = (LoopNode) loops.get(i);
|
|
|
|
// Don't peel irreducible loops or the outermost loop.
|
|
if ((loopTree.preds(loop).size() > 0)
|
|
&& (loop.header.blockType() != Block.IRREDUCIBLE)) {
|
|
|
|
headers.add(loop.header);
|
|
peel.add(new Integer(i));
|
|
|
|
// Find the next outer loop.
|
|
LoopNode outerLoop = null;
|
|
|
|
final Iterator e = loopTree.preds(loop).iterator();
|
|
Assert.isTrue(e.hasNext());
|
|
|
|
outerLoop = (LoopNode) e.next();
|
|
Assert.isTrue(!e.hasNext());
|
|
|
|
final int outerIndex = loops.indexOf(outerLoop);
|
|
Assert.isTrue(outerIndex != -1);
|
|
|
|
outer.add(new Integer(outerIndex));
|
|
}
|
|
}
|
|
|
|
// The level of each loop to be peeled
|
|
final int[] levels = new int[loops.size()];
|
|
|
|
// Replace the integer indicies in loops with the blocks in the
|
|
// loop to be peeled and note the level of each loop
|
|
for (int i = 0; i < loops.size(); i++) {
|
|
final LoopNode loop = (LoopNode) loops.get(i);
|
|
loops.set(i, new ArrayList(loop.elements));
|
|
levels[i] = loop.level;
|
|
maxLoopDepth = (loop.level > maxLoopDepth ? loop.level
|
|
: maxLoopDepth);
|
|
}
|
|
|
|
LOOPS:
|
|
// Examine each loop that is a candidate for peeling. Peel it if
|
|
// we can. If we can't peel it, we might be able to invert it.
|
|
for (int i = 0; i < peel.size(); i++) {
|
|
// Index of loop header
|
|
final Integer loopIndex = (Integer) peel.get(i);
|
|
final Integer outerLoopIndex = (Integer) outer.get(i);
|
|
final Block header = (Block) headers.get(i);
|
|
|
|
final Collection loop = (Collection) loops
|
|
.get(loopIndex.intValue());
|
|
final Collection outerLoop = (Collection) loops.get(outerLoopIndex
|
|
.intValue());
|
|
|
|
// Remove any blocks from the loop that are not in this control
|
|
// flow graph.
|
|
loop.retainAll(nodes());
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println(" loop = " + loop);
|
|
System.out.println(" outer = " + outerLoop);
|
|
}
|
|
|
|
boolean canPeel = false;
|
|
boolean canInvert = false;
|
|
|
|
// If we haven't exceeded the peeling level and the loop
|
|
// contains a block containing an expression that can be
|
|
// hoisted, then we should peel it.
|
|
if (level != FlowGraph.PEEL_NO_LOOPS) {
|
|
if ((level == FlowGraph.PEEL_ALL_LOOPS)
|
|
|| (level >= levels[loopIndex.intValue()])) {
|
|
final Iterator e = loop.iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Block block = (Block) e.next();
|
|
|
|
if (hoistable.contains(block)) {
|
|
canPeel = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we can't peel it, we may still be able to invert it...
|
|
if (!canPeel) {
|
|
boolean hasExitSucc = false;
|
|
boolean hasLoopSucc = false;
|
|
|
|
final Iterator succs = succs(header).iterator();
|
|
|
|
while (succs.hasNext()) {
|
|
final Block succ = (Block) succs.next();
|
|
|
|
if (!loop.contains(succ)) {
|
|
hasExitSucc = true;
|
|
} else if (succ != header) {
|
|
hasLoopSucc = true;
|
|
}
|
|
}
|
|
|
|
// If the loop header has an edge to a block that is not in
|
|
// the loop, then it can be inverted.
|
|
canInvert = hasExitSucc && hasLoopSucc;
|
|
}
|
|
|
|
// The blocks in the loop that are to be copied
|
|
final Set copySet = new HashSet();
|
|
|
|
if (canPeel) {
|
|
// Find the blocks which have exits outside the loop.
|
|
final Set exits = new HashSet();
|
|
|
|
// All blocks in the loop that may throw exceptions have an
|
|
// edge to outside the loop.
|
|
exits.addAll(hoistable);
|
|
exits.retainAll(loop);
|
|
|
|
Iterator e = loop.iterator();
|
|
|
|
BLOCKS: while (e.hasNext()) {
|
|
final Block block = (Block) e.next();
|
|
|
|
final Iterator succs = succs(block).iterator();
|
|
|
|
while (succs.hasNext()) {
|
|
final Block succ = (Block) succs.next();
|
|
|
|
if (!loop.contains(succ)) {
|
|
// If the successor of one of the blocks in the loop
|
|
// is
|
|
// itself not in the loop, then it is an exit block.
|
|
exits.add(block);
|
|
continue BLOCKS;
|
|
}
|
|
}
|
|
}
|
|
|
|
final ArrayList stack = new ArrayList(exits);
|
|
|
|
e = exits.iterator();
|
|
|
|
// Add all "exit" blocks to the copy of the loop
|
|
while (e.hasNext()) {
|
|
final Block block = (Block) e.next();
|
|
copySet.add(block);
|
|
stack.add(block);
|
|
}
|
|
|
|
// Copy all reachable blocks into the copy of the loop. Start
|
|
// with the exit blocks and work upwards.
|
|
while (!stack.isEmpty()) {
|
|
final Block block = (Block) stack.remove(stack.size() - 1);
|
|
|
|
final Iterator preds = preds(block).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block pred = (Block) preds.next();
|
|
|
|
if (!copySet.contains(pred)) {
|
|
copySet.add(pred);
|
|
stack.add(pred);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (canInvert) {
|
|
// If all we're doing is inverting, just copy the loop header.
|
|
copySet.add(header);
|
|
|
|
} else {
|
|
// If we can't invert or peel the loop, copy all the blocks
|
|
// to the outer loop and go to the next loop.
|
|
if (outerLoop != null) {
|
|
outerLoop.addAll(loop);
|
|
}
|
|
|
|
// Consider the next loop to be peeled
|
|
continue LOOPS;
|
|
}
|
|
|
|
// Maintain a mapping between a block in the loop and its copy
|
|
final Map copies = new HashMap();
|
|
|
|
Iterator e = copySet.iterator();
|
|
|
|
// Go throught the blocks in the copy set and create a copy of
|
|
// each of them using copyBlock(). Make sure there are no
|
|
// duplicates.
|
|
while (e.hasNext()) {
|
|
final Block block = (Block) e.next();
|
|
|
|
// Jeez, are we dealing with a finally block?
|
|
if (FlowGraph.DEBUG) {
|
|
final Stmt jump = block.tree().lastStmt();
|
|
|
|
if (jump instanceof JsrStmt) {
|
|
final JsrStmt jsr = (JsrStmt) jump;
|
|
Assert.isTrue(copySet.contains(jsr.follow()));
|
|
Assert.isTrue(copySet.contains(jsr.sub().entry()));
|
|
}
|
|
}
|
|
|
|
if (loop.contains(block)) {
|
|
Block copy = (Block) copies.get(block);
|
|
|
|
if (copy == null) {
|
|
copy = copyBlock(block);
|
|
copies.put(block, copy);
|
|
}
|
|
|
|
// Add the copy to the list of hositable blocks
|
|
if (hoistable.contains(block)) {
|
|
hoistable.add(copy);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println(" copy = " + copies);
|
|
}
|
|
|
|
int copyIndex = -1;
|
|
|
|
e = preds(header).iterator();
|
|
|
|
// Determine the index into the trace to add the copy of the
|
|
// loop. Place the loop after the header's "latest" predacessor
|
|
// in the trace.
|
|
while (e.hasNext()) {
|
|
final Block pred = (Block) e.next();
|
|
|
|
if (!header.dominates(pred)) {
|
|
final int index = trace.indexOf(pred);
|
|
|
|
if (copyIndex <= index) {
|
|
copyIndex = index + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (copyIndex < 0) {
|
|
copyIndex = trace.indexOf(header);
|
|
}
|
|
|
|
// Insert the copies into the trace just above the loop.
|
|
final List copyTrace = new ResizeableArrayList(copies.size());
|
|
|
|
e = trace.iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Block block = (Block) e.next();
|
|
final Block copy = (Block) copies.get(block);
|
|
|
|
if (copy != null) {
|
|
copyTrace.add(copy);
|
|
}
|
|
}
|
|
|
|
// Add copy of loop to trace
|
|
trace.addAll(copyIndex, copyTrace);
|
|
|
|
// Edges to add to the control flow graph
|
|
final List addEdges = new LinkedList();
|
|
|
|
// Edges to remove from the control flow graph
|
|
final List removeEdges = new LinkedList();
|
|
|
|
// Fix up the edges for the block copies.
|
|
|
|
// Add the edges within the peeled body and from the peeled body
|
|
// to the original body.
|
|
e = copies.entrySet().iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Map.Entry pair = (Map.Entry) e.next();
|
|
|
|
final Block block = (Block) pair.getKey();
|
|
final Block copy = (Block) pair.getValue();
|
|
|
|
final Iterator h = handlers.values().iterator();
|
|
|
|
// The copy of the a protected block is also protected
|
|
while (h.hasNext()) {
|
|
final Handler handler = (Handler) h.next();
|
|
|
|
if (handler.protectedBlocks().contains(block)) {
|
|
handler.protectedBlocks().add(copy);
|
|
}
|
|
}
|
|
|
|
final Iterator succs = succs(block).iterator();
|
|
|
|
// Make a list of edges to add to the control flow graph.
|
|
// Create edges within the copied loop so that it looks like
|
|
// the original loop.
|
|
while (succs.hasNext()) {
|
|
final Block succ = (Block) succs.next();
|
|
final Block succCopy = (Block) copies.get(succ);
|
|
|
|
if ((succ != header) && (succCopy != null)) {
|
|
addEdges.add(new Block[] { copy, succCopy });
|
|
copy.visit(new ReplaceTarget(succ, succCopy));
|
|
} else {
|
|
addEdges.add(new Block[] { copy, succ });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the edges from outside the loop to the peeled body.
|
|
// Remove the edges from outside the loop to the original body.
|
|
e = copies.entrySet().iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Map.Entry pair = (Map.Entry) e.next();
|
|
|
|
final Block block = (Block) pair.getKey();
|
|
final Block copy = (Block) pair.getValue();
|
|
|
|
final Iterator preds = preds(block).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block pred = (Block) preds.next();
|
|
|
|
if (!loop.contains(pred)) {
|
|
addEdges.add(new Block[] { pred, copy });
|
|
removeEdges.add(new Block[] { pred, block });
|
|
pred.visit(new ReplaceTarget(block, copy));
|
|
}
|
|
}
|
|
}
|
|
|
|
e = addEdges.iterator();
|
|
|
|
// Add edges to the control flow graph
|
|
while (e.hasNext()) {
|
|
final Block[] edge = (Block[]) e.next();
|
|
addEdge(edge[0], edge[1]);
|
|
}
|
|
|
|
e = removeEdges.iterator();
|
|
|
|
// Remove edges into the original (non-copied) loop
|
|
while (e.hasNext()) {
|
|
final Block[] edge = (Block[]) e.next();
|
|
final Block v = edge[0];
|
|
final Block w = edge[1];
|
|
|
|
if (hasNode(v) && hasNode(w) && hasEdge(v, w)) {
|
|
removeEdge(v, w);
|
|
}
|
|
}
|
|
|
|
// Copy all the blocks to the outer loop.
|
|
if (outerLoop != null) {
|
|
outerLoop.addAll(copies.values());
|
|
outerLoop.addAll(loop);
|
|
}
|
|
}
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("Begin after peeling:");
|
|
System.out.println(this);
|
|
System.out.println("End after peeling");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a copy of a block including its expression tree.
|
|
*/
|
|
private Block copyBlock(final Block block) {
|
|
final Block copy = newBlock();
|
|
|
|
// Copy the stack from the end of the old block.
|
|
// But don't change it when instructions are added.
|
|
|
|
final Tree tree = new Tree(copy, block.tree().stack());
|
|
copy.setTree(tree);
|
|
|
|
// Fill the tree.
|
|
final Iterator stmts = block.tree().stmts().iterator();
|
|
|
|
while (stmts.hasNext()) {
|
|
final Stmt stmt = (Stmt) stmts.next();
|
|
|
|
if (stmt instanceof LabelStmt) {
|
|
continue;
|
|
}
|
|
|
|
tree.addStmt((Stmt) stmt.clone());
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* Returns the <tt>Subroutine</tt> whose entry block is labeled by a given
|
|
* <tt>Label</tt>.
|
|
*/
|
|
public Subroutine labelSub(final Label label) {
|
|
return (Subroutine) subroutines.get(getNode(label));
|
|
}
|
|
|
|
/**
|
|
* Set the entry in the mapping between subroutine entry <tt>Block</tt>s
|
|
* and the <tt>Subroutine</tt>s that they begin. It also sets the
|
|
* <tt>Subroutine</tt>'s entry block.
|
|
*
|
|
* @param sub
|
|
* The subroutine whose entry block is being set.
|
|
* @param entry
|
|
* The subroutine's entry Block.
|
|
*
|
|
* @see Subroutine#setEntry
|
|
*/
|
|
void setSubEntry(final Subroutine sub, final Block entry) {
|
|
if (sub.entry() != null) {
|
|
subroutines.remove(sub.entry());
|
|
}
|
|
|
|
sub.setEntry(entry);
|
|
subroutines.put(entry, sub);
|
|
}
|
|
|
|
/**
|
|
* Returns all of the <tt>Subroutine</tt>s in the method modeled by this
|
|
* <tt>FlowGraph</tt>.
|
|
*/
|
|
public Collection subroutines() {
|
|
return subroutines.values();
|
|
}
|
|
|
|
int file = 0;
|
|
|
|
public void print(final PrintStream out) {
|
|
print(new PrintWriter(out, true));
|
|
}
|
|
|
|
/**
|
|
* Prints the graph.
|
|
*
|
|
* @param out
|
|
* The writer to which to print.
|
|
*/
|
|
public void print(final PrintWriter out) {
|
|
final String dateString = java.text.DateFormat.getDateInstance()
|
|
.format(new Date());
|
|
out.println("Print " + ++file + " at " + dateString + " "
|
|
+ method.type() + " " + method.name() + ":");
|
|
|
|
visit(new PrintVisitor(out));
|
|
|
|
if (FlowGraph.PRINT_GRAPH) {
|
|
printGraph();
|
|
}
|
|
}
|
|
|
|
int next = 1;
|
|
|
|
public void printGraph() {
|
|
try {
|
|
final PrintStream out = new PrintStream(new FileOutputStream(method
|
|
.name()
|
|
+ "." + next++ + ".dot"));
|
|
printGraph(out);
|
|
|
|
} catch (final IOException e) {
|
|
}
|
|
|
|
}
|
|
|
|
public void print() {
|
|
try {
|
|
final PrintStream out = new PrintStream(new FileOutputStream(method
|
|
.name()
|
|
+ "." + next++ + ".cfg"));
|
|
print(out);
|
|
|
|
} catch (final IOException e) {
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates a graphical description of the CFG in the dot language. The name
|
|
* of the generated file is the name of the method modeled by this CFG
|
|
* followed by a number and the ".dot" postfix. For more information about
|
|
* dot and tools that use it see:
|
|
* <p align=center>
|
|
* http://www.research.att.com/sw/tools/graphviz/
|
|
*/
|
|
public void printGraph(final PrintStream out) {
|
|
printGraph(new PrintWriter(out, true));
|
|
}
|
|
|
|
public void printGraph(final PrintWriter out) {
|
|
printGraph(out, "cfg");
|
|
}
|
|
|
|
public void printGraph(final PrintWriter out, final String name) {
|
|
out.println("digraph " + name + " {");
|
|
out.println(" fontsize=8;");
|
|
out.println(" ordering=out;");
|
|
out.println(" center=1;");
|
|
|
|
visit(new PrintVisitor(out) {
|
|
public void println() {
|
|
super.print("\\n");
|
|
}
|
|
|
|
public void println(final Object obj) {
|
|
super.print(obj);
|
|
super.print("\\n");
|
|
}
|
|
|
|
public void visitBlock(final Block block) {
|
|
super
|
|
.print(" "
|
|
+ block.label()
|
|
+ " [shape=box,fontname=\"Courier\",fontsize=6,label=\"");
|
|
block.visitChildren(this);
|
|
super.print("\"];\n");
|
|
|
|
final Iterator succs = succs(block).iterator();
|
|
|
|
while (succs.hasNext()) {
|
|
final Block succ = (Block) succs.next();
|
|
|
|
super.print(" " + block.label() + " -> " + succ.label());
|
|
|
|
if (handlers.containsKey(succ)) {
|
|
super.print(" [style=dotted];\n");
|
|
|
|
} else {
|
|
super.print(" [style=solid];\n");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
out.println(" page=\"8.5,11\";");
|
|
out.println("}");
|
|
out.close();
|
|
}
|
|
|
|
/**
|
|
* Visit each node (block) in this CFG in pre-order.
|
|
*/
|
|
public void visitChildren(final TreeVisitor visitor) {
|
|
final List list = preOrder();
|
|
|
|
if (!visitor.reverse()) {
|
|
final ListIterator iter = list.listIterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block block = (Block) iter.next();
|
|
block.visit(visitor);
|
|
}
|
|
|
|
} else {
|
|
final ListIterator iter = list.listIterator(list.size());
|
|
|
|
while (iter.hasPrevious()) {
|
|
final Block block = (Block) iter.previous();
|
|
block.visit(visitor);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void visit(final TreeVisitor visitor) {
|
|
visitor.visitFlowGraph(this);
|
|
}
|
|
|
|
/**
|
|
* Returns the method editor for the method modeled by this graph.
|
|
*/
|
|
public MethodEditor method() {
|
|
return method;
|
|
}
|
|
|
|
/**
|
|
* Removes the critical edges (edges from a block with more than one
|
|
* successor to a block with more than one predecessor) from the graph.
|
|
* Critical edges can screw up code motion.
|
|
* <p>
|
|
* For code generation, the block must be inserted after the predecessor if
|
|
* the successor is the default target. Throw successors and predecessors
|
|
* are copied from the successor block.
|
|
*/
|
|
private void removeCriticalEdges() {
|
|
// The critical edges
|
|
final List edges = new LinkedList();
|
|
|
|
final Iterator blocks = nodes().iterator();
|
|
|
|
// Examine each block in this CFG. Blocks in subroutines,
|
|
// exception handlers, blocks with one or fewer predacessors, and
|
|
// the sink block are ignored. For all other blocks, dst, their
|
|
// predacessors are examined. If the predacessor, src, has more
|
|
// than one sucessor, then the edge from src to dst is a critical
|
|
// edge. A List of critical egdes is maintained.
|
|
while (blocks.hasNext()) {
|
|
final Block dst = (Block) blocks.next();
|
|
|
|
// Skip edges to subroutine entries.
|
|
if (subroutines.containsKey(dst)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip edges from protected blocks to handlers.
|
|
if (handlers.containsKey(dst)) {
|
|
continue;
|
|
}
|
|
|
|
if (preds(dst).size() <= 1) {
|
|
continue;
|
|
}
|
|
|
|
if (dst == snkBlock) {
|
|
continue;
|
|
}
|
|
|
|
final Iterator preds = preds(dst).iterator();
|
|
|
|
while (preds.hasNext()) {
|
|
final Block src = (Block) preds.next();
|
|
|
|
if (succs(src).size() <= 1) {
|
|
continue;
|
|
}
|
|
|
|
// The edge src->dst is a critical edge. Plop a new
|
|
// block on the edge.
|
|
|
|
edges.add(new Block[] { src, dst });
|
|
}
|
|
}
|
|
|
|
final Iterator e = edges.iterator();
|
|
|
|
// Remove the critical edges from this CFG. Call splitEdge to add
|
|
// a block between the source and destination blocks of the
|
|
// critical edges so that it is no longer critical.
|
|
while (e.hasNext()) {
|
|
final Block[] edge = (Block[]) e.next();
|
|
final Block v = edge[0];
|
|
final Block w = edge[1];
|
|
|
|
if (hasEdge(v, w)) {
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("removing critical edge from " + v
|
|
+ " to " + w);
|
|
}
|
|
|
|
splitEdge(v, w);
|
|
|
|
Assert.isFalse(hasEdge(v, w));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Splits an edge by inserting an new block between a source and a
|
|
* destination block. The new block consists of a goto to the destination
|
|
* block. However, later optimizations may move code from the destination
|
|
* block into the new block. Thus, if the destination block is a proteced
|
|
* block, the new block must also be a protected block.
|
|
*/
|
|
private void splitEdge(final Block src, final Block dst) {
|
|
// This shouldn't happen since
|
|
// (1) edges from the source are either source->init, or
|
|
// source->catch. Edges with catch blocks are already split.
|
|
// (2) edges to the sink are always unconditional jumps.
|
|
//
|
|
Assert.isFalse((src == srcBlock) || (dst == snkBlock),
|
|
"Can't split an edge from the source or to the sink");
|
|
|
|
// Don't split exception edges
|
|
if (handlers.containsKey(dst)) {
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("not removing exception edge " + src
|
|
+ " -> " + dst);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
final Block newBlock = newBlock();
|
|
|
|
// Insert in the trace before the dst.
|
|
trace.add(trace.indexOf(dst), newBlock);
|
|
|
|
final Tree tree = new Tree(newBlock, src.tree().stack());
|
|
newBlock.setTree(tree);
|
|
|
|
tree.addInstruction(new Instruction(Opcode.opcx_goto, dst.label()));
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("add edge " + src + " -> " + newBlock);
|
|
System.out.println("add edge " + newBlock + " -> " + dst);
|
|
System.out.println("remove edge " + src + " -> " + dst);
|
|
}
|
|
|
|
src.visit(new ReplaceTarget(dst, newBlock));
|
|
|
|
addEdge(src, newBlock);
|
|
addEdge(newBlock, dst);
|
|
removeEdge(src, dst);
|
|
|
|
Assert.isTrue(hasEdge(src, newBlock));
|
|
Assert.isTrue(hasEdge(newBlock, dst));
|
|
Assert.isFalse(hasEdge(src, dst));
|
|
|
|
// If the dst is a protected block, the new block must be
|
|
// also since code can be moved from the dst up.
|
|
final JumpStmt newJump = (JumpStmt) newBlock.tree().lastStmt();
|
|
|
|
final Iterator e = handlers.values().iterator();
|
|
|
|
while (e.hasNext()) {
|
|
final Handler handler = (Handler) e.next();
|
|
|
|
if (handler.protectedBlocks().contains(dst)) {
|
|
Assert.isTrue(succs(dst).contains(handler.catchBlock()));
|
|
handler.protectedBlocks().add(newBlock);
|
|
addEdge(newBlock, handler.catchBlock());
|
|
newJump.catchTargets().add(handler.catchBlock());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds any blocks in the CFG that are both the entry block of a subroutine
|
|
* and the return target of (another) subroutine.
|
|
* <p>
|
|
* The Subroutine's in the cfg are examined. If a block is encountered that
|
|
* is both a subroutine entry block and the target of a subroutine return,
|
|
* then we have to make two new blocks: a new target block and a new entry
|
|
* block. The edges have to be adjusted accordingly.
|
|
*/
|
|
private void splitPhiBlocks() {
|
|
// Make sure a block is not more than one of: a catch block,
|
|
// a sub entry block, a sub return target.
|
|
// Otherwise, more than one SSA phi could be placed at the block.
|
|
//
|
|
// Since catch blocks and return targets are mutually exclusive
|
|
// and since catch blocks and sub entries are mutually exclusive,
|
|
// we need only check if the block is both an entry block and a
|
|
// return target. Actually, I don't think this can possibly
|
|
// happen, but do it just to be sure.
|
|
//
|
|
// Note that a phi can also be placed at the block if it has
|
|
// more than one predecessor, but this condition and the others
|
|
// are mutually exclusive since catch blocks and sub entries have
|
|
// only the single source predecessor and return targets have
|
|
// only the caller block as its predecessor.
|
|
//
|
|
final Iterator entries = subroutines.values().iterator();
|
|
|
|
ENTRIES: while (entries.hasNext()) {
|
|
final Subroutine entrySub = (Subroutine) entries.next();
|
|
|
|
// An entry block of a subroutine
|
|
final Block block = entrySub.entry();
|
|
|
|
Subroutine returnSub = null;
|
|
|
|
// A block that calls a subroutine that is followed by a block
|
|
// (the return target of the subroutine) that also starts a
|
|
// subroutine.
|
|
Block returnSubCaller = null;
|
|
|
|
final Iterator returns = subroutines.values().iterator();
|
|
|
|
RETURNS: while (returns.hasNext()) {
|
|
returnSub = (Subroutine) returns.next();
|
|
|
|
if (returnSub == entrySub) {
|
|
continue;
|
|
}
|
|
|
|
final Iterator paths = returnSub.paths().iterator();
|
|
|
|
while (paths.hasNext()) {
|
|
final Block[] path = (Block[]) paths.next();
|
|
|
|
// If the block to which returnSub returns is also the entry
|
|
// block of entrySub, then note the caller of returnSub as
|
|
// the
|
|
// returnSubCaller.
|
|
if (block == path[1]) {
|
|
returnSubCaller = path[0];
|
|
break RETURNS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (returnSubCaller == null) {
|
|
continue ENTRIES;
|
|
}
|
|
|
|
if (FlowGraph.DEBUG) {
|
|
System.out.println("" + block
|
|
+ " is both an entry and a return target");
|
|
}
|
|
|
|
// Create new blocks to be the new sub entry block and the new
|
|
// return target.
|
|
//
|
|
// Use the returning subroutine's exit block to get the state
|
|
// of the operand stack.
|
|
//
|
|
final int traceIndex = trace.indexOf(block);
|
|
|
|
Tree tree;
|
|
|
|
final Block newEntry = newBlock();
|
|
|
|
// Insert in the trace before the block.
|
|
trace.add(traceIndex, newEntry);
|
|
|
|
tree = new Tree(newEntry, returnSub.exit().tree().stack());
|
|
newEntry.setTree(tree);
|
|
|
|
tree
|
|
.addInstruction(new Instruction(Opcode.opcx_goto, block
|
|
.label()));
|
|
|
|
addEdge(newEntry, block);
|
|
|
|
final Iterator paths = entrySub.paths().iterator();
|
|
|
|
while (paths.hasNext()) {
|
|
final Block[] path = (Block[]) paths.next();
|
|
removeEdge(path[0], block);
|
|
addEdge(path[0], newEntry);
|
|
path[0].visit(new ReplaceTarget(block, newEntry));
|
|
}
|
|
|
|
setSubEntry(entrySub, newEntry);
|
|
|
|
final Block newTarget = newBlock();
|
|
|
|
// Insert in the trace immediately after the jsr block.
|
|
trace.add(traceIndex, newTarget);
|
|
|
|
tree = new Tree(newTarget, returnSub.exit().tree().stack());
|
|
newTarget.setTree(tree);
|
|
|
|
tree
|
|
.addInstruction(new Instruction(Opcode.opcx_goto, block
|
|
.label()));
|
|
|
|
returnSub.exit().visit(new ReplaceTarget(block, newTarget));
|
|
((JsrStmt) returnSubCaller.tree().lastStmt()).setFollow(newTarget);
|
|
|
|
addEdge(newTarget, block);
|
|
addEdge(returnSub.exit(), newTarget);
|
|
removeEdge(returnSub.exit(), block);
|
|
|
|
final JumpStmt entryJump = (JumpStmt) newEntry.tree().lastStmt();
|
|
final JumpStmt targetJump = (JumpStmt) newTarget.tree().lastStmt();
|
|
|
|
final Iterator e = handlers.values().iterator();
|
|
|
|
// If block itself is a protected block (man, this block has
|
|
// problems), add egdes from the newEntry and newTarget blocks
|
|
// to the handlers for block.
|
|
while (e.hasNext()) {
|
|
final Handler handler = (Handler) e.next();
|
|
|
|
if (handler.protectedBlocks().contains(block)) {
|
|
Assert.isTrue(succs(block).contains(handler.catchBlock()));
|
|
|
|
handler.protectedBlocks().add(newEntry);
|
|
addEdge(newEntry, handler.catchBlock());
|
|
entryJump.catchTargets().add(handler.catchBlock());
|
|
|
|
handler.protectedBlocks().add(newTarget);
|
|
addEdge(newTarget, handler.catchBlock());
|
|
targetJump.catchTargets().add(handler.catchBlock());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the expressions trees for the "special" blocks (source, sink, and
|
|
* init blocks). Empty expressions trees are built for the source and sink
|
|
* blocks. The init block's expression tree contains code that initializes
|
|
* the method's parameters (represented as local variables).
|
|
* <p>
|
|
*/
|
|
private void buildSpecialTrees(final Map catchBodies, final Map labelPos) {
|
|
Tree tree;
|
|
|
|
tree = new Tree(srcBlock, new OperandStack());
|
|
srcBlock.setTree(tree);
|
|
|
|
tree = new Tree(snkBlock, new OperandStack());
|
|
snkBlock.setTree(tree);
|
|
|
|
tree = new Tree(iniBlock, new OperandStack());
|
|
iniBlock.setTree(tree);
|
|
|
|
if (method.codeLength() > 0) {
|
|
tree.initLocals(methodParams(method));
|
|
tree.addInstruction(new Instruction(Opcode.opcx_goto, method
|
|
.firstBlock()));
|
|
|
|
// (pr)
|
|
if (catchBodies != null) {
|
|
addHandlerEdges(iniBlock, catchBodies, labelPos, null,
|
|
new HashSet());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a block may throws an exception (i.e. it is in a protected region),
|
|
* there must be an edge in the control flow graph from that block to the
|
|
* block that begins the exception handler. This method adds that edge.
|
|
* <p>
|
|
* We iterate over all of the Handler objects created for this FlowGraph. If
|
|
* the block of interest lies in the protected region of the Handler, make
|
|
* note of this fact and add an edge between the block and the first block
|
|
* of the exception handler. Generate the expression tree for the exception
|
|
* handler, if necessary.
|
|
* <p>
|
|
* Recursively call addHandlerEdges for the exception handler to accomodate
|
|
* exception handlers within exception handlers.
|
|
*
|
|
* @param block
|
|
* The "block of interest" (i.e. may throw an exception)
|
|
* @param catchBodies
|
|
* Maps "catch blocks" (first block of exception handler) to
|
|
* "catch bodies" (block that begins the actual work of the
|
|
* exception handler).
|
|
* @param labelPos
|
|
* Maps Labels to their offset in the code (needed for
|
|
* buildTreeForBlock)
|
|
* @param sub
|
|
* The current Subroutine we're in (needed for
|
|
* buildTreeForBlock).
|
|
*/
|
|
private void addHandlerEdges(final Block block, final Map catchBodies,
|
|
final Map labelPos, final Subroutine sub, final Set visited) {
|
|
// (pr)
|
|
if (visited.contains(block)) {
|
|
return;
|
|
}
|
|
visited.add(block);
|
|
|
|
final Tree tree = block.tree();
|
|
|
|
Assert.isTrue(tree != null);
|
|
|
|
final Iterator hiter = handlers.values().iterator();
|
|
|
|
// Iterate over every Handler object created for this FlowGraph
|
|
while (hiter.hasNext()) {
|
|
final Handler handler = (Handler) hiter.next();
|
|
|
|
boolean prot = false;
|
|
|
|
// Determine whether or not the block of interest lies within
|
|
// the Handler's protected region
|
|
if (handler.protectedBlocks().contains(block)) {
|
|
prot = true;
|
|
|
|
} else {
|
|
final Iterator succs = succs(block).iterator();
|
|
|
|
while (succs.hasNext()) {
|
|
final Block succ = (Block) succs.next();
|
|
|
|
if (handler.protectedBlocks().contains(succ)) {
|
|
prot = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the block of interest lies in a protected region, add an
|
|
// edge in this CFG from the block to the Handler's "catch block"
|
|
// (i.e. first block in Handler). Also examine the JumpStmt that
|
|
// ends the block of interest and add the catch block to its list
|
|
// of catch targets.
|
|
//
|
|
// Note that we do not want the init block to be protected.
|
|
// This may happen if the first block in the CFG is protected.
|
|
//
|
|
// Next, obtain the "catch body" block (contains the real code)
|
|
// of the method. If no expression tree has been constructed for
|
|
// it, create a new OperandStack containing only the exception
|
|
// object and build a new tree for it.
|
|
//
|
|
// Finally, recursively add the handler edges for the first block
|
|
// of the exception handler.
|
|
if (prot) { // && block != iniBlock) {
|
|
final Block catchBlock = handler.catchBlock();
|
|
|
|
final JumpStmt jump = (JumpStmt) tree.lastStmt();
|
|
|
|
jump.catchTargets().add(catchBlock);
|
|
addEdge(block, catchBlock);
|
|
|
|
// Build the tree for the exception handler body.
|
|
// We must have already added the edge from the catch block
|
|
// to the catch body.
|
|
|
|
final Block catchBody = (Block) catchBodies.get(catchBlock);
|
|
Assert.isTrue(catchBody != null);
|
|
|
|
if (catchBody.tree() == null) {
|
|
final OperandStack s = new OperandStack();
|
|
s.push(new StackExpr(0, Type.THROWABLE));
|
|
|
|
buildTreeForBlock(catchBody, s, sub, labelPos, catchBodies);
|
|
}
|
|
// (pr)
|
|
// if(!handler.catchBlock.equals(block)) {
|
|
addHandlerEdges(catchBlock, catchBodies, labelPos, sub, visited);
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dave sez: Builds the expression tree for a basic block and all blocks
|
|
* reachable from that block. Basically, the block's code (Instructions and
|
|
* Labels) are iterated over. The instructions are added to the tree with
|
|
* calls to Tree#addInstruction. If an instruction invovles a change of
|
|
* control flow (e.g. jsr, jump, switch), add an edge in the control flow
|
|
* graph between the appropriate blocks. After all that is done, call
|
|
* addHandlerEdges to add edges between blocks that may throw exceptions and
|
|
* the blocks that handle those exceptions
|
|
*
|
|
* Nate sez: Visit a block other than source or catch. Since blocks are
|
|
* visited depth-first, one predecessor was already visited, get the operand
|
|
* stack state at the end of the predecessor block and use it as the initial
|
|
* operand stack state for this block. We assume the class file passed
|
|
* verification so which predecessor used shouldn't matter.
|
|
*
|
|
* @param block
|
|
* The block for which to generate an expression tree.
|
|
* @param stack
|
|
* The operand stack before the block is executed.
|
|
* @param sub
|
|
* The current Subroutine.
|
|
* @param labelPos
|
|
* A mapping between Labels and their offset into the code
|
|
* @param catchBodies
|
|
* A mapping between "catch blocks" and "catch bodies"
|
|
*/
|
|
private void buildTreeForBlock(final Block block, final OperandStack stack,
|
|
final Subroutine sub, final Map labelPos, final Map catchBodies) {
|
|
if (block.tree() != null) {
|
|
return;
|
|
}
|
|
|
|
final Tree tree = new Tree(block, stack);
|
|
block.setTree(tree);
|
|
|
|
final Integer start = (Integer) labelPos.get(block.label());
|
|
Integer targetStart;
|
|
|
|
final ListIterator iter = method.code().listIterator(
|
|
start.intValue() + 1);
|
|
|
|
CODE:
|
|
// Iterate over the code in the method...
|
|
while (iter.hasNext()) {
|
|
final Object ce = iter.next();
|
|
|
|
if (ce instanceof Instruction) {
|
|
final Instruction inst = (Instruction) ce;
|
|
|
|
Block target; // The target of a jump
|
|
Block next = null; // The Block following a jump
|
|
|
|
// For jump instructions, look for the next Block
|
|
if (inst.isJsr() || inst.isConditionalJump()) {
|
|
int save = 0;
|
|
|
|
while (iter.hasNext()) {
|
|
final Object obj = iter.next();
|
|
save++;
|
|
|
|
if (obj instanceof Label) {
|
|
if (((Label) obj).startsBlock()) {
|
|
next = (Block) getNode(obj);
|
|
|
|
while (save-- > 0) {
|
|
iter.previous();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
throw new RuntimeException(inst
|
|
+ " not followed by a label: " + obj + " ("
|
|
+ obj.getClass() + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (inst.opcodeClass() == Opcode.opcx_astore) {
|
|
// We need the current subroutine in case this is a
|
|
// returnAdress store.
|
|
tree.addInstruction(inst, sub);
|
|
|
|
} else if (inst.isRet()) {
|
|
sub.setExit(block);
|
|
tree.addInstruction(inst, sub);
|
|
|
|
final Iterator paths = sub.paths().iterator();
|
|
|
|
// Add edges from the exit Block of the Subroutine to the
|
|
// Block that begins with the Subroutine's return address
|
|
while (paths.hasNext()) {
|
|
final Block[] path = (Block[]) paths.next();
|
|
addEdge(block, path[1]);
|
|
}
|
|
|
|
break CODE;
|
|
|
|
} else if (inst.isThrow() || inst.isReturn()) {
|
|
tree.addInstruction(inst);
|
|
addEdge(block, snkBlock);
|
|
break CODE;
|
|
|
|
} else if (inst.isJsr()) {
|
|
Assert.isTrue(next != null, inst
|
|
+ " not followed by a block");
|
|
|
|
tree.addInstruction(inst, next);
|
|
|
|
final Label label = (Label) inst.operand();
|
|
|
|
target = (Block) getNode(label);
|
|
Assert.isTrue(target != null, inst + " target not found");
|
|
|
|
final Subroutine nextSub = labelSub(label);
|
|
setSubEntry(nextSub, target);
|
|
|
|
buildTreeForBlock(target, tree.stack(), nextSub, labelPos,
|
|
catchBodies);
|
|
addEdge(block, target);
|
|
|
|
if (nextSub.exit() != null) {
|
|
buildTreeForBlock(next, nextSub.exit().tree().stack(),
|
|
sub, labelPos, catchBodies);
|
|
addEdge(nextSub.exit(), next);
|
|
}
|
|
|
|
break CODE;
|
|
|
|
} else if (inst.isGoto()) {
|
|
tree.addInstruction(inst);
|
|
|
|
final Label label = (Label) inst.operand();
|
|
|
|
target = (Block) getNode(label);
|
|
Assert.isTrue(target != null, inst + " target not found");
|
|
|
|
addEdge(block, target);
|
|
|
|
buildTreeForBlock(target, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
|
|
break CODE;
|
|
|
|
} else if (inst.isConditionalJump()) {
|
|
Assert.isTrue(next != null, inst
|
|
+ " not followed by a block");
|
|
|
|
tree.addInstruction(inst, next);
|
|
|
|
final Label label = (Label) inst.operand();
|
|
|
|
target = (Block) getNode(label);
|
|
Assert.isTrue(target != null, inst + " target not found");
|
|
|
|
addEdge(block, target);
|
|
buildTreeForBlock(target, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
|
|
addEdge(block, next);
|
|
buildTreeForBlock(next, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
|
|
break CODE;
|
|
|
|
} else if (inst.isSwitch()) {
|
|
tree.addInstruction(inst);
|
|
|
|
final Switch sw = (Switch) inst.operand();
|
|
|
|
target = (Block) getNode(sw.defaultTarget());
|
|
|
|
addEdge(block, target);
|
|
|
|
buildTreeForBlock(target, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
|
|
for (int j = 0; j < sw.targets().length; j++) {
|
|
target = (Block) getNode(sw.targets()[j]);
|
|
|
|
addEdge(block, target);
|
|
|
|
targetStart = (Integer) labelPos.get(target.label());
|
|
|
|
buildTreeForBlock(target, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
}
|
|
|
|
break CODE;
|
|
|
|
} else {
|
|
tree.addInstruction(inst);
|
|
}
|
|
|
|
} else if (ce instanceof Label) {
|
|
final Label label = (Label) ce;
|
|
|
|
if (label.startsBlock()) {
|
|
tree
|
|
.addInstruction(new Instruction(Opcode.opcx_goto,
|
|
label));
|
|
|
|
final Block next = (Block) getNode(label);
|
|
|
|
Assert.isTrue(next != null, "Block for " + label
|
|
+ " not found");
|
|
|
|
addEdge(block, next);
|
|
buildTreeForBlock(next, tree.stack(), sub, labelPos,
|
|
catchBodies);
|
|
break CODE;
|
|
}
|
|
|
|
tree.addLabel(label);
|
|
}
|
|
}
|
|
|
|
addHandlerEdges(block, catchBodies, labelPos, sub, new HashSet());
|
|
}
|
|
|
|
/**
|
|
* Returns an ArrayList of the parameters of a method, including the
|
|
* receiver (non-static methods only).
|
|
*
|
|
* @param method
|
|
* The method.
|
|
*/
|
|
private ArrayList methodParams(final MethodEditor method) {
|
|
final ArrayList locals = new ArrayList();
|
|
|
|
int index = 0;
|
|
|
|
if (!method.isStatic()) {
|
|
// Add the this pointer to the locals.
|
|
final Type type = method.declaringClass().type();
|
|
final LocalVariable var = method.paramAt(index++);
|
|
locals.add(new LocalExpr(var.index(), type));
|
|
}
|
|
|
|
final Type[] paramTypes = method.type().indexedParamTypes();
|
|
|
|
for (int i = 0; i < paramTypes.length; i++) {
|
|
if (paramTypes[i] != null) {
|
|
final LocalVariable var = method.paramAt(index);
|
|
locals.add(new LocalExpr(var.index(), paramTypes[i]));
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
return locals;
|
|
}
|
|
|
|
/**
|
|
* Returns the basic blocks contained in this CFG in trace order. Trace
|
|
* order implies that basic blocks that end with a conditional jump are
|
|
* followed by their false branch and, where possible, that blocks that end
|
|
* in an unconditional jump are followed by the block that is the target of
|
|
* the unconditional branch.
|
|
* <p>
|
|
* The trace does not contain the source and the sink blocks.
|
|
*
|
|
* @return The basic Blocks in this CFG.
|
|
*/
|
|
public List trace() {
|
|
// The trace must include everything but the source and sink.
|
|
Assert.isTrue(trace.size() == size() - 2, "trace contains "
|
|
+ trace.size() + " " + trace + " blocks, not " + (size() - 2)
|
|
+ " " + nodes());
|
|
return trace;
|
|
}
|
|
|
|
/**
|
|
* Commit changes back to the method editor.
|
|
*/
|
|
public void commit() {
|
|
method.clearCode();
|
|
|
|
final CodeGenerator codegen = new CodeGenerator(method);
|
|
visit(codegen);
|
|
|
|
final Label endLabel = method.newLabel();
|
|
method.addLabel(endLabel);
|
|
|
|
// Add all the handlers back in the same order we got them.
|
|
// This ensures that the correct catch clause will be called
|
|
// when an exception is thrown.
|
|
final Iterator iter = catchBlocks.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block catchBlock = (Block) iter.next();
|
|
|
|
final Handler handler = (Handler) handlers.get(catchBlock);
|
|
Assert.isTrue(handler != null);
|
|
|
|
Type type = handler.catchType();
|
|
|
|
if (type.isNull()) {
|
|
type = null;
|
|
}
|
|
|
|
Block begin = null;
|
|
|
|
final Iterator blocks = trace().iterator();
|
|
|
|
while (blocks.hasNext()) {
|
|
final Block block = (Block) blocks.next();
|
|
|
|
if (handler.protectedBlocks().contains(block)) {
|
|
if (begin == null) {
|
|
begin = block;
|
|
}
|
|
} else if (begin != null) {
|
|
final TryCatch tc = new TryCatch(begin.label(), block
|
|
.label(), catchBlock.label(), type);
|
|
|
|
method.addTryCatch(tc);
|
|
|
|
begin = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the "Enter" block of this CFG. That is, the block through which
|
|
* all paths enter.
|
|
*/
|
|
public Block source() {
|
|
return srcBlock;
|
|
}
|
|
|
|
/**
|
|
* Returns the initialization block.
|
|
*
|
|
*/
|
|
public Block init() {
|
|
return iniBlock;
|
|
}
|
|
|
|
/**
|
|
* Returns the sink block. That is, the block through which all paths exit.
|
|
*/
|
|
public Block sink() {
|
|
return snkBlock;
|
|
}
|
|
|
|
/**
|
|
* Returns the iterated dominance frontiers for several basic blocks.
|
|
*
|
|
* @see Block#domFrontier
|
|
*/
|
|
public Collection iteratedDomFrontier(final Collection blocks) {
|
|
return idf(blocks, false);
|
|
}
|
|
|
|
/**
|
|
* Returns the iterated postdominance frontier for several basic blocks.
|
|
*
|
|
* @see Block#pdomFrontier
|
|
*/
|
|
public Collection iteratedPdomFrontier(final Collection blocks) {
|
|
return idf(blocks, true);
|
|
}
|
|
|
|
/**
|
|
* Returns the iterated dominance frontier (DF+) for a given set of blocks.
|
|
* <p>
|
|
* The iterated dominance frontier for a set of nodes is defined to be the
|
|
* union of the dominance frontiers of all the nodes in the set.
|
|
* <p>
|
|
* The iterated dominance frontier is particularly useful because the DF+ of
|
|
* an assignment node for a variable (expression) specifies the nodes at
|
|
* which phi-functions (PHI-functions) need to be inserted.
|
|
*
|
|
* @param blocks
|
|
* The
|
|
* @param reverse
|
|
* Do we find the reverse (i.e. postdominance) dominance
|
|
* frontier.
|
|
*
|
|
* @see SSAPRE#placePhis
|
|
*/
|
|
private Collection idf(final Collection blocks, boolean reverse) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
final HashSet idf = new HashSet();
|
|
|
|
final HashSet inWorklist = new HashSet(blocks);
|
|
final LinkedList worklist = new LinkedList(inWorklist);
|
|
|
|
while (!worklist.isEmpty()) {
|
|
final Block block = (Block) worklist.removeFirst();
|
|
|
|
Collection df;
|
|
|
|
if (!reverse) {
|
|
df = block.domFrontier();
|
|
} else {
|
|
df = block.pdomFrontier();
|
|
}
|
|
|
|
final Iterator iter = df.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Block dfBlock = (Block) iter.next();
|
|
idf.add(dfBlock);
|
|
|
|
if (inWorklist.add(dfBlock)) {
|
|
worklist.add(dfBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
return idf;
|
|
}
|
|
|
|
/**
|
|
* @return A Collection containing the root(s) of this FlowGraph. In this
|
|
* case there is only one root, so the Collection only contains the
|
|
* source block.
|
|
*/
|
|
public Collection roots() {
|
|
return new AbstractCollection() {
|
|
public int size() {
|
|
return 1;
|
|
}
|
|
|
|
public boolean contains(final Object obj) {
|
|
return obj == srcBlock;
|
|
}
|
|
|
|
public Iterator iterator() {
|
|
return new Iterator() {
|
|
Object next = srcBlock;
|
|
|
|
public boolean hasNext() {
|
|
return next != null;
|
|
}
|
|
|
|
public Object next() {
|
|
final Object n = next;
|
|
next = null;
|
|
return n;
|
|
}
|
|
|
|
public void remove() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return A Collection containing only the sink block.
|
|
*
|
|
* @see #roots
|
|
*/
|
|
public Collection reverseRoots() {
|
|
return new AbstractCollection() {
|
|
public int size() {
|
|
return 1;
|
|
}
|
|
|
|
public boolean contains(final Object obj) {
|
|
return obj == snkBlock;
|
|
}
|
|
|
|
public Iterator iterator() {
|
|
return new Iterator() {
|
|
Object next = snkBlock;
|
|
|
|
public boolean hasNext() {
|
|
return next != null;
|
|
}
|
|
|
|
public Object next() {
|
|
final Object n = next;
|
|
next = null;
|
|
return n;
|
|
}
|
|
|
|
public void remove() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Removes a node (a Block) from the graph.
|
|
*
|
|
* @param key
|
|
* Block to remove
|
|
*/
|
|
public void removeNode(final Object key) {
|
|
final Block block = (Block) getNode(key);
|
|
removeBlock(block);
|
|
}
|
|
|
|
/**
|
|
* Returns A Map mapping the first block in an exception handler to its
|
|
* <tt>Handler</tt> object.
|
|
*
|
|
* @see Handler
|
|
*/
|
|
public Map handlersMap() {
|
|
return handlers;
|
|
}
|
|
|
|
/**
|
|
* Returns all of the <tt>Handler</tt> objects in this CFG.
|
|
*/
|
|
public Collection handlers() {
|
|
return handlers.values();
|
|
}
|
|
|
|
/**
|
|
* Returns the<tt>Block</tt>s in this CFG that begin exception handlers.
|
|
*/
|
|
public List catchBlocks() {
|
|
return catchBlocks;
|
|
}
|
|
|
|
private void removeBlock(final Block block) {
|
|
trace.remove(block);
|
|
subroutines.remove(block);
|
|
catchBlocks.remove(block);
|
|
handlers.remove(block);
|
|
|
|
// edgeModCount is incremented by super.removeNode().
|
|
// Dominators will be recomputed automatically if needed, so just
|
|
// clear the pointers to let the GC work.
|
|
|
|
block.setDomParent(null);
|
|
block.setPdomParent(null);
|
|
block.domChildren().clear();
|
|
block.pdomChildren().clear();
|
|
block.domFrontier().clear();
|
|
block.pdomFrontier().clear();
|
|
|
|
Iterator iter = handlers.values().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Handler handler = (Handler) iter.next();
|
|
handler.protectedBlocks().remove(block);
|
|
}
|
|
|
|
iter = subroutines.values().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Subroutine sub = (Subroutine) iter.next();
|
|
sub.removePathsContaining(block);
|
|
|
|
if (sub.exit() == block) {
|
|
sub.setExit(null);
|
|
}
|
|
}
|
|
|
|
if (block.tree() != null) {
|
|
iter = block.tree().stmts().iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Stmt s = (Stmt) iter.next();
|
|
|
|
if (s instanceof LabelStmt) {
|
|
final Label label = ((LabelStmt) s).label();
|
|
label.setStartsBlock(false);
|
|
iniBlock.tree().addStmt(new LabelStmt(label));
|
|
}
|
|
|
|
s.cleanup();
|
|
}
|
|
}
|
|
|
|
super.removeNode(block.label());
|
|
}
|
|
|
|
/**
|
|
* Returns the blocks that a given block dominates.
|
|
*/
|
|
public Collection domChildren(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.domChildren();
|
|
}
|
|
|
|
/**
|
|
* Returns the <tt>Block</tt> that dominates a given block.
|
|
*/
|
|
public Block domParent(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.domParent();
|
|
}
|
|
|
|
/**
|
|
* Returns the type of a given block. A block's type is one of
|
|
* <tt>Block.NON_HEADER</tt>, <tt>Block.IRREDUCIBLE</tt>, or
|
|
* <tt>Block.REDUCIBLE</tt>.
|
|
*/
|
|
public int blockType(final Block block) {
|
|
if (loopEdgeModCount != edgeModCount) {
|
|
buildLoopTree();
|
|
}
|
|
|
|
return block.blockType();
|
|
}
|
|
|
|
/**
|
|
* Returns the depth of the loop in which a block is contained. The block
|
|
* must be contained in a loop. The procedure has depth 0. A loop (while,
|
|
* for, etc.) at the procedure level has depth 1. Depth increases as loops
|
|
* are nested.
|
|
*
|
|
* @param block
|
|
* A block whose depth we are interested in.
|
|
*
|
|
* @see #loopLevel
|
|
*/
|
|
public int loopDepth(final Block block) {
|
|
if (loopEdgeModCount != edgeModCount) {
|
|
buildLoopTree();
|
|
}
|
|
|
|
if ((block == srcBlock) || (block.blockType() != Block.NON_HEADER)) {
|
|
final LoopNode loop = (LoopNode) loopTree.getNode(block);
|
|
Assert.isTrue(loop != null, "no loop for " + block);
|
|
return loop.depth;
|
|
}
|
|
|
|
if (block.header() != null) {
|
|
final LoopNode loop = (LoopNode) loopTree.getNode(block.header());
|
|
Assert.isTrue(loop != null, "no loop for " + block.header());
|
|
return loop.depth;
|
|
}
|
|
|
|
throw new RuntimeException();
|
|
}
|
|
|
|
/**
|
|
* Returns the level of the loop containing a given block. The innermost
|
|
* loops have level 0. The level increases as you go outward to higher loop
|
|
* nestings. For any given loop, the level is the maximum possible.
|
|
* <p>
|
|
*
|
|
* <pre>
|
|
* procedure()
|
|
* {
|
|
* // Depth 0, Level 2 (max possible)
|
|
* while()
|
|
* {
|
|
* // Depth 1, Level 1
|
|
* while()
|
|
* {
|
|
* // Depth 2, Level 0
|
|
* }
|
|
* }
|
|
* while()
|
|
* {
|
|
* // Depth 1, Level 0
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @param block
|
|
* A block whose loop level we want to know. This block must be
|
|
* contained in a loop.
|
|
*/
|
|
public int loopLevel(final Block block) {
|
|
if (loopEdgeModCount != edgeModCount) {
|
|
buildLoopTree();
|
|
}
|
|
|
|
if ((block == srcBlock) || (block.blockType() != Block.NON_HEADER)) {
|
|
final LoopNode loop = (LoopNode) loopTree.getNode(block);
|
|
Assert.isTrue(loop != null, "no loop for " + block);
|
|
return loop.level;
|
|
}
|
|
|
|
if (block.header() != null) {
|
|
final LoopNode loop = (LoopNode) loopTree.getNode(block.header());
|
|
Assert.isTrue(loop != null, "no loop for " + block.header());
|
|
return loop.level;
|
|
}
|
|
|
|
throw new RuntimeException();
|
|
}
|
|
|
|
/**
|
|
* Returns the loop header of the loop containing a given block. The loop
|
|
* header is the block that dominates all of the blocks in the loop.
|
|
*/
|
|
public Block loopHeader(final Block block) {
|
|
if (loopEdgeModCount != edgeModCount) {
|
|
buildLoopTree();
|
|
}
|
|
|
|
return block.header();
|
|
}
|
|
|
|
/**
|
|
* Returns the blocks in the flow graph sorted in pre-order.
|
|
*/
|
|
public List preOrder() {
|
|
return super.preOrder();
|
|
}
|
|
|
|
/**
|
|
* Returns the blocks in the flow graph sorted in post-order.
|
|
*/
|
|
public List postOrder() {
|
|
return super.postOrder();
|
|
}
|
|
|
|
/**
|
|
* Returns the postdominator children of a given block.
|
|
*
|
|
* @see Block#pdomChildren
|
|
*/
|
|
public Collection pdomChildren(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.pdomChildren();
|
|
}
|
|
|
|
/**
|
|
* Returns the postdominator parent of a given block.
|
|
*
|
|
* @see Block#pdomParent
|
|
*/
|
|
public Block pdomParent(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.pdomParent();
|
|
}
|
|
|
|
/**
|
|
* Returns the dominance frontier of a given block.
|
|
*
|
|
* @see Block#domFrontier
|
|
*/
|
|
public Collection domFrontier(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.domFrontier();
|
|
}
|
|
|
|
/**
|
|
* Returns the postdominance frontier of a given block.
|
|
*
|
|
* @see Block#pdomFrontier
|
|
*/
|
|
public Collection pdomFrontier(final Block block) {
|
|
if (domEdgeModCount != edgeModCount) {
|
|
computeDominators();
|
|
}
|
|
|
|
return block.pdomFrontier();
|
|
}
|
|
|
|
/**
|
|
* A LoopNode is a node in the loop tree. The loop tree is represents the
|
|
* nesting of loops in the method being modeled in this CFG.
|
|
*/
|
|
class LoopNode extends GraphNode {
|
|
Block header;
|
|
|
|
int depth;
|
|
|
|
int level;
|
|
|
|
Set elements;
|
|
|
|
public LoopNode(final Block header) {
|
|
this.header = header;
|
|
this.depth = 1;
|
|
this.level = 1;
|
|
this.elements = new HashSet();
|
|
this.elements.add(header);
|
|
}
|
|
|
|
public String toString() {
|
|
return "level=" + level + " depth=" + depth + " header=" + header
|
|
+ " " + elements;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a brief textual description of this <tt>FlowGraph</tt>, namely
|
|
* the name of the method it represents.
|
|
*/
|
|
public String toString() {
|
|
return ("CFG for " + method);
|
|
}
|
|
}
|
|
|