soot.shimple.toolkits.scalar.SConstantPropagatorAndFolder Maven / Gradle / Ivy
Show all versions of soot Show documentation
package soot.shimple.toolkits.scalar;
/*-
* #%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.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import soot.Body;
import soot.BodyTransformer;
import soot.G;
import soot.Local;
import soot.PhaseOptions;
import soot.Singletons;
import soot.Unit;
import soot.UnitBox;
import soot.UnitBoxOwner;
import soot.Value;
import soot.ValueBox;
import soot.jimple.Constant;
import soot.jimple.DefinitionStmt;
import soot.jimple.GotoStmt;
import soot.jimple.IfStmt;
import soot.jimple.IntConstant;
import soot.jimple.Jimple;
import soot.jimple.LookupSwitchStmt;
import soot.jimple.Stmt;
import soot.jimple.TableSwitchStmt;
import soot.options.Options;
import soot.shimple.ShimpleBody;
import soot.shimple.toolkits.scalar.SEvaluator.BottomConstant;
import soot.shimple.toolkits.scalar.SEvaluator.MetaConstant;
import soot.shimple.toolkits.scalar.SEvaluator.TopConstant;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.ArraySparseSet;
import soot.toolkits.scalar.FlowSet;
import soot.toolkits.scalar.ForwardBranchedFlowAnalysis;
import soot.toolkits.scalar.Pair;
import soot.toolkits.scalar.UnitValueBoxPair;
import soot.util.Chain;
/**
* A powerful constant propagator and folder based on an algorithm sketched by Cytron et al that takes conditional control
* flow into account. This optimization demonstrates some of the benefits of SSA -- particularly the fact that Phi nodes
* represent natural merge points in the control flow.
*
* @author Navindra Umanee
* @see Efficiently Computing Static Single Assignment Form and
* the Control Dependence Graph
**/
public class SConstantPropagatorAndFolder extends BodyTransformer {
private static final Logger logger = LoggerFactory.getLogger(SConstantPropagatorAndFolder.class);
public SConstantPropagatorAndFolder(Singletons.Global g) {
}
public static SConstantPropagatorAndFolder v() {
return G.v().soot_shimple_toolkits_scalar_SConstantPropagatorAndFolder();
}
protected ShimpleBody sb;
protected boolean debug;
protected void internalTransform(Body b, String phaseName, Map options) {
if (!(b instanceof ShimpleBody)) {
throw new RuntimeException("SConstantPropagatorAndFolder requires a ShimpleBody.");
}
this.sb = (ShimpleBody) b;
if (!sb.isSSA()) {
throw new RuntimeException("ShimpleBody is not in proper SSA form as required by SConstantPropagatorAndFolder."
+ "You may need to rebuild it or use ConstantPropagatorAndFolder instead.");
}
boolean pruneCFG = PhaseOptions.getBoolean(options, "prune-cfg");
debug = Options.v().debug();
debug |= sb.getOptions().debug();
if (Options.v().verbose()) {
logger.debug("[" + sb.getMethod().getName() + "] Propagating and folding constants (SSA)...");
}
// *** FIXME: What happens when Shimple is built with another UnitGraph?
SCPFAnalysis scpf = new SCPFAnalysis(new ExceptionalUnitGraph(sb));
propagateResults(scpf.getResults());
if (pruneCFG) {
removeStmts(scpf.getDeadStmts());
replaceStmts(scpf.getStmtsToReplace());
}
}
/**
* Propagates constants to the definition and uses of the relevant locals given a mapping. Notice that we use the Shimple
* implementation of LocalDefs and LocalUses.
**/
protected void propagateResults(Map localToConstant) {
Chain units = sb.getUnits();
Collection locals = sb.getLocals();
ShimpleLocalDefs localDefs = new ShimpleLocalDefs(sb);
ShimpleLocalUses localUses = new ShimpleLocalUses(sb);
Iterator localsIt = locals.iterator();
while (localsIt.hasNext()) {
Local local = localsIt.next();
Constant constant = localToConstant.get(local);
if (constant instanceof MetaConstant) {
continue;
}
// update definition
{
DefinitionStmt stmt = (DefinitionStmt) localDefs.getDefsOf(local).get(0);
ValueBox defSrcBox = stmt.getRightOpBox();
Value defSrc = defSrcBox.getValue();
if (defSrcBox.canContainValue(constant)) {
defSrcBox.setValue(constant);
// remove dangling pointers
if (defSrc instanceof UnitBoxOwner) {
((UnitBoxOwner) defSrc).clearUnitBoxes();
}
} else if (debug) {
logger.warn("Couldn't propagate constant " + constant + " to box " + defSrcBox.getValue() + " in unit " + stmt);
}
}
// update uses
{
Iterator usesIt = localUses.getUsesOf(local).iterator();
while (usesIt.hasNext()) {
UnitValueBoxPair pair = (UnitValueBoxPair) usesIt.next();
ValueBox useBox = pair.getValueBox();
if (useBox.canContainValue(constant)) {
useBox.setValue(constant);
} else if (debug) {
logger.warn(
"Couldn't propagate constant " + constant + " to box " + useBox.getValue() + " in unit " + pair.getUnit());
}
}
}
}
}
/**
* Removes the given list of fall through IfStmts from the body.
**/
protected void removeStmts(List deadStmts) {
Chain units = sb.getUnits();
Iterator deadIt = deadStmts.iterator();
while (deadIt.hasNext()) {
Unit dead = deadIt.next();
units.remove(dead);
dead.clearUnitBoxes();
}
}
/**
* Replaces conditional branches by unconditional branches as given by the mapping.
**/
protected void replaceStmts(Map stmtsToReplace) {
Chain units = sb.getUnits();
Iterator stmtsIt = stmtsToReplace.keySet().iterator();
while (stmtsIt.hasNext()) {
// important not to call clearUnitBoxes() on booted since
// replacement uses the same UnitBox
Unit booted = stmtsIt.next();
Unit replacement = stmtsToReplace.get(booted);
units.swapWith(booted, replacement);
}
}
}
/**
* The actual branching flow analysis implementation. Briefly, a sketch of the sketch from the Cytron et al paper:
*
*
* Initially the algorithm assumes that each edge is unexecutable (the entry nodes are reachable) and that each variable is
* constant with an unknown value, Top. Assumptions are corrected until they stabilise.
*
*
* For example, if q is found to be not a constant (Bottom) in if(q == 0) goto label1 then both edges
* leaving the the statement are considered executable, if q is found to be a constant then only one of the edges
* are executable.
*
*
* Whenever a reachable definition statement such as "x = 3" is found, the information is propagated to all uses of x (this
* works due to the SSA property).
*
*
* Perhaps the crucial point is that if a node such as x =
* Phi(x_1, x_2) is ever found, information on x is assumed as follows:
*
*
* - If x_1 and x_2 are the same assumed constant, x is assumed to be that constant. If they are
* not the same constant, x is Bottom.
*
* - If either one is Top and the other is a constant, x is assumed to be the same as the known constant.
*
* - If either is Bottom, x is assumed to be Bottom.
*
*
*
* The crucial point about the crucial point is that if definitions of x_1 or x_2 are never reached, the
* Phi node will still assume them to be Top and hence they will not influence the decision as to whether x is a
* constant or not.
**/
class SCPFAnalysis extends ForwardBranchedFlowAnalysis {
protected FlowSet emptySet;
/**
* A mapping of the locals to their current assumed constant value (which may be Top or Bottom).
**/
protected Map localToConstant;
/**
* A map from conditional branches to their possible replacement unit, an unconditional branch.
**/
protected Map stmtToReplacement;
/**
* A list of IfStmts that always fall through.
**/
protected List deadStmts;
/**
* Returns the localToConstant map.
**/
public Map getResults() {
return localToConstant;
}
/**
* Returns the list of fall through IfStmts.
**/
public List getDeadStmts() {
return deadStmts;
}
/**
* Returns a Map from conditional branches to the unconditional branches that can replace them.
**/
public Map getStmtsToReplace() {
return stmtToReplacement;
}
public SCPFAnalysis(UnitGraph graph) {
super(graph);
emptySet = new ArraySparseSet();
stmtToReplacement = new HashMap();
deadStmts = new ArrayList();
// initialise localToConstant map -- assume all scalars are
// constant (Top)
{
Collection locals = graph.getBody().getLocals();
Iterator localsIt = locals.iterator();
localToConstant = new HashMap(graph.size() * 2 + 1, 0.7f);
while (localsIt.hasNext()) {
Local local = (Local) localsIt.next();
localToConstant.put(local, TopConstant.v());
}
}
doAnalysis();
}
// *** NOTE: this is here because ForwardBranchedFlowAnalysis does
// *** not handle exceptional control flow properly in the
// *** dataflow analysis. this should be removed when
// *** ForwardBranchedFlowAnalysis is fixed.
protected boolean treatTrapHandlersAsEntries() {
return true;
}
/**
* If a node has empty IN sets we assume that it is not reachable. Hence, we initialise the entry sets to be non-empty to
* indicate that they are reachable.
**/
protected Object entryInitialFlow() {
FlowSet entrySet = (FlowSet) emptySet.emptySet();
entrySet.add(TopConstant.v());
return entrySet;
}
/**
* All other nodes are assumed to be unreachable by default.
**/
protected Object newInitialFlow() {
return emptySet.emptySet();
}
/**
* Since we are interested in control flow from all branches, take the union.
**/
protected void merge(Object in1, Object in2, Object out) {
FlowSet fin1 = (FlowSet) in1;
FlowSet fin2 = (FlowSet) in2;
FlowSet fout = (FlowSet) out;
fin1.union(fin2, fout);
}
/**
* Defer copy to FlowSet.
**/
protected void copy(Object source, Object dest) {
FlowSet fource = (FlowSet) source;
FlowSet fest = (FlowSet) dest;
fource.copy(fest);
}
/**
* If a node has an empty in set, it is considered unreachable. Otherwise the node is examined and if any assumptions have
* to be corrected, a Pair containing the corrected assumptions is flowed to the reachable nodes. If no assumptions have to
* be corrected then no information other than the in set is propagated to the reachable nodes.
*
*
* Pair serves no other purpose than to keep the analysis flowing for as long as needed. The final results are accumulated
* in the localToConstant map.
**/
protected void flowThrough(Object in, Unit s, List fallOut, List branchOuts) {
FlowSet fin = ((FlowSet) in).clone();
// not reachable
if (fin.isEmpty()) {
return;
}
// If s is a definition, check if any assumptions have to be
// corrected.
Pair pair = processDefinitionStmt(s);
if (pair != null) {
fin.add(pair);
}
// normal, non-branching statement
if (!s.branches() && s.fallsThrough()) {
Iterator fallOutIt = fallOut.iterator();
while (fallOutIt.hasNext()) {
FlowSet fallSet = (FlowSet) fallOutIt.next();
fallSet.union(fin);
}
return;
}
/* determine which nodes are reachable. */
boolean conservative = true;
boolean fall = false;
boolean branch = false;
FlowSet oneBranch = null;
IFSTMT: {
if (s instanceof IfStmt) {
IfStmt ifStmt = (IfStmt) s;
Value cond = ifStmt.getCondition();
Constant constant = SEvaluator.getFuzzyConstantValueOf(cond, localToConstant);
// flow both ways
if (constant instanceof BottomConstant) {
deadStmts.remove(ifStmt);
stmtToReplacement.remove(ifStmt);
break IFSTMT;
}
// no flow
if (constant instanceof TopConstant) {
return;
}
/* determine whether to flow through or branch */
conservative = false;
Constant trueC = IntConstant.v(1);
Constant falseC = IntConstant.v(0);
if (constant.equals(trueC)) {
branch = true;
GotoStmt gotoStmt = Jimple.v().newGotoStmt(ifStmt.getTargetBox());
stmtToReplacement.put(ifStmt, gotoStmt);
}
if (constant.equals(falseC)) {
fall = true;
deadStmts.add(ifStmt);
}
}
} // end IFSTMT
TABLESWITCHSTMT: {
if (s instanceof TableSwitchStmt) {
TableSwitchStmt table = (TableSwitchStmt) s;
Value keyV = table.getKey();
Constant keyC = SEvaluator.getFuzzyConstantValueOf(keyV, localToConstant);
// flow all branches
if (keyC instanceof BottomConstant) {
stmtToReplacement.remove(table);
break TABLESWITCHSTMT;
}
// no flow
if (keyC instanceof TopConstant) {
return;
}
// flow all branches
if (!(keyC instanceof IntConstant)) {
break TABLESWITCHSTMT;
}
/* find the one branch we need to flow to */
conservative = false;
int key = ((IntConstant) keyC).value;
int low = table.getLowIndex();
int high = table.getHighIndex();
int index = key - low;
UnitBox branchBox = null;
if (index < 0 || index > high) {
branchBox = table.getDefaultTargetBox();
} else {
branchBox = table.getTargetBox(index);
}
GotoStmt gotoStmt = Jimple.v().newGotoStmt(branchBox);
stmtToReplacement.put(table, gotoStmt);
List unitBoxes = table.getUnitBoxes();
int setIndex = unitBoxes.indexOf(branchBox);
oneBranch = (FlowSet) branchOuts.get(setIndex);
}
} // end TABLESWITCHSTMT
LOOKUPSWITCHSTMT: {
if (s instanceof LookupSwitchStmt) {
LookupSwitchStmt lookup = (LookupSwitchStmt) s;
Value keyV = lookup.getKey();
Constant keyC = SEvaluator.getFuzzyConstantValueOf(keyV, localToConstant);
// flow all branches
if (keyC instanceof BottomConstant) {
stmtToReplacement.remove(lookup);
break LOOKUPSWITCHSTMT;
}
// no flow
if (keyC instanceof TopConstant) {
return;
}
// flow all branches
if (!(keyC instanceof IntConstant)) {
break LOOKUPSWITCHSTMT;
}
/* find the one branch we need to flow to */
conservative = false;
int index = lookup.getLookupValues().indexOf(keyC);
UnitBox branchBox = null;
if (index == -1) {
branchBox = lookup.getDefaultTargetBox();
} else {
branchBox = lookup.getTargetBox(index);
}
GotoStmt gotoStmt = Jimple.v().newGotoStmt(branchBox);
stmtToReplacement.put(lookup, gotoStmt);
List unitBoxes = lookup.getUnitBoxes();
int setIndex = unitBoxes.indexOf(branchBox);
oneBranch = (FlowSet) branchOuts.get(setIndex);
}
} // end LOOKUPSWITCHSTMT
// conservative control flow estimates
if (conservative) {
fall = s.fallsThrough();
branch = s.branches();
}
if (fall) {
Iterator fallOutIt = fallOut.iterator();
while (fallOutIt.hasNext()) {
FlowSet fallSet = (FlowSet) fallOutIt.next();
fallSet.union(fin);
}
}
if (branch) {
Iterator branchOutsIt = branchOuts.iterator();
while (branchOutsIt.hasNext()) {
FlowSet branchSet = (FlowSet) branchOutsIt.next();
branchSet.union(fin);
}
}
if (oneBranch != null) {
oneBranch.union(fin);
}
}
/**
* Returns (Unit, Constant) pair if an assumption has changed due to the fact that u is reachable. Else returns null.
**/
protected Pair processDefinitionStmt(Unit u) {
if (!(u instanceof DefinitionStmt)) {
return null;
}
DefinitionStmt dStmt = (DefinitionStmt) u;
Local local;
{
Value value = dStmt.getLeftOp();
if (!(value instanceof Local)) {
return null;
}
local = (Local) value;
}
/* update assumptions */
Value rightOp = dStmt.getRightOp();
Constant constant = SEvaluator.getFuzzyConstantValueOf(rightOp, localToConstant);
if (!merge(local, constant)) {
return null;
}
return new Pair(u, localToConstant.get(local));
}
/**
* Verifies if the given assumption "constant" changes the previous assumption about "local" and merges the information
* into the localToConstant map. Returns true if something changed.
**/
protected boolean merge(Local local, Constant constant) {
Constant current = localToConstant.get(local);
if (current instanceof BottomConstant) {
return false;
}
if (current instanceof TopConstant) {
localToConstant.put(local, constant);
return true;
}
if (current.equals(constant)) {
return false;
}
// not equal
localToConstant.put(local, BottomConstant.v());
return true;
}
}