/* This file is part of the db4o object database http://www.db4o.com
Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com
db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.
db4o 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 General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see http://www.gnu.org/licenses/. */
package EDU.purdue.cs.bloat.ssa;
import java.util.*;
import EDU.purdue.cs.bloat.cfg.*;
import EDU.purdue.cs.bloat.tree.*;
import EDU.purdue.cs.bloat.util.*;
/**
* Compute the SSA form of the control flow graph and build FUD chains.
* <p>
* The SSA algorithm is from:
*
* <pre>
* R. Cytron, J. Ferrante J, B. K. Rosen, M. N. Wegman, and F. K. Zadeck,
* "Efficiently Computing Static Single Assignment Form and the Control
* Dependence Graph", TOPLAS, 13(4): 451-490, October 1991.
* </pre>
*
* <p>
* I made modifications to the algorithm to compute FUD chains and to run the
* algorithm separately for each variable similar to the SSAPRE algorithm.
* Making a separate pass for each variable allows variables to be added
* incrementally.
*/
public class SSA {
public static boolean DEBUG = false;
/**
* Transforms a control flow graph into Single Static Assignment (SSA) form.
* First, the CFG is traversed and a list of all variables (both local and
* stack) eligible for SSA renaming is compiled. Variables are represented
* by instances of <tt>SSAConstructionInfo</tt>. Each of these variables
* is then transformed.
*
* @see #transform(FlowGraph)
* @see SSAConstructionInfo
*/
public static void transform(final FlowGraph cfg) {
final Iterator e = SSA.collectVars(cfg);
while (e.hasNext()) {
final SSAConstructionInfo info = (SSAConstructionInfo) e.next();
SSA.transform(cfg, info);
}
}
/**
* Performs the actions necessary to convert a CFG into SSA form with
* respect to one variable. The variable's information is stored in the
* <tt>SSAConstructionInfo</tt>.
*/
public static void transform(final FlowGraph cfg,
final SSAConstructionInfo info) {
if (SSA.DEBUG) {
System.out.println("transforming " + info.prototype + " ("
+ info.prototype.type() + ")");
}
SSA.placePhiFunctions(cfg, info);
SSA.rename(cfg, info);
SSA.insertCode(cfg, info);
}
/**
* Visits the nodes in a control flow graph and constructs
* <tt>SSAConstructionInfo</tt> objects for each variable in the CFG.
* Returns the <tt>SSAConstructionInfo</tt>s for the variables in the
* CFG.
*/
private static Iterator collectVars(final FlowGraph cfg) {
// SSAConstructionInfo objects for cfg
final Map infos = new HashMap();
cfg.visit(new TreeVisitor() {
// Visit all statements in the CFG. Remove any pre-existing
// PhiStmts.
public void visitTree(final Tree tree) {
final Iterator iter = tree.stmts().iterator();
while (iter.hasNext()) {
final Stmt stmt = (Stmt) iter.next();
if (stmt instanceof PhiStmt) {
iter.remove();
} else {
stmt.visit(this);
}
}
}
// Recall that VarExprs represent variables. If we have not
// already created a SSAConstructionInfo for a variable
// (VarExpr), do so. Make note of the fact that this is a real
// occurrence of the variable.
public void visitVarExpr(final VarExpr expr) {
expr.visitChildren(this);
expr.setDef(null);
final Object key = expr.comparator();
SSAConstructionInfo info = (SSAConstructionInfo) infos.get(key);
if (info == null) {
info = new SSAConstructionInfo(cfg, expr);
infos.put(key, info);
}
info.addReal(expr);
}
});
return infos.values().iterator();
}
/**
* Places phi statements at the appropriate locations in the CFG. This
* implementation only places phi functions for variables that are live on
* entry to at least one block. That is, if a variable is only used within
* one block, we don't bother searching for a place to put phi functions for
* it.
*
* @param cfg
* The CFG in which phi functions are placed.
* @param info
* The variable for which phi functions will be placed.
*/
private static void placePhiFunctions(final FlowGraph cfg,
final SSAConstructionInfo info) {
if (SSA.DEBUG) {
System.out.println("Placing phi-functions for " + info);
}
// Phis are only placed for variables which are live on entry to
// at least one block.
//
// This is the semi-pruned form described in "Practical
// Improvements to the Construction and Destruction of Static
// Single Assignment Form" by Briggs, Cooper, Harvey, Simpson
//
// Blocks in which the variable in the SSAConstructionInfo is
// defined. That is, variables that are defined in this block.
final BitSet killed = new BitSet(cfg.size());
// Is the variable used in more than one block?
boolean nonLocal = false;
final Iterator reals = info.reals().iterator();
// Look at all real (not in phi statement) occurrences of the
// variable in the SSAConstructionInfo. Determine which variables
// are live on entry to some basic block (i.e. "non-local"). If
// a variable is not live on entry to some basic block, it is only
// used within the block in which it is defined, so don't bother
// adding a phi statement for it.
while (reals.hasNext()) {
final VarExpr real = (VarExpr) reals.next();
final Block block = real.block(); // Block in which variable
// occurs
if (real.isDef()) {
killed.set(cfg.preOrderIndex(block));
} else if (!killed.get(cfg.preOrderIndex(block))) {
// There is a use of the variable as an operand that is not
// defined in the block in which it occurs. Therefore, the
// variable is non-local.
nonLocal = true;
break;
}
}
if (!nonLocal) {
return;
}
// We've decided that this variable is used in multiple blocks,
// so go ahead and place phi functions for it.
// Iterate over all of the catch blocks (blocks that begin an
// exception handler) in the CFG and instert PhiCatchStmts where
// appropriate.
final Iterator catchBlocks = cfg.catchBlocks().iterator();
while (catchBlocks.hasNext()) {
final Block block = (Block) catchBlocks.next();
info.addCatchPhi(block);
info.addDefBlock(block);
}
// Iterate over all of the subroutines (finally blocks) and insert
// PhiReturnStmts where appropriate.
final Iterator subs = cfg.subroutines().iterator();
while (subs.hasNext()) {
final Subroutine sub = (Subroutine) subs.next();
info.addRetPhis(sub);
final Iterator paths = sub.paths().iterator();
while (paths.hasNext()) {
final Block[] path = (Block[]) paths.next();
info.addDefBlock(path[1]);
}
}
// Now we add real phi functions to the CFG. Phi functions are
// placed at the (blocks in the) iterated dominance fontier of each
// of the blocks containing a definition of the variable.
final Iterator df = cfg.iteratedDomFrontier(info.defBlocks())
.iterator();
while (df.hasNext()) {
final Block block = (Block) df.next();
// Don't place phi-statements in the exit block because one of
// the operands will always have a null definition.
if (block != cfg.sink()) {
info.addPhi(block);
}
}
}
/**
* If the block resides in a protected region and there is a
* <tt>PhiCatchStmt</tt> for the variable in question in the handler of
* the exception thrown by the protected region (meaning that the variable
* is used in the protected region), the variable becomes an operand to the
* <tt>PhiCatchStmt</tt>.
*
* @param info
* The variable (LocalExpr) that we're dealing with
* @param block
* The block in a potentially protected region. If the block is
* indeed in a protected region, the occurrence of the the
* variable represented by info becomes an operand to the
* PhiCatchStmt at the beginning of the protected region's
* handler.
* @param def
* The defining occurrence of the variable stored in info.
*/
private static void addCatchPhiOperands(final SSAConstructionInfo info,
final Block block, final LocalExpr def) {
final Iterator handlers = block.graph().handlers().iterator();
// Iterate over all of the exception handlers in the CFG. If
// the block we are dealing with is a protected block (that is,
// is inside a try block), then the variable represented by info
// becomes an operand to the PhiCatchStmt residing at the
// beginning of the protected block's handler.
while (handlers.hasNext()) {
final Handler handler = (Handler) handlers.next();
if (handler.protectedBlocks().contains(block)) {
final PhiCatchStmt phi = (PhiCatchStmt) info.phiAtBlock(handler
.catchBlock());
if ((phi != null) && !phi.hasOperandDef(def)) {
final LocalExpr operand = (LocalExpr) info.prototype
.clone();
operand.setDef(def); // ???
phi.addOperand(operand);
}
}
}
}
/**
* The actual renamining is done by the search method. This method just
* takes care of <Tt>PhiReturnStmts</tt>.
*/
private static void rename(final FlowGraph cfg,
final SSAConstructionInfo info) {
SSA.search(cfg, info, null, cfg.source());
// Eliminate PhiReturns by replacing their uses with the defs live
// at the end of the returning sub or live on the same path on entry
// to the sub (if the variable did not occur in the subroutine).
// Examine each PhiReturnStmt in the CFG. Recall that
// PhiReturnStmts are "inserted" at blocks that begin exceptions
boolean changed = true;
while (changed) {
changed = false;
final Iterator subs = cfg.subroutines().iterator();
while (subs.hasNext()) {
final Subroutine sub = (Subroutine) subs.next();
final Iterator paths = sub.paths().iterator();
final PhiJoinStmt entry = (PhiJoinStmt) info.phiAtBlock(sub
.entry());
if (entry == null) {
// If there was no PhiJoinStmt for the variable in the
// subroutine, who cares? We don't.
continue;
}
while (paths.hasNext()) {
final Block[] path = (Block[]) paths.next();
final PhiReturnStmt ret = (PhiReturnStmt) info
.phiAtBlock(path[1]);
if (ret != null) {
DefExpr def = ret.operand().def();
if (def != entry.target()) {
// If the operand of the PhiReturnStmt is different
// from
// the new SSA variable defined by the PhiCatchStmt
// at
// the beginning of the subroutine, then the
// variable
// was defined in the subroutine, so the operand to
// the
// PhiReturnStmt is the correct SSA variable. This
// is
// like the variable "b" in figure 3.5 in Nate's
// Thesis.
continue;
}
// Replace all uses of the target of the PhiReturnStmt
// with the SSA variable corresponding to the block in
// which the jsr occured. This is like variable "a" in
// figure 3.5 in Nate's Thesis.
def = ((VarExpr) entry.operandAt(path[0])).def();
final Iterator uses = ret.target().uses().iterator();
while (uses.hasNext()) {
final VarExpr use = (VarExpr) uses.next();
use.setDef(def);
}
// The PhiReturnStmt is no longer needed
info.removePhiAtBlock(path[1]);
changed = true;
}
}
}
}
final Iterator subs = cfg.subroutines().iterator();
// Examine any remaining PhiReturnStmts. Replace all uses of the
// target of the PhiReturnStmt with its operand.
while (subs.hasNext()) {
final Subroutine sub = (Subroutine) subs.next();
final Iterator paths = sub.paths().iterator();
while (paths.hasNext()) {
final Block[] path = (Block[]) paths.next();
final PhiReturnStmt ret = (PhiReturnStmt) info
.phiAtBlock(path[1]);
if (ret != null) {
final DefExpr def = ret.operand().def();
final Iterator uses = ret.target().uses().iterator();
while (uses.hasNext()) {
final VarExpr use = (VarExpr) uses.next();
use.setDef(def);
}
info.removePhiAtBlock(path[1]);
}
}
}
}
/**
* Does the actual renaming. It keeps track of the most recent occurrence of
* an (SSA numbered) variable and recalculates the definitions of variables
* appropriately.
*
* @param info
* SSAConstructionInfo representing the variable being converted
* into SSA form.
* @param top
* "Top" of the variable stack for the variable in question. Each
* variable has a "stack" associated with it. The top of the
* stack contains the current SSA name of the variable. It can
* also be thought of as the "most recent definition" of the
* variable.
* @param block
* Basic block in which the variable is being renamed.
*/
private static void search(final FlowGraph cfg,
final SSAConstructionInfo info, VarExpr top, final Block block) {
if (SSA.DEBUG) {
System.out.println(" renaming " + info.prototype + " in " + block);
}
// If appropriate, add top as an operand of a PhiCatchStmt
if (top instanceof LocalExpr) {
SSA.addCatchPhiOperands(info, block, (LocalExpr) top);
}
// First handle any phi in the block.
final PhiStmt phi = info.phiAtBlock(block);
if (phi != null) {
top = phi.target();
if (top instanceof LocalExpr) {
SSA.addCatchPhiOperands(info, block, (LocalExpr) top);
}
}
// If the block in which the variable is being renamed begins an
// exception handler and we're dealing with a stack variable, then
// there is no most recent definition of the variable because the
// stack is cleared when an exception is handled. I dunno.
if (cfg.catchBlocks().contains(block)
&& (info.prototype instanceof StackExpr)) {
if (SSA.DEBUG) {
System.out.println(" Killing TOS at " + block);
}
// The operand stack is popped down to 0 at catch blocks.
top = null;
}
final Iterator e = info.realsAtBlock(block).iterator();
// Examine each occurrence of the variable in the block of
// interest. When we encounter a definition of the variable, make
// that definition to the most recent SSA variable (top). For
// each use, make this most recent SSA variable be its defining
// expression.
while (e.hasNext()) {
final VarExpr real = (VarExpr) e.next();
if (real.isDef()) {
real.setDef(null);
top = real; // A definition means a new SSA variable
if (top instanceof LocalExpr) {
SSA.addCatchPhiOperands(info, block, (LocalExpr) top);
}
if (SSA.DEBUG) {
System.out.println(" TOS = " + top);
}
} else {
// Make sure that the variable is defined somewhere else
// (somewhere that we have already seen).
Assert.isTrue(top != null, "Null def for " + real);
real.setDef(top);
}
}
final Iterator succs = cfg.succs(block).iterator();
// Examine all successors of the block in question. If the
// successor contains a PhiJoinStmt for the variable, then set the
// operand corresponding to the block to be defined by the most
// recent SSA variable. Similarly for a PhiReturnStmt.
while (succs.hasNext()) {
final Block succ = (Block) succs.next();
final PhiStmt succPhi = info.phiAtBlock(succ);
if (succPhi instanceof PhiJoinStmt) {
final PhiJoinStmt f = (PhiJoinStmt) succPhi;
final VarExpr operand = (VarExpr) f.operandAt(block);
operand.setDef(top);
} else if (succPhi instanceof PhiReturnStmt) {
final PhiReturnStmt f = (PhiReturnStmt) succPhi;
final VarExpr operand = (VarExpr) f.operand();
operand.setDef(top);
}
// Adjust the operands of any PhiCatchStmts if the sucessor node
// is protected.
if (top instanceof LocalExpr) {
SSA.addCatchPhiOperands(info, succ, (LocalExpr) top);
}
}
final Iterator children = cfg.domChildren(block).iterator();
// Visit the children in the dominator tree. Keep the same most
// recent SSA variable (top).
while (children.hasNext()) {
final Block child = (Block) children.next();
SSA.search(cfg, info, top, child);
}
}
/**
* Iterates over the blocks in the CFG and inserts the phi statement
* associated with that block. Up until this point, the phi statement is
* only maintained in SSAConstructionInfo. Note that the phi statement
* cannot be a return phi.
*
* @param cfg
* The CFG into which to insert phi statements.
* @param info
* Represents the variable whose phi statements we are inserting.
*
* @see PhiReturnStmt
*/
private static void insertCode(final FlowGraph cfg,
final SSAConstructionInfo info) {
final Iterator blocks = cfg.nodes().iterator();
while (blocks.hasNext()) {
final Block block = (Block) blocks.next();
final PhiStmt phi = info.phiAtBlock(block);
if (phi != null) {
Assert.isFalse(phi instanceof PhiReturnStmt);
block.tree().prependStmt(phi);
}
}
}
}