soot.shimple.internal.PhiNodeManager Maven / Gradle / Ivy
package soot.shimple.internal;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2003 Navindra Umanee
* %%
* This program 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 program 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import soot.IdentityUnit;
import soot.Local;
import soot.Trap;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.AssignStmt;
import soot.jimple.Jimple;
import soot.shimple.PhiExpr;
import soot.shimple.Shimple;
import soot.shimple.ShimpleBody;
import soot.shimple.ShimpleFactory;
import soot.toolkits.graph.Block;
import soot.toolkits.graph.BlockGraph;
import soot.toolkits.graph.DominanceFrontier;
import soot.toolkits.graph.DominatorNode;
import soot.toolkits.graph.DominatorTree;
import soot.toolkits.scalar.GuaranteedDefs;
import soot.toolkits.scalar.ValueUnitPair;
import soot.util.Chain;
import soot.util.HashMultiMap;
import soot.util.MultiMap;
/**
* @author Navindra Umanee
* @see soot.shimple.ShimpleBody
* @see Efficiently Computing Static Single Assignment Form and
* the Control Dependence Graph
**/
public class PhiNodeManager {
protected ShimpleBody body;
protected ShimpleFactory sf;
protected DominatorTree dt;
protected DominanceFrontier df;
protected BlockGraph cfg;
protected GuaranteedDefs gd;
public PhiNodeManager(ShimpleBody body, ShimpleFactory sf) {
this.body = body;
this.sf = sf;
}
public void update() {
gd = new GuaranteedDefs(sf.getUnitGraph());
cfg = sf.getBlockGraph();
dt = sf.getDominatorTree();
df = sf.getDominanceFrontier();
}
protected MultiMap varToBlocks;
/**
* Phi node Insertion Algorithm from Cytron et al 91, P24-5,
*
*
* Special Java case: If a variable is not defined along all paths of entry to a node, a Phi node is not needed.
*
**/
public boolean insertTrivialPhiNodes() {
update();
boolean change = false;
varToBlocks = new HashMultiMap();
Map> localsToDefPoints = new LinkedHashMap>();
// compute localsToDefPoints and varToBlocks
for (Block block : cfg) {
for (Unit unit : block) {
List defBoxes = unit.getDefBoxes();
for (ValueBox vb : defBoxes) {
Value def = vb.getValue();
if (def instanceof Local) {
Local local = (Local) def;
List def_points = null;
if (localsToDefPoints.containsKey(local)) {
def_points = localsToDefPoints.get(local);
} else {
def_points = new ArrayList();
localsToDefPoints.put(local, def_points);
}
def_points.add(block);
}
}
if (Shimple.isPhiNode(unit)) {
varToBlocks.put(Shimple.getLhsLocal(unit), block);
}
}
}
/* Routine initialisations. */
int[] workFlags = new int[cfg.size()];
int iterCount = 0;
Stack workList = new Stack();
Map has_already = new HashMap();
for (Iterator blocksIt = cfg.iterator(); blocksIt.hasNext();) {
Block block = blocksIt.next();
has_already.put(block.getIndexInMethod(), 0);
}
/* Main Cytron algorithm. */
{
for (Local local : localsToDefPoints.keySet()) {
iterCount++;
// initialise worklist
{
List def_points = localsToDefPoints.get(local);
// if the local is only defined once, no need for phi nodes
if (def_points.size() == 1) {
continue;
}
for (Block block : def_points) {
workFlags[block.getIndexInMethod()] = iterCount;
workList.push(block);
}
}
while (!workList.empty()) {
Block block = workList.pop();
DominatorNode node = dt.getDode(block);
Iterator> frontierNodes = df.getDominanceFrontierOf(node).iterator();
while (frontierNodes.hasNext()) {
Block frontierBlock = frontierNodes.next().getGode();
int fBIndex = frontierBlock.getIndexInMethod();
Iterator unitsIt = frontierBlock.iterator();
if (!unitsIt.hasNext()) {
continue;
}
if (has_already.get(frontierBlock.getIndexInMethod()) < iterCount) {
has_already.put(frontierBlock.getIndexInMethod(), iterCount);
prependTrivialPhiNode(local, frontierBlock);
change = true;
if (workFlags[fBIndex] < iterCount) {
workFlags[fBIndex] = iterCount;
workList.push(frontierBlock);
}
}
}
}
}
}
return change;
}
/**
* Inserts a trivial Phi node with the appropriate number of arguments.
**/
public void prependTrivialPhiNode(Local local, Block frontierBlock) {
List preds = frontierBlock.getPreds();
PhiExpr pe = Shimple.v().newPhiExpr(local, preds);
pe.setBlockId(frontierBlock.getIndexInMethod());
Unit trivialPhi = Jimple.v().newAssignStmt(local, pe);
Unit blockHead = frontierBlock.getHead();
// is it a catch block?
if (blockHead instanceof IdentityUnit) {
frontierBlock.insertAfter(trivialPhi, frontierBlock.getHead());
} else {
frontierBlock.insertBefore(trivialPhi, frontierBlock.getHead());
}
varToBlocks.put(local, frontierBlock);
}
/**
* Exceptional Phi nodes have a huge number of arguments and control flow predecessors by default. Since it is useless
* trying to keep the number of arguments and control flow predecessors in synch, we might as well trim out all redundant
* arguments and eliminate a huge number of copy statements when we get out of SSA form in the process.
**/
public void trimExceptionalPhiNodes() {
Set handlerUnits = new HashSet();
Iterator trapsIt = body.getTraps().iterator();
while (trapsIt.hasNext()) {
Trap trap = trapsIt.next();
handlerUnits.add(trap.getHandlerUnit());
}
for (Block block : cfg) {
// trim relevant Phi expressions
if (handlerUnits.contains(block.getHead())) {
for (Unit unit : block) {
// if(!(newPhiNodes.contains(unit)))
PhiExpr phi = Shimple.getPhiExpr(unit);
if (phi == null) {
continue;
}
trimPhiNode(phi);
}
}
}
}
/**
* @see #trimExceptionalPhiNodes()
**/
public void trimPhiNode(PhiExpr phiExpr) {
/*
* A value may appear many times in an exceptional Phi. Hence, the same value may be associated with many UnitBoxes. We
* build the MultiMap valueToPairs for convenience.
*/
MultiMap valueToPairs = new HashMultiMap();
for (ValueUnitPair argPair : phiExpr.getArgs()) {
Value value = argPair.getValue();
valueToPairs.put(value, argPair);
}
/*
* Consider each value and see if we can find the dominating UnitBoxes. Once we have found all the dominating UnitBoxes,
* the rest of the redundant arguments can be trimmed.
*/
Iterator valuesIt = valueToPairs.keySet().iterator();
while (valuesIt.hasNext()) {
Value value = valuesIt.next();
// although the champs list constantly shrinks, guaranteeing
// termination, the challengers list never does. This could
// be optimised.
Set pairsSet = valueToPairs.get(value);
List champs = new LinkedList(pairsSet);
List challengers = new LinkedList(pairsSet);
// champ is the currently assumed dominator
ValueUnitPair champ = champs.remove(0);
Unit champU = champ.getUnit();
// hopefully everything will work out the first time, but
// if not, we will have to try a new champion just in case
// there is more that can be trimmed.
boolean retry = true;
while (retry) {
retry = false;
// go through each challenger and see if we dominate them
// if not, the challenger becomes the new champ
for (Iterator iterator = challengers.iterator(); iterator.hasNext();) {
ValueUnitPair challenger = iterator.next();
if (challenger.equals(champ)) {
continue;
}
Unit challengerU = challenger.getUnit();
// kill the challenger
if (dominates(champU, challengerU)) {
phiExpr.removeArg(challenger);
iterator.remove();
}
// we die, find a new champ
else if (dominates(challengerU, champU)) {
phiExpr.removeArg(champ);
champ = challenger;
champU = champ.getUnit();
} else {
retry = true;
}
}
if (retry) {
if (champs.isEmpty()) {
break;
}
champ = champs.remove(0);
champU = champ.getUnit();
}
}
}
/*
* { List preds = phiExpr.getPreds();
*
* for(int i = 0; i < phiExpr.getArgCount(); i++){ ValueUnitPair vup = phiExpr.getArgBox(i); Value value =
* vup.getValue(); Unit unit = vup.getUnit();
*
* PhiExpr innerPhi = Shimple.getPhiExpr(unit); if(innerPhi == null) continue;
*
* Value innerValue = Shimple.getLhsLocal(unit); if(!innerValue.equals(value)) continue;
*
* boolean canRemove = true; for(int j = 0; j < innerPhi.getArgCount(); j++){ Unit innerPred = innerPhi.getPred(j);
* if(!preds.contains(innerPred)){ canRemove = false; break; } }
*
* if(canRemove) phiExpr.removeArg(vup); } }
*/
}
protected Map unitToBlock;
/**
* Returns true if champ dominates challenger. Note that false doesn't necessarily mean that challenger dominates champ.
**/
public boolean dominates(Unit champ, Unit challenger) {
if (champ == null || challenger == null) {
throw new RuntimeException("Assertion failed.");
}
// self-domination
if (champ.equals(challenger)) {
return true;
}
if (unitToBlock == null) {
unitToBlock = getUnitToBlockMap(cfg);
}
Block champBlock = unitToBlock.get(champ);
Block challengerBlock = unitToBlock.get(challenger);
if (champBlock.equals(challengerBlock)) {
Iterator unitsIt = champBlock.iterator();
while (unitsIt.hasNext()) {
Unit unit = unitsIt.next();
if (unit.equals(champ)) {
return true;
}
if (unit.equals(challenger)) {
return false;
}
}
throw new RuntimeException("Assertion failed.");
}
DominatorNode champNode = dt.getDode(champBlock);
DominatorNode challengerNode = dt.getDode(challengerBlock);
// *** FIXME: System.out.println("champ: " + champNode);
// System.out.println("chall: " + challengerNode);
return (dt.isDominatorOf(champNode, challengerNode));
}
/**
* Eliminate Phi nodes in block by naively replacing them with shimple assignment statements in the control flow
* predecessors. Returns true if new locals were added to the body during the process, false otherwise.
**/
public boolean doEliminatePhiNodes() {
// flag that indicates whether we created new locals during the
// elimination process
boolean addedNewLocals = false;
// List of Phi nodes to be deleted.
List phiNodes = new ArrayList();
// This stores the assignment statements equivalent to each
// (and every) Phi. We use lists instead of a Map of
// non-determinate order since we prefer to preserve the order
// of the assignment statements, i.e. if a block has more than
// one Phi expression, we prefer that the equivalent
// assignments be placed in the same order as the Phi expressions.
List equivStmts = new ArrayList();
// Similarly, to preserve order, instead of directly storing
// the pred, we store the pred box so that we follow the
// pointers when SPatchingChain moves them.
List predBoxes = new ArrayList();
Chain units = body.getUnits();
for (Unit unit : units) {
PhiExpr phi = Shimple.getPhiExpr(unit);
if (phi == null) {
continue;
}
Local lhsLocal = Shimple.getLhsLocal(unit);
for (int i = 0; i < phi.getArgCount(); i++) {
Value phiValue = phi.getValue(i);
AssignStmt convertedPhi = Jimple.v().newAssignStmt(lhsLocal, phiValue);
equivStmts.add(convertedPhi);
predBoxes.add(phi.getArgBox(i));
}
phiNodes.add(unit);
}
if (equivStmts.size() != predBoxes.size()) {
throw new RuntimeException("Assertion failed.");
}
/* Avoid Concurrent Modification exceptions. */
for (int i = 0; i < equivStmts.size(); i++) {
AssignStmt stmt = equivStmts.get(i);
Unit pred = predBoxes.get(i).getUnit();
if (pred == null) {
throw new RuntimeException("Assertion failed.");
}
// if we need to insert the copy statement *before* an
// instruction that happens to be *using* the Local being
// defined, we need to do some extra work to make sure we
// don't overwrite the old value of the local
if (pred.branches()) {
boolean needPriming = false;
Local lhsLocal = (Local) stmt.getLeftOp();
Local savedLocal = Jimple.v().newLocal(lhsLocal.getName() + "_", lhsLocal.getType());
Iterator useBoxesIt = pred.getUseBoxes().iterator();
while (useBoxesIt.hasNext()) {
ValueBox useBox = useBoxesIt.next();
if (lhsLocal.equals(useBox.getValue())) {
needPriming = true;
addedNewLocals = true;
useBox.setValue(savedLocal);
}
}
if (needPriming) {
body.getLocals().add(savedLocal);
AssignStmt copyStmt = Jimple.v().newAssignStmt(savedLocal, lhsLocal);
units.insertBefore(copyStmt, pred);
}
// this is all we really wanted to do!
units.insertBefore(stmt, pred);
} else {
units.insertAfter(stmt, pred);
}
}
Iterator phiNodesIt = phiNodes.iterator();
while (phiNodesIt.hasNext()) {
Unit removeMe = phiNodesIt.next();
units.remove(removeMe);
removeMe.clearUnitBoxes();
}
return addedNewLocals;
}
/**
* Convenience function that maps units to blocks. Should probably be in BlockGraph.
**/
public Map getUnitToBlockMap(BlockGraph blocks) {
Map unitToBlock = new HashMap();
Iterator blocksIt = blocks.iterator();
while (blocksIt.hasNext()) {
Block block = blocksIt.next();
Iterator unitsIt = block.iterator();
while (unitsIt.hasNext()) {
Unit unit = unitsIt.next();
unitToBlock.put(unit, block);
}
}
return unitToBlock;
}
}