/* Soot - a J*va Optimization Framework * Copyright (C) 2003 Navindra Umanee <navindra@cs.mcgill.ca> * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package soot.shimple.toolkits.scalar; import soot.*; import soot.util.*; import soot.options.*; import soot.jimple.*; import soot.shimple.*; import soot.toolkits.scalar.*; import soot.toolkits.graph.*; import java.util.*; import soot.shimple.toolkits.scalar.SEvaluator.MetaConstant; import soot.shimple.toolkits.scalar.SEvaluator.TopConstant; import soot.shimple.toolkits.scalar.SEvaluator.BottomConstant; /** * 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 <a * href="http://citeseer.nj.nec.com/cytron91efficiently.html">Efficiently * Computing Static Single Assignment Form and the Control Dependence * Graph</a> **/ public class SConstantPropagatorAndFolder extends BodyTransformer { 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()) G.v().out.println("[" + 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<Local, Constant> localToConstant) { Chain units = sb.getUnits(); Chain locals = sb.getLocals(); ShimpleLocalDefs localDefs = new ShimpleLocalDefs(sb); ShimpleLocalUses localUses = new ShimpleLocalUses(sb); Iterator localsIt = locals.iterator(); while(localsIt.hasNext()){ Local 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) G.v().out.println("Warning: 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) G.v().out.println("Warning: 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<IfStmt> deadStmts) { Chain units = sb.getUnits(); Iterator<IfStmt> 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<Stmt, GotoStmt> stmtsToReplace) { Chain units = sb.getUnits(); Iterator<Stmt> 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: * * <p> 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. * * <p> For example, if <tt>q</tt> is found to be not a constant (Bottom) * in <tt>if(q == 0) goto label1</tt> then both edges leaving the the * statement are considered executable, if <tt>q</tt> is found to be a * constant then only one of the edges are executable. * * <p> 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). * * <p> Perhaps the crucial point is that if a node such as <tt>x = * Phi(x_1, x_2)</tt> is ever found, information on <tt>x</tt> is * assumed as follows: * * <ul> * <li>If <tt>x_1</tt> and <tt>x_2</tt> are the same assumed * constant, <tt>x</tt> is assumed to be that constant. If they are * not the same constant, <tt>x</tt> is Bottom.</li> * * <li>If either one is Top and the other is a constant, <tt>x</tt> * is assumed to be the same as the known constant.</li> * * <li>If either is Bottom, <tt>x</tt> is assumed to be Bottom.</li> * </ul> * * <p> The crucial point about the crucial point is that if * definitions of <tt>x_1</tt> or <tt>x_2</tt> are never reached, the * Phi node will still assume them to be Top and hence they will not * influence the decision as to whether <tt>x</tt> 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<Local, Constant> localToConstant; /** * A map from conditional branches to their possible replacement * unit, an unconditional branch. **/ protected Map<Stmt, GotoStmt> stmtToReplacement; /** * A list of IfStmts that always fall through. **/ protected List<IfStmt> deadStmts; /** * Returns the localToConstant map. **/ public Map<Local, Constant> getResults() { return localToConstant; } /** * Returns the list of fall through IfStmts. **/ public List<IfStmt> getDeadStmts() { return deadStmts; } /** * Returns a Map from conditional branches to the unconditional branches * that can replace them. **/ public Map<Stmt, GotoStmt> getStmtsToReplace() { return stmtToReplacement; } public SCPFAnalysis(UnitGraph graph) { super(graph); emptySet = new ArraySparseSet(); stmtToReplacement = new HashMap<Stmt, GotoStmt>(); deadStmts = new ArrayList<IfStmt>(); // initialise localToConstant map -- assume all scalars are // constant (Top) { Chain locals = graph.getBody().getLocals(); Iterator localsIt = locals.iterator(); localToConstant = new HashMap<Local, Constant>(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. * * <p> 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; } }