/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * (C) Copyright IBM Corporation 2010. */ package x10.visit; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import polyglot.ast.Assign; import polyglot.ast.Expr; import polyglot.ast.FlagsNode; import polyglot.ast.Formal; import polyglot.ast.Id; import polyglot.ast.Local; import polyglot.ast.LocalAssign; import polyglot.ast.LocalDecl; import polyglot.ast.Node; import polyglot.ast.NodeFactory; import polyglot.ast.ProcedureCall; import polyglot.ast.Stmt; import polyglot.ast.TypeNode; import polyglot.ast.Unary; import polyglot.frontend.Job; import polyglot.types.Flags; import polyglot.types.LocalDef; import polyglot.types.Name; import polyglot.types.SemanticException; import polyglot.types.TypeSystem; import polyglot.util.InternalCompilerError; import polyglot.util.Position; import polyglot.util.CollectionUtil; import x10.util.CollectionFactory; import polyglot.visit.ContextVisitor; import polyglot.visit.NodeVisitor; /** * Dead Variable Eliminator * * Purpose: to remove useless variable declarations. * * A local variable declaration is said to be: * "dead" -- if the variable is never used and its initializer is clearly side effect free, * "weak" -- if the variable is never used but its initializer might have side effects, and * "lame" -- if the variable is used exactly once as the initializer of another variable. * * Dead variable declarations are removed (replaced by empty statements). Weak variable * declarations are replaced by the evaluation of the initializer. * * Possible improvement: currently "uses" of a variable on the left hand side of an ordinary * (non updating) assignment are not distinguished from actual uses. This distinction might be * exploited but it might be better to put the AST in SSA form before dead variable elimination. * * @author Bowen Alpern * */ public class DeadVariableEliminator extends ContextVisitor { private static final boolean DEBUG = false; // private static final boolean DEBUG = true; private static final boolean VERBOSE = false; // private static final boolean VERBOSE = true; /** * State of the dead variable eliminator */ private TypeSystem xts; private NodeFactory xnf; private SideEffectDetector sed; private Node root; private VariableState state; public DeadVariableEliminator(Job job, TypeSystem ts, NodeFactory nf) { super(job, ts, nf); xts = ts; xnf = nf; state = new VariableState(); } @Override public NodeVisitor begin() { sed = new SideEffectDetector(job, ts, nf); sed = (SideEffectDetector) sed.begin(); return super.begin(); } /* // unused code @Override public DeadVariableEliminator copy() { DeadVariableEliminator copy = (DeadVariableEliminator) super.copy(); copy.state = state; copy.root = root; return copy; } */ /* (non-Javadoc) * @see polyglot.visit.NodeVisitor#override(polyglot.ast.Node) */ @Override public Node override(Node n) { if (ExpressionFlattener.cannotFlatten(n)) { return n; } return super.override(n); } /* (non-Javadoc) * @see polyglot.visit.ErrorHandlingVisitor#enterCall(polyglot.ast.Node, polyglot.ast.Node) */ @Override protected NodeVisitor enterCall(Node parent, Node n)throws SemanticException { if (null == parent) { // n is a root AST debug("DeadVariableEliminator entering root " +n, n); root = n; // DEBUG state.clear(); } if (n instanceof LocalDecl) { state.addDecl((LocalDecl) n); } else if (n instanceof Formal) { state.ignoreFormal((Formal) n); } else if (n instanceof Local) { state.recordUse((Local) n); } else if (n instanceof LocalAssign && ((LocalAssign) n).operator() == Assign.ASSIGN) { // state.recodeStore((LocalAssign) n); // TODO: handle dead assignments } return super.enterCall(parent, n); } /* (non-Javadoc) * @see polyglot.visit.ErrorHandlingVisitor#leaveCall(polyglot.ast.Node, polyglot.ast.Node, polyglot.ast.Node, polyglot.visit.NodeVisitor) */ @Override protected Node leaveCall(Node parent, Node old, Node n, NodeVisitor v) { if (null != parent) return n; // Wait for the root of the ast assert(((DeadVariableEliminator) v).root == old); Map<Node, Node> substitutions = state.makeReplacementMap(); if (!substitutions.isEmpty()) { n = n.visit(new SubstitutionVisitor(substitutions)); assert substitutions.isEmpty(); // n = n.visit(new ConstantPropagator(job, ts, nf).context(context())); } debug("DeadVariableEliminator leaving " + n, n); return n; } private final class VariableState { private final Set<LocalDef> formals = CollectionFactory.newHashSet(); // defs that are formal params private final Map<LocalDef, LocalDecl> declMap = CollectionFactory.newHashMap(); // the declaration of a def private final Map<LocalDef, Set<Local>> useMap = CollectionFactory.newHashMap(); // the uses of a def // private final Map<LocalDef, Set<LocalAssign>> storeMap = CollectionFactory.newHashMap(); // the pure stores of a def /** * */ private void clear() { formals.clear(); declMap.clear(); useMap.clear(); // storeMap.clear(); } /** * @param decl */ private void addDecl(LocalDecl decl) { LocalDef def = decl.localDef(); declMap.put(def, decl); if (null == useMap.get(def)) { useMap.put(def, CollectionFactory.<Local>newHashSet()); }/* if (null == storeMap.get(def)) { storeMap.put(def, CollectionFactory.<LocalAssign>newHashSet()); }*/ } /** * @param n */ private void ignoreFormal(Formal n) { formals.add(n.localDef()); } /** * @param local */ private void recordUse(Local local) { LocalDef def = local.localInstance().def(); if (formals.contains(def)) return; Set<Local> uses = useMap.get(def); if (null == uses) { uses = CollectionFactory.newHashSet(); useMap.put(def, uses); } uses.add(local); } /** * @param assign *//* public void recodeStore(LocalAssign assign) { LocalDef def = assign.local().localInstance().def(); Set<LocalAssign> stores = storeMap.get(def); if (null == stores) { stores = CollectionFactory.newHashSet(); storeMap.put(def, stores); } stores.add(assign); }*/ public Map<Node, Node> makeReplacementMap() { Set<LocalDecl> dead = CollectionFactory.newHashSet(); for (LocalDef def : useMap.keySet()) { if (isDead(def)) dead.add(declMap.get(def)); } Map<Node, Node> map = CollectionFactory.newHashMap(); while (!dead.isEmpty()) { LocalDecl decl = removeDecl(dead); Expr init = decl.init(); if (sed.hasSideEffects(init)) { map.put(decl, xnf.Eval(decl.position(), init)); } else { map.put(decl, xnf.Empty(decl.position())); dead.addAll(ignoreUses(init)); } /* TODO handle dead assignments for (LocalAssign assign : storeMap.get(decl.localDef())) { map.put(assign, assign.right().type(assign.type())); } */ } return map; } /** * @param def * @return */ private boolean isDead(LocalDef def) { if (useMap.containsKey(def)) { Set<Local> uses = useMap.get(def); if (uses.isEmpty()) return true; } // if (true) return false; // TODO: remove dead LocalAssign's // Set<LocalAssign> stores = storeMap.get(def); // return uses.size() == stores.size(); } /** * @param set * @return */ private LocalDecl removeDecl(Set<LocalDecl> set) { if (set.isEmpty()) return null; LocalDecl decl = set.iterator().next(); set.remove(decl); return decl; } /** * @param init * @return */ private Set<LocalDecl> ignoreUses(Expr init) { Set<LocalDecl> dead = CollectionFactory.newHashSet(); for (Local use : uses(init)) { LocalDef def = use.localInstance().def(); Set<Local> uses = useMap.get(def); if (null != uses) uses.remove(use); if (isDead(def)) { dead.add(declMap.get(def)); } } return dead; } /** * * @param expr * @return */ private Set<Local> uses(Expr expr) { final Set<Local> result = CollectionFactory.newHashSet(); if (null == expr) return result; expr.visit(new NodeVisitor() { /* (non-Javadoc) * @see polyglot.visit.NodeVisitor#override(polyglot.ast.Node) */ @Override public Node override(Node n) { if (n instanceof Local & !formals.contains(n)) result.add((Local) n); return null; } }); return result; } } private final class SubstitutionVisitor extends NodeVisitor { private final Map<Node, Node> subst; /** * @param substitutions */ public SubstitutionVisitor(Map<Node, Node> substitutions) { super(); this.subst = substitutions; } /* (non-Javadoc) * @see polyglot.visit.NodeVisitor#leave(polyglot.ast.Node, polyglot.ast.Node, polyglot.visit.NodeVisitor) */ @Override public Node leave(Node old, Node n, NodeVisitor v) { if (old instanceof Stmt && !(n instanceof Stmt)) { Position pos = n.position(); Expr e = (Expr) n; if (!sed.hasSideEffects(e)) { debug("DVE eliminating side-effect free expr: " +n, n); n = xnf.Empty(pos); } else if (e instanceof ProcedureCall || e instanceof Assign || e instanceof Unary) { n = xnf.Eval(pos, e); } else if (!e.type().typeEquals(xts.Void(), context())) { FlagsNode fn = xnf.FlagsNode(pos, Flags.FINAL); TypeNode tn = xnf.X10CanonicalTypeNode(pos, e.type()); Id id = xnf.Id(pos, Name.makeFresh("dummy")); n = xnf.LocalDecl(pos, fn, tn, id, e); } else { String msg = "Error: unexpected void expr: " +e+ " replacing stmt: " +old+ " at " +e.position(); System.err.println(msg); throw new InternalCompilerError(msg); } } if (n instanceof LocalDecl || n instanceof LocalAssign) { Node s = subst.remove(old); if (null != s) { verbose("DVE replacing " +old+ " by " +s, n); if (n instanceof LocalDecl) { // TODO: handle LocalAssign's (perhaps by replacing every "expr" by "Type dummy = expr" before Java back end n = s; } else { // TODO: handle n instanceof LocalAssign debug("DVE NOT replacing " +old+ " by " +s, n); if (false) n = s; } } } return n; } } private static void verbose(String msg, Node node) { if (!VERBOSE) return; try { Thread.sleep(10); System.out.print(" "); if (null != node) System.out.print(node.position() + ": "); System.out.println(msg); Thread.sleep(10); } catch (InterruptedException e) { // Ignore exception (we are just trying to avoid stepping on writes to STDERR } } private static void debug(String msg, Node node) { if (!DEBUG) return; try { Thread.sleep(10); System.err.print(" DEBUG "); if (null != node) System.err.print(node.position() + ": "); System.err.println(msg); Thread.sleep(10); } catch (InterruptedException e) { // Ignore exception (we are just trying to avoid stepping on writes to STDOUT } } }