/* 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.trans; import java.util.*; import EDU.purdue.cs.bloat.cfg.*; import EDU.purdue.cs.bloat.editor.*; import EDU.purdue.cs.bloat.ssa.*; import EDU.purdue.cs.bloat.tbaa.*; import EDU.purdue.cs.bloat.tree.*; import EDU.purdue.cs.bloat.util.*; /** * Perform partial redundancy elimination of a CFG in SSA form using the * SSA-based algorithm described in: * * <pre> * Fred Chow, Sun Chan, Robert Kennedy, Shin-Ming Liu, Raymond Lo, * and Peng Tu, "A New Algorithm for Partial Redundancy Elimination * based on SSA Form", Proc. PLDI '97: 273-286, 1997. * </pre> * * NOTE: The type for all occurrences of an inserted variable is the same as the * type of the first occurrence of the expression the variable replaces. This * type is guaranteed since we only group expression with equal types. */ /* * Okay, you asked for it: SSAPRE. SSAPRE stands for Partial Redundency * Elimination in Static Single-Assignment form. Partial Redundency Elimination * invovles finding multiple occurreces of the same expressions (i.e. * expressions that are lexically equivalent) and moving those occurrences up * higher in the CFG so that the expression is evaluated fewer times. * Occurrences of the expression that has been moved are replaced with a * temporary variable that is assigned the value of the moved expression. PRE is * a superset of Common Subexpression Elimination. * * The Golden Rule of SSAPRE: Move expressions to eliminate redundent * evaluations, but do not make more work along a given path in the CFG by * introducing an expression along a path where it originally was not. * * PRE techniques have been around for quite sometime, but most algorithms used * a bit vector notation to represent occurrences of an expression. In order to * apply those PRE techniques, the CFG in SSA form would have to be transformed * into bit vector notation before PRE could be performed. The modified bit * vectors would then have to be transformed back into a CFG in SSA form before * any other optimizations (or code generation) could take place. * * That was the way life was until [Chow, et. al.] came along. At the 1997 PLDI * conference, they presented a paper that showed how PRE could be performed * directly on a CFG in SSA form. The paper itself is a rather difficult read. * So don't feel bad if you don't understand it on the first (or seventeenth) * read. * * BLOAT performs SSAPRE on the CFG for optimizable methods. Due to some of * Java's quirks (e.g. exceptions), this implementation does not follow Chow's * exactly. However, it is very close and understanding this implementation will * aid the understanding of Chow and vice versa. * * The first step in SSAPRE is to create a worklist of all expressions that are * candidates for SSAPRE. Only first-order expressions are entered into the * worklist. First-order expressions consist of a single operators and two * operands that are either a variable or have a constant value. For instance, * a+b is a first-order expression, while a+b-c is not. The first-order * expressions that are inserted into the worklist are "real occurrences" of the * expression (as opposed to PHI-statements or PHI-operands, as we shall shortly * see). In addition to real occurrences of the expression, the worklist also * contains kill statements. Certain constructs in Java, such as exceptions, * method calls, and synchronized blocks, limit the assumptions one can make * about the behavior of the code. Thus, at places in the code where such * constructs occur, kills are inserted to say "We cannot move code across this * point." The above is performed in collectOccurrences(). * * SSAPRE is then performed on each expression in the worklist. The first step * in SSAPRE is to place PHI-functions. PHI-functions are similar to the * phi-functions that occur in converting a CFG to SSA form. Essentially, a * PHI-function is placed at a block (node in the CFG) at which a join occurs * (i.a. there are two paths that can enter the block), at which the expression * is available on at least one of the incoming paths, and at which the * expression will be used again (e.g. we don't bother putting a PHI-function at * the "exit" node). This leads us to place PHI-functions in two situations. * First, for each block that contains a real occurrence of the expression, a * PHI-function is placed at every block in its iterated dominance frontier * (DF+). Second, a PHI-function is placed in every block that contains a * phi-function for either of the operands to the expression. At this point, the * operands to the PHI-functions are unknown. They are of the form: * * h = PHI(h, h, ..., h) * * and are referred to as PHI-statements (also called "expression-PHIs" in * Chow's paper). The method placePhis() inserts these hypothetical * PHI-functions into the CFG. * * Once the expression-PHIs have been placed, version numbers are assigned to * each hypothetical h. Both real occurrences of the expression and * PHI-statements for the expression have a numbered h value associated with * them. Occurrences that have the same version number have identical values and * any control flow path that includes two difference h-versions must cross an * assignment to an operand of the expression or an PHI-statement for h. The * version numbers are assigned in two passes over the CFG. The first pass * compiles of a worklist containing all of the real occurrences that are * defined by an PHI-statement. It also maintains a stack of real occurrences of * the expression. The first pass optimistically assumes that versions of the * operands to a PHI-function are the same as the version of the expression that * is on top of the expression stack. The second pass corrects any false * assumptions that were made during the first pass. Since the first pass saw * all of the occurrences, the versions of the h-variables can be determined * from the existence of a phi-function (note lowercase phi) at that block. In * some cases, the occurrence may be placed back into the worklist for further * processing. The rename() method handles the assignment of version numbers * using this two-pass method. * * Now that the PHI-functions have been placed and version numbers have been * assigned, other information about the hypotheticals is extracted and will be * used to move redundent occurrences of the expression. The first piece of * information that is calculated is whether or not an PHI-statement is * "down-safe" (also referred to as being "anticipated"). When version numbers * were assigned to hypotheticals, a use-def relationship was created: * PHI-statements use operands that are hypotheticals defined by other * occurrences. One can conceptualize a directed graph consisting of * PHI-statements and the directed edges going from a use of a hypothetical to * its definition. This graph is referred to by Chow as the "SSA Graph". (This * is not to be confused with the class SSAGraph which models the SSA Graph for * variables, not expressions. This implementation does not directly model the * SSA Graph.) Using the SSA Graph the down-safety of an PHI-statement is * computed by backwards propagation along the use-def chains. An PHI-statement * is not down-safe if there is a control flow path from that PHI-statement * along which the expression is not evaluated (i.e. there is no real * occurrence) before the exit block is encountered or one of the expression's * variables is redefined. Why is down-safety important? If an PHI-statement is * not down-safe, it is not worthwhile to hoist it any higher in the code. If it * were to be hoisted, it might add a unnecessary evaluation of the expression * along some path. This would break the golden rule of SSAPRE. There are two * situations in which an PHI-statement is not down-safe. The first occurs when * there is no path to exit along which the result (left hand side) of the * PHI-statement is not used. The second occurs when there is a path to exit * along which the only use of the PHI-statement result is as an operand to an * PHI-statement which itself is not down-safe. The PHI-statement that fit the * first criterion can be marked as not down-safe during the renaming step. In * addition, each operand to an PHI-statement (called an PHI-operand) is marked * as "has real use" when the path to the PHI-operand crosses a real occurrence * of the same version of the expression. Simply put, an PHI-operand has a real * use, if it is associated with a real expression instead of an PHI-statement. * The down-safety of the PHI-statements in the CFG is computed using of the * downSafety() and resetDownSafe() methods. * * The above description was written by Dave and Steve Lennon in early September * of 1998. I'm surprised at how much we knew then. Consult the BLOAT Book for * the conclusion to our exciting SSAPRE saga. */ public class SSAPRE { public static boolean DEBUG = false; public static boolean NO_THREAD = false; // Do we ignore threads? public static boolean NO_PRECISE = false; // Are exceptions not precise? public static boolean NO_ACCESS_PATHS = false; protected FlowGraph cfg; // CFG on which to perform SSAPRE protected int nextValueNumber; // Next value number to assign protected EditorContext context; protected ResizeableArrayList[] kills; protected boolean[] killsSorted; protected SideEffectChecker sideEffects; protected ExprWorklist worklist; // Worklist containing expr to analyze // Maps phi statements together as to allow for access path reduction? protected HashMap phiRelated; /** * Constructor. * * @param cfg * Control flow graph on which to perform SSA-based PRE. * @param context The EditorContext containing all the classes that BLOAT * knows about. */ public SSAPRE(final FlowGraph cfg, final EditorContext context) { this.cfg = cfg; this.context = context; } /** * Performs SSA-based partial redundency elimination (PRE) on a control flow * graph. */ public void transform() { sideEffects = new SideEffectChecker(context); kills = new ResizeableArrayList[cfg.size()]; killsSorted = new boolean[cfg.size()]; for (int i = 0; i < kills.length; i++) { kills[i] = new ResizeableArrayList(); killsSorted[i] = false; } // In a single pass over the CFG: // // Number the expressions in each block in ascending order. // Insert all first-order expressions into the worklist. // Insert access path kills into the worklist. // Insert exception throw kills into the worklist. // Locate phi-related expressions. // Find the next value number. worklist = new ExprWorklist(); phiRelated = new HashMap(); // Compile the worklist of expressions on which to perform SSAPRE. collectOccurrences(); // Do the transformation for each expression. while (!worklist.isEmpty()) { final ExprInfo exprInfo = worklist.removeFirst(); transform(exprInfo); } // null these guys so that they'll be garbage collected sooner sideEffects = null; kills = null; worklist = null; } /** * Performs partial redundency elimination on a given expression. This * method is called on every lexically-distinct expression in a method. * * @see #collectOccurrences * * @see #placePhis * @see #rename * @see #downSafety * @see #willBeAvail * @see #finalize */ private void transform(final ExprInfo exprInfo) { if (SSAPRE.DEBUG) { System.out.println("PRE for " + exprInfo.prototype() + " -------------------------"); } if (exprInfo.numUses() == 0) { if (SSAPRE.DEBUG) { System.out.println("Skipping...all occurrences are " + "as targets. -------------------------"); } exprInfo.cleanup(); return; } if (SSAPRE.DEBUG) { System.out.println("Placing Phis for " + exprInfo.prototype() + " -------------------------"); } // Place the PHI nodes for the expression. Note that these PHI nodes are // for expressions, not variables. However, the same Phi classes are // used. placePhis(exprInfo); if (SSAPRE.DEBUG) { exprInfo.print(); System.out.println("Renaming for " + exprInfo.prototype() + " -------------------------"); } // Calculate version numbers for each occurrence of the expression // in exprInfo. Rename occurrences that have the same version number. rename(exprInfo); if (SSAPRE.DEBUG) { exprInfo.print(); System.out.println("Down safety for " + exprInfo.prototype() + " -------------------------"); } // Determine which PHI-nodes are "down safe". "Down safe" nodes are used // at least once on all paths from the PHI-node to the exit node. downSafety(exprInfo); if (SSAPRE.DEBUG) { System.out.println("Will be available for " + exprInfo.prototype() + " -------------------------"); } // Determine at which PHI-nodes the expression in exprInfo will be // available after code insertions are performed. Code can only be // inserted at the end of the predacessor blocks of these nodes. willBeAvail(exprInfo); if (SSAPRE.DEBUG) { System.out.println("Finalize for " + exprInfo.prototype() + " -------------------------"); } finalize(exprInfo); if (SSAPRE.DEBUG) { System.out.println("Code motion for " + exprInfo.prototype() + " -------------------------"); } final Type type = exprInfo.prototype().type(); final LocalVariable v = cfg.method().newLocal(type); final VarExpr tmp = new LocalExpr(v.index(), type); final SSAConstructionInfo consInfo = new SSAConstructionInfo(cfg, tmp); codeMotion(exprInfo, tmp, consInfo); if (SSAPRE.DEBUG) { System.out.println("Performing incremental SSA for " + exprInfo.prototype() + " -------------------------"); } // OK, this shouldn't be necessary. We should construct the SSA // form for t as we do code motion using the expr-phis. But that was // quite buggy in the early implementations (and probably still is), // so I just build SSA form in another pass. If you change it to // build SSA form for t during code motion, you must also remove // the phis not in the IDF of the defs of t and fix up the FUD chains // afterward. SSA.transform(cfg, consInfo); // Set the value numbers for all the new exprs. // This uses the occurrences of the var and the var-phi information // added to consInfo by SSA construction. setValueNumbers(consInfo); // Add parents of the real occurrences to the var. enqueueParents(consInfo); if (SSAPRE.DEBUG) { exprInfo.print(); System.out.println("Done with PRE for " + exprInfo.prototype() + " -------------------------"); } // Null out all the pointers in the exprInfo in case the exprInfo // is still reachable. exprInfo.cleanup(); } /** * Visits the CFG and for each lexically-distinct first-order expression * whose subexpressions no have subexpressions nor side effects (that is, * expressions containing one operatand and comprised of only local * variables and/or constants), places all occurrences of that expression, * sorted by their pre-order positions in the CFG, into a worklist. * * Note that only real occurrences of expression are inserted into the * worklist. PHI and PHI-operand occurrences have not been placed yet. * * Additionally, Kill expressions are placed in the worklist to indicate * boundaries across which code cannot be hoisted. */ private void collectOccurrences() { // count represents the preorder number for each expression. It is // assigned to each expression's key final Int count = new Int(); // maxValue is the maximum value number encountered on a traversal // of the expression tree. It is used to determine this.nextValueNumber final Int maxValue = new Int(); // A Set of Blocks that begin a protected region final Set beginTry = beginTry(); // Visit each node in the CFG. At each Expr node make note of the // node's preorder number. Keep track of the largest value number // encountered. Add Kills to the worklist when necessary. Add // first-order real occurrences of an expression to the worklist at // MemRefExpr (access paths) and Expr (expression) nodes. cfg.visit(new TreeVisitor() { public void visitBlock(final Block block) { if (beginTry.contains(block)) { // If the block begins a protected region, then we must // insert // a Kill to prevent hoisting out of the region. worklist.addKill(block, new ExceptionKill(count.value++)); } block.visitChildren(this); } public void visitPhiStmt(final PhiStmt stmt) { if (maxValue.value < stmt.valueNumber()) { maxValue.value = stmt.valueNumber(); } stmt.visitChildren(this); final Iterator iter = stmt.operands().iterator(); // Iterate over all of the operands to the phi node. // Make special note of local (or stack) variables. while (iter.hasNext()) { final Expr operand = (Expr) iter.next(); if (operand instanceof VarExpr) { if (operand.def() != null) { phiRelatedUnion(operand.def(), stmt.target()); } } } } public void visitConstantExpr(final ConstantExpr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } expr.setKey(count.value++); } public void visitVarExpr(final VarExpr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } expr.setKey(count.value++); } public void visitCatchExpr(final CatchExpr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } expr.visitChildren(this); expr.setKey(count.value++); worklist.addKill(expr.block(), new ExceptionKill(expr.key())); } public void visitMonitorStmt(final MonitorStmt stmt) { if (maxValue.value < stmt.valueNumber()) { maxValue.value = stmt.valueNumber(); } if (!SSAPRE.NO_THREAD) { stmt.visitChildren(this); stmt.setKey(count.value++); worklist.addKill(stmt.block(), new MemRefKill(stmt.key())); } } public void visitCallExpr(final CallExpr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } expr.visitChildren(this); expr.setKey(count.value++); worklist.addKill(expr.block(), new MemRefKill(expr.key())); } public void visitMemRefExpr(final MemRefExpr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } boolean firstOrder = isFirstOrder(expr); if (!firstOrder) { expr.visitChildren(this); } expr.setKey(count.value++); if (expr.isDef()) { worklist.addKill(expr.block(), new MemRefKill(expr, expr .key())); } if (firstOrder) { worklist.addReal(expr); } } public void visitStmt(final Stmt stmt) { if (maxValue.value < stmt.valueNumber()) { maxValue.value = stmt.valueNumber(); } stmt.visitChildren(this); } public void visitExpr(final Expr expr) { if (maxValue.value < expr.valueNumber()) { maxValue.value = expr.valueNumber(); } if (isFirstOrder(expr)) { worklist.addReal(expr); } else { expr.visitChildren(this); } expr.setKey(count.value++); } }); nextValueNumber = maxValue.value + 1; } /** * Returns a Set of Blocks that begin the protected regions in the CFG. */ private Set beginTry() { final Set beginTry = new HashSet(); final Iterator blocks = cfg.catchBlocks().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Handler handler = (Handler) cfg.handlersMap().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(cfg.preds(prot)); } p.removeAll(handler.protectedBlocks()); // Add the protected region blocks which have preds outside the // region to the beginTry set. final Iterator preds = p.iterator(); while (preds.hasNext()) { final Block pred = (Block) preds.next(); beginTry.addAll(cfg.succs(pred)); } } } return beginTry; } private void enqueueParents(final SSAConstructionInfo consInfo) { final Set seen = new HashSet(); final Iterator iter = cfg.nodes().iterator(); while (iter.hasNext()) { final Block block = (Block) iter.next(); final Iterator e = consInfo.realsAtBlock(block).iterator(); while (e.hasNext()) { final VarExpr real = (VarExpr) e.next(); Node p = real.parent(); if ((p instanceof StoreExpr) && real.isDef()) { p = p.parent(); } if ((p instanceof Expr) && !seen.contains(p)) { final Expr expr = (Expr) p; seen.add(p); if (isFirstOrder(expr)) { worklist.addReal(expr); } } } } } private void setValueNumbers(final SSAConstructionInfo consInfo) { // Compute value numbers using the RPO algorithm \cite{Simpson96}. // For such a small set of numbers this should be faster than // recomputing the strongly connected components of the entire SSA // graph and using the SCC-based algorithm. boolean changed = true; while (changed) { changed = false; final List postOrder = cfg.postOrder(); final ListIterator iter = postOrder.listIterator(postOrder.size()); while (iter.hasPrevious()) { final Block block = (Block) iter.previous(); final PhiStmt phi = consInfo.phiAtBlock(block); if (phi != null) { if (phi.target().valueNumber() == -1) { phi.target().setValueNumber(nextValueNumber++); changed = true; } final Iterator operands = phi.operands().iterator(); while (operands.hasNext()) { final VarExpr operand = (VarExpr) operands.next(); if (operand == null) { continue; } final VarExpr def = (VarExpr) operand.def(); if (def == null) { if (operand.valueNumber() == -1) { operand.setValueNumber(nextValueNumber++); changed = true; } continue; } if (def.valueNumber() == -1) { def.setValueNumber(nextValueNumber++); changed = true; } if (def.valueNumber() != operand.valueNumber()) { operand.setValueNumber(def.valueNumber()); changed = true; } } } final Iterator e = consInfo.realsAtBlock(block).iterator(); while (e.hasNext()) { final VarExpr real = (VarExpr) e.next(); if (real.isDef()) { Assert.isTrue(real.parent() instanceof StoreExpr); final StoreExpr store = (StoreExpr) real.parent(); final Expr rhs = store.expr(); if (rhs.valueNumber() == -1) { // This should only happen with hoisted stores. rhs.setValueNumber(nextValueNumber++); changed = true; } if (store.valueNumber() != rhs.valueNumber()) { // This should only happen with hoisted stores. store.setValueNumber(rhs.valueNumber()); changed = true; } if (real.valueNumber() != rhs.valueNumber()) { real.setValueNumber(rhs.valueNumber()); changed = true; } } else { final VarExpr def = (VarExpr) real.def(); if (def == null) { if (real.valueNumber() == -1) { real.setValueNumber(nextValueNumber++); changed = true; } continue; } if (def.valueNumber() == -1) { // This shouldn't happen. def.setValueNumber(nextValueNumber++); changed = true; } if (def.valueNumber() != real.valueNumber()) { real.setValueNumber(def.valueNumber()); changed = true; } } } } } final Iterator iter = cfg.nodes().iterator(); while (iter.hasNext()) { final Block block = (Block) iter.next(); final PhiStmt phi = consInfo.phiAtBlock(block); if (phi != null) { final Iterator operands = phi.operands().iterator(); while (operands.hasNext()) { final Expr operand = (Expr) operands.next(); if (operand instanceof VarExpr) { if (operand.def() != null) { phiRelatedUnion(operand.def(), phi.target()); } } } } } } /** * A PHI-function (different from a phi-function) is needed whenever * different values of the same expression reach a common point in the * program. A PHI is inserted in a block in two different situations: * * 1) Place PHI at the expression's iterated dominance frontier (DF+) 2) * When there is a phi for a variable contained in the expression (this * indicates an alteration in the expression) * * It is only necessary to insert a PHI at a merge point when the expression * will occur again after that block. */ private void placePhis(final ExprInfo exprInfo) { // Place Phis for each expression at the iterated dominance // frontier of the blocks containing the expression. // w contains all of the blocks in which the expression occurs final ArrayList w = new ArrayList(cfg.size()); Iterator blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); if (exprInfo.occurrencesAtBlock(block).size() > 0) { w.add(block); } } // The iterated dominance frontier for all of the blocks containing // the expression. Will ultimately contain all of the places at which // PHI-function need to be inserted. final Set df = new HashSet(cfg.iteratedDomFrontier(w)); // Set of phi functions for the variables in the expression final ArrayList worklist = new ArrayList(); // Set of phi functions that have ever been added to the worklist. // When blocks in the worklist are processed, they are removed. // inWorklist ensures that a block is not processed more than once. final Set inWorklist = new HashSet(); // For each variable occurrence in exprInfo, place a Phi where // there is a phi for the variable. blocks = cfg.nodes().iterator(); // Iterate over every block in the method and make a worklist of all // phi functions that define one of the variables in this expression. while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Iterator e = exprInfo.realsAtBlock(block).iterator(); while (e.hasNext()) { final Expr real = (Expr) e.next(); real.visit(new TreeVisitor() { public void visitVarExpr(final VarExpr var) { final Expr def = var.def(); if (def == null) { return; } final Node p = def.parent(); if ((p instanceof PhiStmt) && !inWorklist.contains(p)) { worklist.add(p); inWorklist.add(p); } } }); } } // Go through the worklist and add the blocks containing the // phi-functions to list of blocks to which to add PHI-functions. // Also, examine each operand to the phi-function and add it to // the worklist if it itself is defined by a phi-function. while (!worklist.isEmpty()) { final PhiStmt phi = (PhiStmt) worklist.remove(worklist.size() - 1); df.add(phi.block()); final Iterator iter = phi.operands().iterator(); while (iter.hasNext()) { final Expr expr = (Expr) iter.next(); if (expr instanceof VarExpr) { final VarExpr var = (VarExpr) expr; final Expr def = var.def(); if (def == null) { continue; } final Node p = def.parent(); if ((p instanceof PhiStmt) && !inWorklist.contains(p)) { worklist.add(p); inWorklist.add(p); } } } } // df contains all of the blocks in which an PHI-statement for the // expression should be added. Add them to the exprInfo. final Iterator iter = df.iterator(); while (iter.hasNext()) { final Block block = (Block) iter.next(); exprInfo.addPhi(block); } } /** * Rename all occurrences of the expression. placePhis went through the CFG * and placed PHI-functions at merge blocks in the code. Now, we have to go * through and assign version numbers to all of the "h" variables generated * by the PHI-functions. * <p> * There are two methods outlined in [Chow 1997]. The first is more * straightforward (ya right, it took us two days to figure it out) while * the second is more space efficient. The second method delays the renaming * of the "h" variables. It makes two passes over the CFG (actually, they * are preorder traversals of the dominator tree). * <p> * The first pass builds a worklist containing all of the real occurrences * that are defined by a PHI for a given expression. We optimisitically * assume that versions of PHI operands are the same as the version on top * of the expression stack. These assumptions are checked for correctness * during the second pass. * <p> * The second pass performs the correct renaming. It relies on seeing a * later occurrence of the expression. That is, it implies that at the * earlier PHI, the expression is partially anticipated. The second pass * operates on all of the real occurrences in the worklist built in the * first pass. From the versions of the variables at the merge block of a * PHI, the versions of the variables at each predacessor block are * determined based on the presence or absence of a phi-function for the at * that merge block. If the versions are different from the assumed versions * from the first pass, the operand is rest to null (bottom). Otherwise, the * operand is correct. If the PHI operand is also defined by a PHI, it is * added to the worklost and is handled later. */ // Rename all occurrences of the expression. This is done in two passes. // // The first pass assigns version numbers (FUD chain pointers really) in a // pre-order traversal of the dominator tree and builds the worklist for // pass 2. // // We optimistically assume that a Phi can be used as a definition for a // real and clean up in pass 2 by adjusting all the FUD chains if the // assumption proves false. // // NOTE: Renaming is where almost all previous PRE bugs have come from, so // when looking for a bug, it might be good to start looking here first. private void rename(final ExprInfo exprInfo) { // Renaming pass 1. This assigns version numbers (FUD chain // pointers really) in a pre-order traversal of the dominator tree // and builds the worklist for pass 2. final ArrayList renameWorklist = new ArrayList(); search(cfg.source(), exprInfo, null, null, renameWorklist); // Pass 2. // First, build another worklist which uses the leaves of the reals // on the old worklist. We extend this worklist later with the leaves // factored through var-phis. final HashSet seen = new HashSet(); final LinkedList leavesWorklist = new LinkedList(); final Iterator iter = renameWorklist.iterator(); while (iter.hasNext()) { // Examine each real occurrence that may need more work. // Construct a list of the operands of the real occurrence. We // should have already determined that the occurrence is first // order. So, if we hit anything other than a constant, local // variable, or stack expression, we have a problem. final Expr real = (Expr) iter.next(); final Phi phi = (Phi) exprInfo.def(real); // Keep track of operands of the real occurrence. final ArrayList leaves = new ArrayList(); real.visitChildren(new TreeVisitor() { public void visitStoreExpr(final StoreExpr expr) { // This should have been checked before adding // the real to the worklist. throw new RuntimeException(); } public void visitConstantExpr(final ConstantExpr expr) { leaves.add(expr); } public void visitVarExpr(final VarExpr expr) { leaves.add(expr.def()); } public void visitExpr(final Expr expr) { throw new RuntimeException(); } }); // Save the leaves for later use when building phi operands. phi.setLeaves(leaves); leavesWorklist.add(phi); } // Now we actually go about assigning version numbers to the // operands of the PHI-statement. If the operand is defined by a // real occurrence (RealDef), examine the children of the real // occurrence. while (!leavesWorklist.isEmpty()) { final Phi phi = (Phi) leavesWorklist.removeFirst(); phi.setLive(true); final List leaves = phi.leaves(); // Compare the leaves against what we expect for the Phi operands. final Iterator preds = cfg.preds(phi.block()).iterator(); PREDS: while (preds.hasNext()) { final Block pred = (Block) preds.next(); final Def operand = phi.operandAt(pred); if (operand instanceof RealDef) { final Expr expr = ((RealDef) operand).expr; final Bool match = new Bool(); match.value = true; final Iterator leafIter = leaves.iterator(); expr.visitChildren(new TreeVisitor() { public void visitExpr(final Expr expr) { throw new RuntimeException(); } public void visitStoreExpr(final StoreExpr expr) { expr.target().visit(this); } public void visitConstantExpr(final ConstantExpr expr) { visitLeaf(expr); } public void visitVarExpr(final VarExpr expr) { visitLeaf(expr); } public void visitLeaf(final Expr expr) { if (!leafIter.hasNext()) { // We've already examined all of the leaves, // they // don't match match.value = false; return; } Expr leaf = (Expr) leafIter.next(); // Factor the leaf through any var-phis there. That // is, // If the leaf is defined by a phi-statement, use // the // corresponding phi-operand as the leaf. If the // leaves // don't match (i.e. are not constants, variables, // nor // have the same value number), say so. if (leaf instanceof VarExpr) { Assert.isTrue(((VarExpr) leaf).isDef()); if (leaf.parent() instanceof PhiJoinStmt) { final PhiJoinStmt leafPhi = (PhiJoinStmt) leaf .parent(); if (leafPhi.block() == phi.block()) { leaf = leafPhi.operandAt(pred); } } } if (!(leaf instanceof ConstantExpr) && !(leaf instanceof VarExpr)) { match.value = false; return; } if (expr.valueNumber() != leaf.valueNumber()) { match.value = false; return; } } }); if (!match.value || leafIter.hasNext()) { // If the leaves do not match (or if we didn't get to // all // of the leaves), then we have a null PHI-operand // (bottom) and the operand does not have a real use. phi.setOperandAt(pred, null); phi.setHasRealUse(pred, false); } } else if (operand instanceof Phi) { final ArrayList newLeaves = new ArrayList(leaves.size()); final Phi opPhi = (Phi) operand; final Iterator leafIter = leaves.iterator(); // If the operand is defined by a PHI-statement, LEAVES: while (leafIter.hasNext()) { Expr leaf = (Expr) leafIter.next(); // Factor the leaf through a phi. if (leaf instanceof VarExpr) { Assert.isTrue(((VarExpr) leaf).isDef()); if (leaf.parent() instanceof PhiJoinStmt) { final PhiJoinStmt leafPhi = (PhiJoinStmt) leaf .parent(); if (leafPhi.block() == phi.block()) { leaf = leafPhi.operandAt(pred); } } } if (leaf instanceof VarExpr) { leaf = leaf.def(); if (leaf.block() == opPhi.block()) { if (leaf.parent() instanceof PhiJoinStmt) { newLeaves.add(leaf); continue LEAVES; } } else if (leaf.block().dominates(opPhi.block())) { newLeaves.add(leaf); continue LEAVES; } } // The leaf is defined after the operand. phi.setOperandAt(pred, null); phi.setHasRealUse(pred, false); continue PREDS; } Assert.isTrue(leaves.size() == newLeaves.size()); // If we got here, the real only uses leaves defined above // the operand. Add the operand to the worklist. final Pair pair = new Pair(phi, opPhi); if (!seen.contains(pair)) { seen.add(pair); opPhi.setLeaves(newLeaves); leavesWorklist.add(opPhi); } } } } // Remove the dead phis. final Iterator blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if ((phi != null) && !phi.live()) { if (SSAPRE.DEBUG) { System.out.println(" dead Phi at " + block); } exprInfo.removePhi(block); } } } class Pair { Object a; Object b; Pair(final Object a, final Object b) { this.a = a; this.b = b; } public boolean equals(final Object o) { return (o instanceof Pair) && ((Pair) o).a.equals(a) && ((Pair) o).b.equals(b); } public int hashCode() { return a.hashCode() ^ b.hashCode(); } } /** * This method performs the first pass of the delayed renaming algorithm. * Recall that the original renaming algorithm kept a stack for the * "current" version number of each variable used in the expression being * renamed. Well, when a real occurrence of the expression is encountered, * we don't need the stacks because the real occurrence contains the current * version numbers of the variables. So, we only need the version numbers * when renaming the "h" variables for PHI-operands. * * When this first pass encounters a PHI-operand, it optimistically assumes * that the version on top of the stack is the correct version. The "h" * values for real occurrences will be handled correctly. * * This implementation represents an occurrences "h" value by its Def (the * "setDef" method of ExprInfo). This method performs a pre-order traversal * of the CFG's dominator tree and assigns "names" (actually references to * Defs) to occurrences of an expression. * * The end result of this traversal is a worklist of real occurrences that * require further renaming. Along the way, we compute the down safety (or * lack there of) of some PHI-statements. * * @param block * The block in the CFG being traversed * @param exprInfo * The expression on which we are performing PRE. * @param top * The most recently encountered real occurrence of the * expression. It can be thought of as the "top" of a stack of * expressions. * @param topdef * top's Def. That is, its "h" value. */ // This pass is pretty much as described in Chow97 in the Delayed // Renaming section, except we have to handle kills for access paths // and for exceptions. // // Instead of using an explicit stack of occurrences, top points to // the occurrence at the top of the stack and topdef points to top's // def. top is null if topdef is a Phi and a real occurrence hasn't // followed it. Thus a Phi is not down safe if it is killed and top // is null. private void search(final Block block, final ExprInfo exprInfo, Expr top, Def topdef, final List renameWorklist) { if (SSAPRE.DEBUG) { System.out.println(" renaming in " + block); } final Phi phi = exprInfo.exprPhiAtBlock(block); // If there's a PHI in the block, make this PHI the new topdef. // if (phi != null) { top = null; topdef = phi; // If the expression has a stack variable, don't allow any // hoisting. // // To prevent hoisting, it is sufficient to make the phi not // down safe. If any operand of the phi is null and the phi is // not down safe, no hoisting will be attempted (see // canBeAvail). If an operand is non-null, then the expression // is already evaluated on that path and no hoisting should be // attempted. if (exprInfo.hasStackVariable()) { phi.setDownSafe(false); } // If the expression can throw an exception, don't allow any // hoisting. This is stricter than it should be. // // We can fix this for fields, divisions, and remainders with // a trick like: NullCheck(p).f or x / ZeroCheck(y). // // Array refs are more complicated since you need both the // array and the index checked. // // Don't bother if exceptions are not precise. if (!SSAPRE.NO_PRECISE && exprInfo.hasSideEffects()) { phi.setDownSafe(false); } } // If we hit the sink node, a phi at the top of the stack is not // down safe. if (block == cfg.sink()) { if ((topdef instanceof Phi) && (top == null)) { ((Phi) topdef).setDownSafe(false); } // The sink node has no real occurrences and no children in // the dominator tree. So, go home. return; } // Kill (nullify) topdef in catch blocks. This prevents hoisting into // protected regions. if (cfg.catchBlocks().contains(block)) { if ((topdef instanceof Phi) && (top == null)) { ((Phi) topdef).setDownSafe(false); } if (SSAPRE.DEBUG) { System.out.println("Top killed at catch " + block); } top = null; topdef = null; } // Go through all of the real occurrences (and any kills) in the // block in the order that they appear. final Iterator e = exprInfo.occurrencesAtBlock(block).iterator(); while (e.hasNext()) { final Object obj = e.next(); if (obj instanceof Kill) { if (topdef != null) { final Kill kill = (Kill) obj; if (SSAPRE.DEBUG) { System.out.println("Kill " + kill.expr); } boolean die = false; // If we have a memory reference (access path), we need to // check if the Kill could be an alias def for this // expression. if (exprInfo.prototype() instanceof MemRefExpr) { if (kill instanceof MemRefKill) { final MemRefExpr k = (MemRefExpr) kill.expr; final MemRefExpr p = (MemRefExpr) exprInfo .prototype(); if (kill.expr == null) { // If kill.expr is null, kill everything. die = true; } else if (TBAA.canAlias(context, k, p)) { die = true; } } } // If we haven't been killed yet, see if the kill is there // to prevent us from hoisting out of protected regions. // // This is possibly not necessary since if the exception // can be thrown outside the protected region, we won't get // here in the first place. Removing this code could give // us better results. if (!die && exprInfo.hasSideEffects()) { if (kill instanceof ExceptionKill) { // Just a kill to keep us from hoisting out of // a protected region or out of a handler. die = true; } } if (die) { if (SSAPRE.DEBUG) { System.out.println("Killed"); } if ((topdef instanceof Phi) && (top == null)) { ((Phi) topdef).setDownSafe(false); // Can't use it } top = null; topdef = null; } } continue; } // If we get here, we are dealing with a real occurrence of the // expression. Now we need to determine whether or not the real // occurrence matches the "h" value (definition) on top of the // "stack" (topdef). final Expr real = (Expr) obj; // If the real has a store in it, we can't reuse the def at // the top of stack, even if it is a Phi. Because something // got redefined inside the expression. final Bool hasStore = new Bool(); if (real.isDef()) { hasStore.value = true; } else { real.visit(new TreeVisitor() { public void visitStoreExpr(final StoreExpr expr) { hasStore.value = true; } public void visitExpr(final Expr expr) { if (!hasStore.value) { expr.visitChildren(this); } } }); } boolean matches = true; if (hasStore.value) { matches = false; if (SSAPRE.DEBUG) { System.out.println("real has store"); } } if (matches && (topdef == null)) { matches = false; if (SSAPRE.DEBUG) { System.out.println("null topdef"); } } if (matches && (topdef instanceof Phi)) { if (!matchesPhi(real, (Phi) topdef)) { // Some variable used in the real got redefined after the // PHI. So they'll have different values. matches = false; if (SSAPRE.DEBUG) { System.out.println("uses var defined after topdef"); } } } if (matches && (top != null)) { if (!matches(top, real)) { matches = false; if (SSAPRE.DEBUG) { System.out.println("mismatch " + top + " != " + real); } } } // If topdef does not match the real occurrence, then make the real // occurrence the new topdef. if (!matches) { if ((top == null) && (topdef instanceof Phi)) { // No real occurrence after the Phi, so the Phi is not down // safe. ((Phi) topdef).setDownSafe(false); } // We know that the real occurrence defines the expression final RealDef def = new RealDef(real); exprInfo.setDef(real, def); topdef = def; } else { // The operands of the real occurrence and the PHI-statement // match. So, the definition on top of the "stack" defines // the expression. Assert.isTrue(topdef != null); if (SSAPRE.DEBUG) { System.out.println("copying top def"); } if (topdef instanceof Phi) { // If the definition on top of the renaming stack is a // PHI-statement, the real occurrence may need more work. // Add it to the renameWorklist. renameWorklist.add(real); } // Copy the definition from the top of the stack. exprInfo.setDef(real, topdef); } top = real; } final Iterator succs = cfg.succs(block).iterator(); // Examine each successor block of the block being traversed. If // the block contains a PHI-statement, set the PHI-statement's // operand corresponding to the block being traversed to the // definition on top of the renaming stack. while (succs.hasNext()) { final Block succ = (Block) succs.next(); // If we hit the sink node, a Phi at the top of the stack is not // down safe. if (succ == cfg.sink()) { if ((top == null) && (topdef instanceof Phi)) { ((Phi) topdef).setDownSafe(false); } } final Phi succPhi = exprInfo.exprPhiAtBlock(succ); if (succPhi != null) { succPhi.setOperandAt(block, topdef); if (top != null) { succPhi.setHasRealUse(block, true); } } } final Iterator children = cfg.domChildren(block).iterator(); // Visit each child in the dominator tree. while (children.hasNext()) { final Block child = (Block) children.next(); search(child, exprInfo, top, topdef, renameWorklist); } } /** * This method determines whether or not a given (real occurrence of an) * expression has the same operands as the target of a PHI-statement. That * is, has one of the operands of the real occurrence changed since the the * PHI-statement? */ private boolean matchesPhi(final Expr real, final Phi phi) { final Bool match = new Bool(); match.value = true; real.visitChildren(new TreeVisitor() { public void visitExpr(final Expr expr) { if (match.value) { expr.visitChildren(this); } } public void visitStoreExpr(final StoreExpr expr) { // A store means a new SSA number, so they won't match match.value = false; } public void visitVarExpr(final VarExpr expr) { if (!match.value) { return; } // We're dealing with one of the operands of the real // occurrence. If the operand is defined by a phi-statement // that occurrs in the same block as the PHI-statement, then // the variable in the real occurrence is the same as that in // the PHI-statement. Similarly, is the block in which the // real occurrence's definition occurs dominate the block in // which the PHI-statement occurs, the two versions of the // variable are the same. Otherwise the variable has been // modified between the PHI-statement and the real occurrence. final VarExpr def = (VarExpr) expr.def(); if (def == null) { match.value = false; return; } final Block block = phi.block(); final Node p = def.parent(); if (block == p.block()) { // Anything other than a var-phi means the real occurrence // uses a variable defined after the Phi. if (p instanceof PhiJoinStmt) { return; } } else if (p.block().dominates(block)) { // The real uses a var defined above the phi. // This, too, is okay. return; } // The real uses a variable defined after the Phi. match.value = false; } }); return match.value; } /** * Compares two expressions and determines whether or not they match. */ private boolean matches(final Expr expr1, final Expr expr2) { final LinkedList leaves = new LinkedList(); expr1.visit(new TreeVisitor() { public void visitStoreExpr(final StoreExpr expr) { expr.target().visit(this); } public void visitConstantExpr(final ConstantExpr expr) { leaves.add(expr); } public void visitVarExpr(final VarExpr expr) { leaves.add(expr); } }); final Bool match = new Bool(); match.value = true; expr2.visit(new TreeVisitor() { public void visitExpr(final Expr expr) { if (match.value) { expr.visitChildren(this); } } public void visitStoreExpr(final StoreExpr expr) { if (match.value) { expr.target().visit(this); } } public void visitConstantExpr(final ConstantExpr expr) { visitLeaf(expr); } public void visitVarExpr(final VarExpr expr) { visitLeaf(expr); } public void visitLeaf(final Expr expr) { if (leaves.isEmpty()) { match.value = false; return; } final Expr leaf = (Expr) leaves.removeFirst(); if ((leaf == null) || (expr.valueNumber() != leaf.valueNumber())) { match.value = false; } } }); return match.value; } private Expr buildPhiOperand(final ExprInfo exprInfo, final Phi phi, final Block pred) { final Iterator leaves = phi.leaves().iterator(); final Expr expr = (Expr) exprInfo.prototype().clone(); expr.visit(new TreeVisitor() { public void visitExpr(final StoreExpr expr) { throw new RuntimeException(); } public void visitConstantExpr(final ConstantExpr expr) { visitLeaf(expr); } public void visitVarExpr(final VarExpr expr) { visitLeaf(expr); } public void visitLeaf(final Expr expr) { if (leaves.hasNext()) { Expr leaf = (Expr) leaves.next(); if (leaf instanceof VarExpr) { Assert.isTrue(((VarExpr) leaf).isDef()); if (leaf.parent() instanceof PhiJoinStmt) { final PhiJoinStmt leafPhi = (PhiJoinStmt) leaf .parent(); if (leafPhi.block() == phi.block()) { leaf = leafPhi.operandAt(pred); if (leaf instanceof VarExpr) { leaf = leaf.def(); } } } } Assert.isTrue(leaf != null); final Expr copy = (Expr) leaf.clone(); if (leaf.isDef()) { copy.setDef((VarExpr) leaf); } expr.replaceWith(copy); } else { throw new RuntimeException(); } } }); expr.setValueNumber(nextValueNumber++); return expr; } /** * A Phi is not down safe if there is a control flow path from that Phi * along which the expression is not evaluated before exit or being altered * by redefinition of one of the variables of the expression. This can * happen if: * * 1) There is a path to exit along which the Phi target is not used. 2) * There is a path to exit along which the Phi target is used only as the * operand of a non-down-safe Phi. */ private void downSafety(final ExprInfo exprInfo) { final Iterator blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if ((phi == null) || phi.downSafe()) { continue; } final Iterator e = cfg.preds(block).iterator(); while (e.hasNext()) { final Block pred = (Block) e.next(); resetDownSafe(phi, pred); } } } private void resetDownSafe(final Phi phi, final Block block) { if (phi.hasRealUse(block)) { return; } final Def def = phi.operandAt(block); if (def instanceof Phi) { final Phi phidef = (Phi) def; if (phidef.downSafe()) { phidef.setDownSafe(false); if (SSAPRE.DEBUG) { System.out.println(" def = Phi in " + phidef.block()); System.out.println(" def made not down safe"); } final Iterator e = cfg.preds(block).iterator(); while (e.hasNext()) { final Block pred = (Block) e.next(); resetDownSafe(phidef, pred); } } } } /** * Determines whether or not a PHI expression is "will be available". Will * be available determines where we end up placing an evaluation of the * expression. Will be available = Can be available AND (not Later) */ private void willBeAvail(final ExprInfo exprInfo) { computeCanBeAvail(exprInfo); computeLater(exprInfo); } /** * Can be available (cba) means "at this point, we can insert an evaluation * of the expression". If cba = 0, then the PHI-statement is "useless" and * uses of it are changed to tack (bottom). Can be available depends on the * down safety of the PHI-statement. */ private void computeCanBeAvail(final ExprInfo exprInfo) { final Iterator blocks = cfg.nodes().iterator(); // Go through every PHI-statement of the exprInfo. while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if (phi == null) { continue; } if (!phi.canBeAvail()) { continue; } if (phi.downSafe()) { continue; } final Iterator e = cfg.preds(block).iterator(); // We determined above that: // 1. This PHI-statement is not down safe // 2. It is currently can be available // Now, if one of the PHI-statement's operands is tack (null), // reset "can be avail" for this PHI-statement. while (e.hasNext()) { final Block pred = (Block) e.next(); final Def operand = phi.operandAt(pred); if (operand == null) { resetCanBeAvail(exprInfo, phi); break; } } } } /** * Resets the cba flag for a given PHI-expression and then iterates over the * PHI-statement's operands and resets them under certain conditions. */ private void resetCanBeAvail(final ExprInfo exprInfo, final Phi phi) { phi.setCanBeAvail(false); final Iterator blocks = cfg.nodes().iterator(); // Iterate over every PHI-statement, other, that uses the // the "h" defined by phi as an operand... while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi other = exprInfo.exprPhiAtBlock(block); if (other == null) { continue; } final Iterator e = cfg.preds(block).iterator(); while (e.hasNext()) { final Block pred = (Block) e.next(); final Def operand = other.operandAt(pred); // For each use of the "h" defined by exprInfo... if (operand == phi) { if (other.hasRealUse(pred)) { continue; } // If the use does not have a real use, set the use to tack // (bottom). other.setOperandAt(pred, null); // Since we changed other (by setting one of its operands to // tack), if other is not down safe, propagate this // information // back up the CFG by resetting its cba. if (!other.downSafe() && other.canBeAvail()) { resetCanBeAvail(exprInfo, other); } } } } } /** * Later basically says, "We cannot place an evaluation of the expression * any later that this point without adding additional evaluation(s) along * some path". An expression is "interesting" when later is false. */ private void computeLater(final ExprInfo exprInfo) { Iterator blocks = cfg.nodes().iterator(); // Initialize later to can be available... while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if (phi == null) { continue; } phi.setLater(phi.canBeAvail()); } blocks = cfg.nodes().iterator(); // Iterate over each PHI-statement, phi... while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if (phi == null) { continue; } if (!phi.later()) { continue; } final Iterator e = cfg.preds(block).iterator(); // If later == true and there is an operand of phi that: // 1. is not tack // 2. has a real use // set later to false and propagate this information back up the // CFG. // Basically, what we're saying is that if an operand of the // PHI-statement has a real use, we want to evaluate the expression // now. while (e.hasNext()) { final Block pred = (Block) e.next(); final Def operand = phi.operandAt(pred); if ((operand != null) && phi.hasRealUse(pred)) { resetLater(exprInfo, phi); break; } } } } /** * Resets later and propagates this information back up the CFG. */ private void resetLater(final ExprInfo exprInfo, final Phi phi) { phi.setLater(false); final Iterator blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi other = exprInfo.exprPhiAtBlock(block); if (other == null) { continue; } final Iterator e = cfg.preds(block).iterator(); // For PHI-statement that has the "h" defined by phi as an // operand... while (e.hasNext()) { final Block pred = (Block) e.next(); final Def operand = other.operandAt(pred); if (operand == phi) { if (!other.later()) { continue; } // Propagate later = false up the CFG. resetLater(exprInfo, other); break; } } } } /** * Finalize is the final step in preparing for the placement of temporaries * and evaluations of the expression. It decides whether the results of real * occurrences should be computed on the spot (and saved to a temporary) or * reloaded from a temporary. Some PHI-statements are removed and some are * replaced by PHI-statements operating on the temporaries. Additional * evaluations of the expression may be added where the expression is not * available. */ private void finalize(final ExprInfo exprInfo) { // We assume that all availDef for exprInfo are tack. // Perform a perorder traversal of the dominance tree. Remember that // the root of the dominance tree is also the root of the CFG. finalizeVisit(exprInfo, cfg.source(), null); } /** * * * @param exprInfo * The expression on which we're performing SSAPRE. * @param block * The block to search for occurrences of exprInfo. * @param top * Top is used to determine when an element of Avail_def * dominates a given occurrence. */ private void finalizeVisit(final ExprInfo exprInfo, final Block block, Def top) { if (SSAPRE.DEBUG) { System.out.println(" finalizing " + block); } // Get the (only) PHI-statement at the current block. If wba = 1 for // the PHI-statement, final Phi phi = exprInfo.exprPhiAtBlock(block); if (phi != null) { if (phi.willBeAvail()) { exprInfo.setAvailDef(phi, phi); top = phi; } else { top = null; } } final Iterator reals = exprInfo.realsAtBlock(block).iterator(); // Iterate over all of the real occurrences in the block. while (reals.hasNext()) { final Expr real = (Expr) reals.next(); if (SSAPRE.DEBUG) { System.out.println(" -----------"); } // Get defining "h" occurrence for the expression final Def def = exprInfo.def(real); Assert.isTrue(def != null, real + " is undefined"); // Get Avail_def[i][x] final Def availDef = exprInfo.availDef(def); // If Avail_def[i][x] == bottom (tack) // or Avail_def[i][x] does not dominate this occurrence of E[i] // Avail_def[i][x] = this occurrence of E[i] // // The statement (availDef != top) is equivalent to saying "availDef // does not dominate real". Why is this so? Top essentially keeps // track of the last PHI-statement we've seen. Thus, top will only // be changed when we encounter a PHI-statement. We only encounter // PHI-statements at join blocks, which are obviously not dominated // by a block (containing availDef) along one of its paths. if ((availDef == null) || (availDef != top)) { top = new RealDef(real); exprInfo.setAvailDef(def, top); } // If the available definition is a real occurrence, set its // save and reload flags else if (availDef instanceof RealDef) { exprInfo.setReload(real, true); exprInfo.setSave(((RealDef) availDef).expr, true); } else { Assert.isTrue(availDef instanceof Phi); exprInfo.setReload(real, true); } } final Iterator succs = cfg.succs(block).iterator(); // Iterate over each successor block in the CFG... while (succs.hasNext()) { final Block succ = (Block) succs.next(); final Phi succPhi = exprInfo.exprPhiAtBlock(succ); // If the PHI-statement is will be available, if (succPhi != null) { if (succPhi.willBeAvail()) { if (succPhi.canInsert(block)) { succPhi.setSaveOperand(block, true); } else { final Def operand = succPhi.operandAt(block); Assert.isTrue(operand != null); final Def availDef = exprInfo.availDef(operand); if (availDef instanceof RealDef) { exprInfo.setSave(((RealDef) availDef).expr, true); } } } } } final Iterator children = cfg.domChildren(block).iterator(); while (children.hasNext()) { final Block child = (Block) children.next(); finalizeVisit(exprInfo, child, top); } } private void codeMotion(final ExprInfo exprInfo, final VarExpr tmp, final SSAConstructionInfo consInfo) { final List[] targets = new List[cfg.size()]; Iterator blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final Phi phi = exprInfo.exprPhiAtBlock(block); if (phi != null) { final Iterator preds = cfg.preds(block).iterator(); while (preds.hasNext()) { final Block pred = (Block) preds.next(); if (!phi.saveOperand(pred)) { continue; } final Expr operand = buildPhiOperand(exprInfo, phi, pred); Assert.isTrue(operand != null); final VarExpr t = (VarExpr) tmp.clone(); t.setValueNumber(operand.valueNumber()); final StoreExpr store = new StoreExpr(t, operand, t.type()); store.setValueNumber(operand.valueNumber()); pred.tree().addStmtBeforeJump(new ExprStmt(store)); if (SSAPRE.DEBUG) { System.out.println("Created new store: " + store); } // Save the target for later since we need to add // it to consInfo last. final int predIndex = cfg.preOrderIndex(pred); if (targets[predIndex] == null) { targets[predIndex] = new ArrayList(); } targets[predIndex].add(t); if (SSAPRE.DEBUG) { System.out.println("insert at end of " + pred + ": " + store); } } } final Iterator e = exprInfo.realsAtBlock(block).iterator(); while (e.hasNext()) { final Expr real = (Expr) e.next(); if (exprInfo.save(real)) { if (!real.isDef()) { save(exprInfo, tmp, real, consInfo); } else { saveTarget(exprInfo, tmp, real, consInfo); } } else if (exprInfo.reload(real)) { Assert.isFalse(real.isDef(), "Can't reload a def: " + real + " in " + real.parent()); reload(exprInfo, tmp, real, consInfo); } } } blocks = cfg.nodes().iterator(); while (blocks.hasNext()) { final Block block = (Block) blocks.next(); final int blockIndex = cfg.preOrderIndex(block); if (targets[blockIndex] != null) { final Iterator iter = targets[blockIndex].iterator(); while (iter.hasNext()) { final VarExpr t = (VarExpr) iter.next(); consInfo.addReal(t); } } } } private void save(final ExprInfo exprInfo, final VarExpr tmp, final Expr real, final SSAConstructionInfo consInfo) { if (SSAPRE.DEBUG) { System.out.println("SAVE: " + real + " to " + tmp + "--------------------------------"); } if ((real instanceof CheckExpr) && exprInfo.hasStackVariable()) { // Check(x) leaves x on the stack. Do nothing. return; } // Replace expression // use x + e // with // use x + (t := e) // We must evaluate x before e. final Node parent = real.parent(); final VarExpr t = (VarExpr) tmp.clone(); t.setValueNumber(real.valueNumber()); final StoreExpr store = new StoreExpr(t, real, real.type()); store.setValueNumber(real.valueNumber()); parent.visit(new ReplaceVisitor(real, store)); consInfo.addReal(t); if (SSAPRE.DEBUG) { System.out.println("END SAVE--------------------------------"); } } private void reload(final ExprInfo exprInfo, final VarExpr tmp, final Expr real, final SSAConstructionInfo consInfo) { if (SSAPRE.DEBUG) { System.out.println("RELOAD: " + real + " to " + tmp + "--------------------------------"); } Expr t; if ((real instanceof CheckExpr) && exprInfo.hasStackVariable()) { // Check(x) leaves x on the stack. Replace with just x. t = ((CheckExpr) real).expr(); real.parent().visit(new ReplaceVisitor(real, t)); real.cleanupOnly(); } else { // Replace // use e // with // use t t = (VarExpr) tmp.clone(); t.setValueNumber(real.valueNumber()); real.replaceWith(t); consInfo.addReal((VarExpr) t); } if (SSAPRE.DEBUG) { System.out.println("reload t " + t + " in " + t.parent()); } if (SSAPRE.DEBUG) { System.out.println("END RELOAD--------------------------------"); } } private void saveTarget(final ExprInfo exprInfo, final VarExpr tmp, final Expr real, final SSAConstructionInfo consInfo) { if (SSAPRE.DEBUG) { System.out.println("SAVE TARGET: " + real + " to " + tmp + "--------------------------------"); } Assert.isTrue(real instanceof MemRefExpr); // Replace // a.b := c // with: // a.b := (t := c); final VarExpr t = (VarExpr) tmp.clone(); t.setValueNumber(real.valueNumber()); final StoreExpr store = (StoreExpr) real.parent(); final Expr rhs = store.expr(); final StoreExpr rhsStore = new StoreExpr(t, rhs, rhs.type()); rhsStore.setValueNumber(real.valueNumber()); store.visit(new ReplaceVisitor(rhs, rhsStore)); consInfo.addReal(t); if (SSAPRE.DEBUG) { System.out.println("save target " + store); } if (SSAPRE.DEBUG) { System.out.println("END SAVE TARGET------------------------------"); } } /** * Returns whether or not an expression is first-order. A first-order * expression has only one operator. For example, a+b is first-order. */ boolean isFirstOrder(final Expr expr) { final FirstOrderChecker f = new FirstOrderChecker(); expr.visit(f); return f.firstOrder; } /** * FirstOrderChecker is a TreeVistor that traverses an expression tree and * determines whether or not it is first order. A first order expression * Override all visitXXXExpr methods so that they do not visit children. We * only want to check the expr we first visit. */ private final class FirstOrderChecker extends TreeVisitor { boolean firstOrder = false; public void visitExpr(final Expr expr) { } /** * A leaf in the expression tree consists of an expression that only * references local variables, or an expression that is a constant, or * an expressions that stores into a local variable. */ private boolean isLeaf(final Expr expr) { if (expr instanceof StoreExpr) { return ((StoreExpr) expr).target() instanceof LocalExpr; } return (expr instanceof LocalExpr) || (expr instanceof ConstantExpr); } public void visitCheckExpr(final CheckExpr expr) { // UGLY: We special case RC and UC to allow stack variables since // they do not change the operand stack at all. However, since // they do contain stack variables, we cannot hoist these // expressions, but we can one eliminate them so long as they // are replaced with stack variables rather than locals. if (isLeaf(expr.expr()) || (expr.expr() instanceof StackExpr)) { firstOrder = true; } } /** * An arithmetic expression is first-order if both its left and right * operands are leaves. */ public void visitArithExpr(final ArithExpr expr) { if (isLeaf(expr.left()) && isLeaf(expr.right())) { firstOrder = true; } } /** * An ArrayLengthExpr is first-order when the array whose length is * being taken is expressed as a leaf. */ public void visitArrayLengthExpr(final ArrayLengthExpr expr) { if (isLeaf(expr.array())) { firstOrder = true; } } /** * An ArrayRefExpr is first order when both the array it references and * the index used to reference it are expressed as leaves. */ public void visitArrayRefExpr(final ArrayRefExpr expr) { if (SSAPRE.NO_ACCESS_PATHS) { return; } if (isLeaf(expr.array()) && isLeaf(expr.index())) { firstOrder = true; } } public void visitCastExpr(final CastExpr expr) { if (isLeaf(expr.expr())) { firstOrder = true; } } /** * If a field is volatile (meaning that a field may be changed by other * threads), a reference to it is not first order. I'm not too sure why * this makes any difference. */ public void visitFieldExpr(final FieldExpr expr) { if (SSAPRE.NO_ACCESS_PATHS) { return; } if (isLeaf(expr.object())) { try { final FieldEditor e = context.editField(expr.field()); if (!e.isVolatile()) { firstOrder = true; } context.release(e.fieldInfo()); } catch (final NoSuchFieldException e) { // A field wasn't found. Silently assume it's volatile. firstOrder = false; } } } public void visitInstanceOfExpr(final InstanceOfExpr expr) { if (isLeaf(expr.expr())) { firstOrder = true; } } public void visitNegExpr(final NegExpr expr) { if (isLeaf(expr.expr())) { firstOrder = true; } } public void visitShiftExpr(final ShiftExpr expr) { if (isLeaf(expr.expr()) && isLeaf(expr.bits())) { firstOrder = true; } } /** * Once again, an expression that references a volatile static field is * not first-order. * * @see #visitFieldExpr */ public void visitStaticFieldExpr(final StaticFieldExpr expr) { if (SSAPRE.NO_ACCESS_PATHS) { return; } try { final FieldEditor e = context.editField(expr.field()); if (!e.isVolatile()) { firstOrder = true; } context.release(e.fieldInfo()); } catch (final NoSuchFieldException e) { // A field wasn't found. Silently assume it's volatile. firstOrder = false; } } } /** * Wrapper classes that are used to simulate pass-by-reference. That is, * their values are changed inside methods. When used as parameters they * must be declared as being final. */ class Int { int value = 0; } class Bool { boolean value = false; } int next = 0; /** * Def represents a point at which a variable is defined. Each definition * has a version number associated with it. */ abstract class Def { int version = next++; } /** * RealDef represents a real occurrence of an expression. */ class RealDef extends Def { Expr expr; public RealDef(final Expr expr) { this.expr = expr; } public String toString() { return "[" + expr + "]_" + version; } } /** * Phi represents a PHI-statement (PHI-function) for merging an expression * along two paths. * <p> * Information about the operands to PHI-statements is maintained in the PHI * class. * <p> * A PHI-statement has the form: h = PHI(h, h) * * @see #operands */ class Phi extends Def { Block block; // Block in which the PHI-statement occurs // Note that arrays are indexed by a block's preorder number. Def[] operands; // Operand to the PHI-statement boolean[] hasRealUse; // Is the ith operand a real use? boolean[] saveOperand; boolean downSafe; // downsafe flag (ds) boolean canBeAvail; // can_be_available (cba) boolean later; // later flag (later) boolean live; List leaves; /** * Constructor. * * @param exprInfo * The expression that this PHI-statement is associated with. * @param block * The block in which this PHI-statement occurs. Note that an * PHI-statement can only occur in one block. */ public Phi(final ExprInfo exprInfo, final Block block) { this.block = block; final int size = cfg.size(); operands = new Def[size]; hasRealUse = new boolean[size]; saveOperand = new boolean[size]; leaves = null; downSafe = true; // Initially, ds = 1 canBeAvail = true; // Initially, cba = 1 later = true; // Initially, later = cba live = false; // Initially, live = 0 } /** * Returns the block in which this PHI-statement is occurs. */ public Block block() { return block; } /** * Sets the operands to a real occurrence of the expression. Leaves only * apply to PHI-statements that are associated with a real occurrence of * the expression. */ public void setLeaves(final List leaves) { if (SSAPRE.DEBUG) { System.out.println("setting leaves of " + this + " to " + leaves); } this.leaves = new ArrayList(leaves); } /** * Returns the operands to the real occurrence represented by this * PHI-statement. It is assumed that this PHI-statement represents a * real occurrence. */ public List leaves() { Assert.isTrue(leaves != null); final Iterator iter = leaves.iterator(); while (iter.hasNext()) { final Expr e = (Expr) iter.next(); Assert.isTrue((e instanceof VarExpr) || (e instanceof ConstantExpr), "not a leaf: " + e); } return leaves; } /** * Returns the operands of the PHI-statement. This is just a list of all * of the block's predacessors. */ public Collection operands() { final LinkedList v = new LinkedList(); final Iterator preds = cfg.preds(block).iterator(); while (preds.hasNext()) { final Block pred = (Block) preds.next(); v.addFirst(operandAt(pred)); } return v; } /** * Sets an operand of this PHI-statement. Recall that PHI-operands are * associated with a predacessor block of the block in which the * PHI-statement resides. * * @param block * The block associated with the operand. * @param def * The PHI-definition that is the operand. */ public void setOperandAt(final Block block, final Def def) { final int blockIndex = cfg.preOrderIndex(block); operands[blockIndex] = def; if (SSAPRE.DEBUG) { System.out.println(this); } } /** * Returns the PHI-operand of this PHI-statement associated with a given * block. Recall that PHI-operands are associated with a predacessor * block of the block in which the PHI-statement resides. */ public Def operandAt(final Block block) { final int blockIndex = cfg.preOrderIndex(block); return operands[blockIndex]; } /** * Sets the "has real use" flag. */ public void setHasRealUse(final Block block, final boolean flag) { final int blockIndex = cfg.preOrderIndex(block); hasRealUse[blockIndex] = flag; if (SSAPRE.DEBUG) { System.out.println(this); } } public boolean hasRealUse(final Block block) { final int blockIndex = cfg.preOrderIndex(block); return hasRealUse[blockIndex]; } /** * */ public void setSaveOperand(final Block block, final boolean flag) { final int blockIndex = cfg.preOrderIndex(block); saveOperand[blockIndex] = flag; if (SSAPRE.DEBUG) { System.out.println(this); } } public boolean saveOperand(final Block block) { final int blockIndex = cfg.preOrderIndex(block); return saveOperand[blockIndex]; } /** * Determines whether or not a PHI-operand satisfies "insert". For * insert to hold, the following conditions must be met: 1. The * PHI-statement is "will be available" 2. The PHI-operand is tack * (null), or "has real use" is false and the operand is defined by an * PHI-statement that does not satisfy "will be available". * * @param block * The block with which a desired operand is associated with. * Recall that PHI-operands are associated with the block * that is a predacessor of the block in which they are * contained. */ public boolean canInsert(final Block block) { final int blockIndex = cfg.preOrderIndex(block); final Def def = operands[blockIndex]; if (def == null) { return true; } if (!hasRealUse[blockIndex]) { if (def instanceof Phi) { final Phi phi = (Phi) def; if (!phi.willBeAvail()) { return true; } } } return false; } /** * Returns whether or not an PHI-statement satisfies "will be * available". "Will be available" is used to determine the locations in * which to insert evaluations of the expression in the finalize() pass. * <p> * willBeAvail = canBeAvail && !later * * @see #finalize() */ public boolean willBeAvail() { // WBA => CBA => DS return canBeAvail && !later; } /** * Sets the "can be available" flag. */ public void setCanBeAvail(final boolean flag) { canBeAvail = flag; if (SSAPRE.DEBUG) { System.out.println(this); } } /** * Returns the "can be available" flag. */ public boolean canBeAvail() { return canBeAvail; } /** * Sets the "later" flag. If the later flag is false, it means that an * evaluation of the expression may not be placed at any point below * this PHI-statement without introducing a useless computation along * some path. */ public void setLater(final boolean flag) { later = flag; if (SSAPRE.DEBUG) { System.out.println(this); } } /** * Returns the "later" flag. * * @see #setLater */ public boolean later() { return later; } public void setLive(final boolean flag) { live = flag; } public boolean live() { return live; } /** * Sets the "down-safe" flag. An PHI-statement is "down-safe" if there * is no path from the PHI-statement to the exit block that does not * recalculate the expression. If an PHI-statement is "down-safe" it is * worthwhile to attempt to hoist it up higher in the program. * <p> * An PHI-statement is not "down-safe" when a) There is a path to exit * along which the PHI-statement result is never used. b) There is a * path to exit along which the only used of the result of the * PHI-statement is an operand of an PHI-statement which itself is not * "down-safe". */ public void setDownSafe(final boolean flag) { downSafe = flag; if (SSAPRE.DEBUG) { System.out.println(this); } } /** * Returns whether or not this PHI-statement is "down-safe". * * @see #setDownSafe */ public boolean downSafe() { return downSafe; } /** * Returns a textual representation of this PHI-statement. */ public String toString() { String s = "Phi_" + version + "["; if (!downSafe) { s += "!"; } s += "DS,"; if (!canBeAvail) { s += "!"; } s += "CBA,"; if (!later) { s += "!"; } s += "later]("; if (operands != null) { final Iterator e = cfg.preds(block).iterator(); while (e.hasNext()) { final Block pred = (Block) e.next(); final int predIndex = cfg.preOrderIndex(pred); s += pred.label() + "="; final Def operand = operands[predIndex]; if (operand == null) { s += "undef["; } else { s += operand.version + "["; } if (!hasRealUse[predIndex]) { s += "!"; } s += "HRU,"; if (!saveOperand[predIndex]) { s += "!"; } s += "save,"; if (!canInsert(pred)) { s += "!"; } s += "insert]"; if (e.hasNext()) { s += ", "; } } } s += ")"; return s; } } /** * ExprInfo represents an expression that we are performing SSA-based PRE * on. An occurrence of an expression can take one of three forms: 1) A real * occurrence of an expression (h = a+b) 2) A (target of a) PHI function (h = * PHI(...)) 3) An operand to a PHI function (PHI(h, ...)) * * The occurrences of an expression are ordered according to a preorder * traversal of the CFG. * */ private final class ExprInfo { ExprKey key; // A unique key for an Expr instance private int numUses; // Number of uses (not defs) of this expr private List[] reals; // The real occurrences of this expression private boolean[] realsSorted; // Are the reals at a given block // sorted? private Phi[] phis; // PHI expressions for this occurrences Map defs; // Maps an Expr to its defining occurrence // "h" in the CFG. Map availDefs; // Map saves; Map reloads; private Expr prototype; // The actual expression being represented private boolean isFinal; // Does the expression access a final field? private boolean hasSideEffects; private boolean hasStackVariable; /** * Constructor. * * @param expr * The expression (real occurrence) represented by this * ExprInfo. * @param key * A unique key by which this expression can be identified. */ public ExprInfo(final Expr expr, final ExprKey key) { this.key = key; prototype = (Expr) expr.clone(); // Clean up the expression's children (remember that expr's children // are also cloned, so we aren't changing the tree). prototype.visitChildren(new TreeVisitor() { public void visitStoreExpr(final StoreExpr expr) { expr.target().setDef(null); expr.target().setParent(null); expr.replaceWith(expr.target(), false); expr.cleanupOnly(); expr.expr().cleanup(); } public void visitVarExpr(final VarExpr expr) { expr.setDef(null); } public void visitConstantExpr(final ConstantExpr expr) { } // The prototype expression should only // contain StoreExpr, VarExpr, or // ConstantExpr... public void visitExpr(final Expr expr) { throw new RuntimeException(); } }); numUses = 0; reals = new ArrayList[cfg.size()]; realsSorted = new boolean[cfg.size()]; for (int i = 0; i < reals.length; i++) { reals[i] = new ArrayList(); realsSorted[i] = false; } phis = new Phi[cfg.size()]; defs = new HashMap(); availDefs = new HashMap(); saves = new HashMap(); reloads = new HashMap(); if (prototype instanceof MemRefExpr) { // Traverse the tree and determine whether expr accesses a final // field. final FinalChecker fch = new FinalChecker(); prototype.visit(fch); isFinal = fch.isFinal; } else { isFinal = true; } // For PRE, RCs, UCs, stores, and possible reassignment // through aliases are not considered side effects. sideEffects.reset(); prototype.visit(sideEffects); int flag = sideEffects.sideEffects(); flag &= ~SideEffectChecker.STORE; flag &= ~SideEffectChecker.ALIAS; flag &= ~SideEffectChecker.RC; flag &= ~SideEffectChecker.UC; hasSideEffects = flag != 0; // Special case: allow RC(S) and UC(S). if ((flag & SideEffectChecker.STACK) != 0) { Assert.isTrue(prototype instanceof CheckExpr); hasStackVariable = true; } } public boolean hasStackVariable() { return hasStackVariable; } public boolean hasSideEffects() { return hasSideEffects; } public int numUses() { return numUses; } public void cleanup() { reals = null; phis = null; saves = null; reloads = null; defs = null; availDefs = null; prototype = null; } // Reload is used in finalize public void setReload(final Expr expr, final boolean flag) { if (SSAPRE.DEBUG) { System.out.println(" setting reload for " + expr + " to " + flag); } reloads.put(expr, new Boolean(flag)); } public boolean reload(final Expr expr) { final Boolean flag = (Boolean) reloads.get(expr); return (flag != null) && flag.booleanValue(); } // Save is used in finalize public void setSave(final Expr expr, final boolean flag) { if (SSAPRE.DEBUG) { System.out.println(" setting save for " + expr + " to " + flag); } saves.put(expr, new Boolean(flag)); } public boolean save(final Expr expr) { final Boolean flag = (Boolean) saves.get(expr); return (flag != null) && flag.booleanValue(); } // AvailDef is used in finalize public void setAvailDef(final Def def, final Def availDef) { if (SSAPRE.DEBUG) { System.out.println(" setting avail def for " + def + " to " + availDef); } availDefs.put(def, availDef); } public Def availDef(final Def def) { final Def availDef = (Def) availDefs.get(def); if (SSAPRE.DEBUG) { System.out.println(" avail def for " + def + " is " + availDef); } return availDef; } /** * Sets the defining occurrence (the "h") of a given real occurrence. */ public void setDef(final Expr expr, final Def def) { if (SSAPRE.DEBUG) { System.out.println(" setting def for " + expr + " to " + def); } if (def != null) { defs.put(expr, def); } else { defs.remove(expr); } } /** * Returns the Def (either a ReafDef or Phi) defining a given occurrence * of the expression modeled by this ExprInfo. */ public Def def(final Expr expr) { final Def def = (Def) defs.get(expr); if (SSAPRE.DEBUG) { System.out.println(" def for " + expr + " is " + def); } return def; } public Expr prototype() { return prototype; } /** * Notifies this ExprInfo of the existence of another real occurrence of * the expression. */ public void addReal(final Expr real) { if (!real.isDef()) { numUses++; } final int blockIndex = cfg.preOrderIndex(real.block()); reals[blockIndex].add(real); realsSorted[blockIndex] = false; } /** * Notifies this ExprInfo of the existence of an PHI-statement for this * expression. If the PHI is not already present, a new Phi instance is * created for it and placed in the phis array. * * @param block * The block at which the PHI occurs. */ public void addPhi(final Block block) { final int blockIndex = cfg.preOrderIndex(block); if (phis[blockIndex] == null) { if (SSAPRE.DEBUG) { System.out.println(" add phi for " + prototype + " at " + block); } phis[blockIndex] = new Phi(this, block); } } /** * Removes a PHI occurrence from the phis array. */ public void removePhi(final Block block) { final int blockIndex = cfg.preOrderIndex(block); phis[blockIndex] = null; } /** * Returns the PHI occurrence for this expression at a given Block in * the code. */ public Phi exprPhiAtBlock(final Block block) { final int blockIndex = cfg.preOrderIndex(block); return phis[blockIndex]; } /** * Returns the real occurrences of this expression at a given Block in * the code. */ public List realsAtBlock(final Block block) { final int blockIndex = cfg.preOrderIndex(block); final List r = reals[blockIndex]; if (!realsSorted[blockIndex]) { sortExprs(r); realsSorted[blockIndex] = true; } return r; } /** * Returns a List of the real occurrences of the expression and any Kill * expressions contained in a given Block in the code. */ public List occurrencesAtBlock(final Block block) { if (isFinal && !hasSideEffects) { return realsAtBlock(block); } final int blockIndex = cfg.preOrderIndex(block); final List a = kills[blockIndex]; final List r = reals[blockIndex]; if (!killsSorted[blockIndex]) { sortKills(a); killsSorted[blockIndex] = true; } if (!realsSorted[blockIndex]) { sortExprs(r); realsSorted[blockIndex] = true; } // return a list that is essentially a combination of the // real occurrences and the kill expressions return new AbstractList() { public int size() { return r.size() + a.size(); } public boolean contains(final Object obj) { if (obj instanceof Kill) { return a.contains(obj); } else if (obj instanceof Expr) { return r.contains(obj); } return false; } public Object get(final int index) { throw new UnsupportedOperationException(); } public Iterator iterator() { return new Iterator() { Iterator aiter; Iterator riter; Kill anext; Expr rnext; { aiter = a.iterator(); riter = r.iterator(); if (aiter.hasNext()) { anext = (Kill) aiter.next(); } else { anext = null; } if (riter.hasNext()) { rnext = (Expr) riter.next(); } else { rnext = null; } } public boolean hasNext() { return (anext != null) || (rnext != null); } public Object next() { boolean real = false; if (anext == null) { if (rnext == null) { throw new NoSuchElementException(); } real = true; } else if (rnext == null) { real = false; } else { // Kills go first if keys are equal. if (anext.key() <= rnext.key()) { real = false; } else { real = true; } } if (real) { final Object t = rnext; if (riter.hasNext()) { rnext = (Expr) riter.next(); } else { rnext = null; } return t; } else { final Object t = anext; if (aiter.hasNext()) { anext = (Kill) aiter.next(); } else { anext = null; } return t; } } public void remove() { throw new UnsupportedOperationException(); } }; } public ListIterator listIterator() { throw new UnsupportedOperationException(); } }; } // end occurrencesAtBlock() /** * Sort a list of expressions into preorder. * * Recall that the key of each occurrence node was set to its preorder * number in collectOccurrences. */ private void sortExprs(final List list) { Collections.sort(list, new Comparator() { public int compare(final Object a, final Object b) { if (a == b) { return 0; } final int ka = ((Expr) a).key(); final int kb = ((Expr) b).key(); return ka - kb; } }); } /** * Sorts a lists of Kills into preorder. That is, the Kills in a given * block are sorted by the pre-order number. */ private void sortKills(final List list) { Collections.sort(list, new Comparator() { public int compare(final Object a, final Object b) { if (a == b) { return 0; } final int ka = ((Kill) a).key(); final int kb = ((Kill) b).key(); return ka - kb; } }); } /** * Print a textual description of this ExprInfo. */ protected void print() { System.out.println("Print for " + prototype + "------------------"); cfg.visit(new PrintVisitor() { Phi phi = null; public void visitBlock(final Block block) { phi = exprPhiAtBlock(block); super.visitBlock(block); } public void visitLabelStmt(final LabelStmt stmt) { super.visitLabelStmt(stmt); if (stmt.label().startsBlock()) { if (phi != null) { println(phi); phi = null; } } } }); System.out.println("End Print ----------------------------"); } } // end class ExprInfo /** * Traverses a tree and determines if a final (class or instance) field is * accessed. */ class FinalChecker extends TreeVisitor { public boolean isFinal = true; public void visitExpr(final Expr expr) { if (isFinal) { expr.visitChildren(this); } } public void visitArrayRefExpr(final ArrayRefExpr expr) { isFinal = false; } public void visitFieldExpr(final FieldExpr expr) { final MemberRef field = expr.field(); try { final FieldEditor e = context.editField(field); if (!e.isFinal()) { isFinal = false; } context.release(e.fieldInfo()); } catch (final NoSuchFieldException e) { // A field wasn't found. Silently assume it's not final. isFinal = false; } if (isFinal) { expr.visitChildren(this); } } public void visitStaticFieldExpr(final StaticFieldExpr expr) { final MemberRef field = expr.field(); try { final FieldEditor e = context.editField(field); if (!e.isFinal()) { isFinal = false; } context.release(e.fieldInfo()); } catch (final NoSuchFieldException e) { // A field wasn't found. Silently assume it's not final. isFinal = false; } if (isFinal) { expr.visitChildren(this); } } } /** * ExprWorklist is a worklist of expressions (represented by ExprInfo) * containing all of the first-order expressions in the CFG (method). The * worklist is assembled in collectOccurrences() and its expressions are * used throughout SSAPRE. */ class ExprWorklist { Map exprInfos; // A mapping between ExprKey and ExprInfo LinkedList exprs; // All the ExprInfos we know about public ExprWorklist() { exprInfos = new HashMap(); exprs = new LinkedList(); } public boolean isEmpty() { return exprs.isEmpty(); } public ExprInfo removeFirst() { final ExprInfo exprInfo = (ExprInfo) exprs.removeFirst(); exprInfos.remove(exprInfo.key); return exprInfo; } /** * Add a real occurrence of an expression to the worklist. If necessary, * an ExprInfo is created to represent the expression. */ public void addReal(final Expr real) { if (SSAPRE.DEBUG) { System.out.println(" add to worklist=" + real); } final ExprKey key = new ExprKey(real); ExprInfo exprInfo = (ExprInfo) exprInfos.get(key); if (exprInfo == null) { exprInfo = new ExprInfo(real, key); exprs.add(exprInfo); exprInfos.put(key, exprInfo); if (SSAPRE.DEBUG) { System.out.println(" add info"); } } exprInfo.addReal(real); } /** * Adds a Kill expression to the worklist at a given block. */ public void addKill(final Block block, final Kill kill) { if (SSAPRE.DEBUG) { System.out.println(" add alias to worklist=" + kill.expr + " " + kill); } final int blockIndex = cfg.preOrderIndex(block); kills[blockIndex].add(kill); killsSorted[blockIndex] = false; } } /** * Kill represents a point at which code cannot be hoisted across. */ abstract class Kill { int key; Expr expr; /** * Constructor. * * @param expr * The expression that causes this kill. */ public Kill(final Expr expr, final int key) { this.expr = expr; this.key = key; } public Kill(final int key) { this(null, key); } public int key() { return key; } } /** * ExceptionKill is a Kill that occurrs because an exception may be * encountered. An ExceptionKill is used when a Block that begins a * protected region or an expression that catches an exception is * encountered. */ class ExceptionKill extends Kill { public ExceptionKill(final Expr expr, final int key) { super(expr, key); } public ExceptionKill(final int key) { super(key); } } /** * MemRefKill is a Kill that occurrs because a reference to a memory * location may be made. A MemRefKill is used when a synchronized * (monitorenter and monitorexit) block of code, or an expression that * accesses a memory location (MemRefExpr) and defines a variable, or an * expression that invokes a method is encountered. */ class MemRefKill extends Kill { public MemRefKill(final Expr expr, final int key) { super(expr, key); } public MemRefKill(final int key) { super(key); } } /** * Represents an expression and a hash code for that expression. */ class ExprKey { Expr expr; int hash; public ExprKey(final Expr expr) { this.expr = expr; this.hash = NodeComparator.hashCode(expr) + expr.type().hashCode(); } public int hashCode() { return hash; } private List listChildren(Expr expr) { final List children = new ArrayList(); if (expr instanceof StoreExpr) { expr = ((StoreExpr) expr).target(); } expr.visitChildren(new TreeVisitor() { public void visitStoreExpr(final StoreExpr expr) { // Ignore the RHS. children.add(expr.target()); } public void visitExpr(final Expr expr) { children.add(expr); } }); return children; } public boolean equals(final Object obj) { if (obj instanceof ExprKey) { final ExprKey other = (ExprKey) obj; if (!expr.type().equals(other.expr.type())) { return false; } if (!NodeComparator.equals(expr, other.expr)) { return false; } final List children = listChildren(expr); final List otherChildren = listChildren(other.expr); if (children.size() != otherChildren.size()) { return false; } final Iterator iter1 = children.iterator(); final Iterator iter2 = otherChildren.iterator(); while (iter1.hasNext() && iter2.hasNext()) { final Expr child1 = (Expr) iter1.next(); final Expr child2 = (Expr) iter2.next(); if ((child1 instanceof StackExpr) != (child2 instanceof StackExpr)) { return false; } if ((child1 instanceof VarExpr) && (child2 instanceof VarExpr)) { if (phiRelatedFind(child1.def()) != phiRelatedFind(child2 .def())) { return false; } } else { Assert.isTrue((child1 instanceof ConstantExpr) || (child2 instanceof ConstantExpr), "neither " + child1 + " nor " + child2 + " are constants"); // If one is a constant the other must have the same // value as the constant. if (child1.valueNumber() != child2.valueNumber()) { return false; } } } if (iter1.hasNext() || iter2.hasNext()) { // Size mismatch. return false; } return true; } return false; } } // end class ExprKey /** * */ Expr phiRelatedFind(Expr a) { final ArrayList stack = new ArrayList(); while (a != null) { Object p = phiRelated.get(a); if ((p == a) || (p == null)) { // Path compression. final Iterator iter = stack.iterator(); while (iter.hasNext()) { p = iter.next(); if (p != a) { phiRelated.put(p, a); } } return a; } stack.add(a); a = (Expr) p; } return null; } /** * phiRelatedUnion associates a variable (local or stack) */ void phiRelatedUnion(final Expr a, final Expr b) { final Expr p = phiRelatedFind(a); final Expr q = phiRelatedFind(b); if (p != q) { phiRelated.put(p, q); } } }