/* * 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.ArrayList; import java.util.List; import java.util.Set; import polyglot.ast.Assign; import polyglot.ast.Block; import polyglot.ast.Branch; import polyglot.ast.Catch; import polyglot.ast.Expr; import polyglot.ast.Field; import polyglot.ast.Formal; import polyglot.ast.Id; import polyglot.ast.Labeled; import polyglot.ast.LocalDecl; import polyglot.ast.Loop; import polyglot.ast.MethodDecl; import polyglot.ast.Node; import polyglot.ast.NodeFactory; import polyglot.ast.Return; import polyglot.ast.Stmt; import polyglot.ast.StringLit; import polyglot.ast.Switch; import polyglot.ast.Try; import polyglot.ast.TypeNode; import polyglot.frontend.Job; import polyglot.types.ClassType; import polyglot.types.Context; import polyglot.types.Flags; import polyglot.types.Name; import polyglot.types.QName; import polyglot.types.SemanticException; import polyglot.types.Type; import polyglot.types.TypeSystem; import polyglot.util.InternalCompilerError; import polyglot.util.Position; import polyglot.visit.ContextVisitor; import polyglot.visit.NodeVisitor; import x10.ast.Closure; import x10.errors.Warnings; import x10.types.TypeParamSubst; import x10.util.AltSynthesizer; import x10.util.CollectionFactory; /** * @author Bowen Alpern * */ public class FinallyEliminator extends ContextVisitor { private static final boolean INLINE_FOR_BRANCH = false; private static final boolean INLINE_FOR_RETURN = true; private static final Name THROW_RETURN = Name.make("throwReturn"); private static final Name THROW_BREAK = Name.make("throwBreak"); private static final Name THROW_CONTINUE = Name.make("throwContinue"); private static final Name PLAUSIBLE_THROW = Name.make("plausibleThrow"); private static final Name IS_BREAK = Name.make("isBreak"); private static final Name IS_RETURN = Name.make("isReturn"); private static final Name IS_CONTINUE = Name.make("isCONTINUE"); private static final Name VALUE = Name.make("value"); private static final Name LABEL = Name.make("label"); private static final Name EQUALS = Name.make("equals"); private static final QName FINALIZATION = QName.make("x10.compiler.Finalization"); private static final QName ABORT = QName.make("x10.compiler.Abort"); protected final TypeSystem ts; protected AltSynthesizer syn; protected final FinallyEliminatorState fes; /** * @param job * @param ts * @param nf */ public FinallyEliminator(Job job, TypeSystem ts, NodeFactory nf) { super(job, ts, nf); this.ts = ts; this.syn = new AltSynthesizer(ts, nf); this.fes = new FinallyEliminatorState(); } /** * Prevent the Java compiler from complaining about unreachable code. * s; -> if (true) s; * * @param stmt a statement that might not have normal code flow * @return a statement, semantically the same as stmt, that will look to a Java compiler as if it might have normal code flow * * TODO: implement dead code elimination and throw this code away */ Stmt protect(Stmt stmt, TypeSystem ts){ Expr cond; try { // if possible, create a true that wouldn't be recognized by (another pass of) the ConstantPropagator QName qname = QName.make("x10.compiler.CompilerFlags"); Type container = ts.forName(qname); Name name = Name.make("TRUE"); cond = syn.createStaticCall(stmt.position(), container, name); } catch (Exception e) { cond = syn.createTrue(stmt.position()); } return syn.createIf(stmt.position(), cond, stmt, null); } /* (non-Javadoc) * @see polyglot.visit.ErrorHandlingVisitor#enterCall(polyglot.ast.Node, polyglot.ast.Node) */ @Override protected NodeVisitor enterCall(Node parent, Node n) { FinallyEliminator res = this; if (n instanceof Closure) { res = (FinallyEliminator) ((ContextVisitor) new FinallyEliminator(job, ts, nodeFactory()).begin()).context(context()); } else { Try t = tryWithFinally(n); if (null == t) { if (n instanceof MethodDecl) this.fes.returnType = ((MethodDecl) n).returnType(); } else { TryVisitor tv = new TryVisitor(this, t.finallyBlock()); res = (TryVisitor) tv.context(context()); } } return res; } /* (non-Javadoc) * @see polyglot.visit.ErrorHandlingVisitor#leaveCall(polyglot.ast.Node) */ @Override protected Node leaveCall(Node parent, Node old, Node n, NodeVisitor v) { Try t = tryWithFinally(n); if (null == t) { if (n instanceof MethodDecl) this.fes.returnType = null; return n; } if (!(v instanceof TryVisitor)) throw new InternalCompilerError("Bad child visitor in FinallyEliminator: "+v.getClass(), n.position()); TryVisitor tv = (TryVisitor) v; Stmt stmt = rewriteTry(t, tv.tvs); return stmt; } /** * Determine if argument is a Try node with a Finally clause. * * @param n a node, possibly a Try with a Finally clause. * @return n as a Try, if n is a Try with a finally clause; null, otherwise */ private Try tryWithFinally(Node n) { if (n instanceof Try && null != ((Try) n).finallyBlock()) return (Try) n; return null; } private ClassType Finalization() { try { return (ClassType) ts.forName(FINALIZATION); } catch (SemanticException e) { throw new InternalCompilerError("Unable to load the Finalization class", e); } } private ClassType Abort() { try { return (ClassType) ts.forName(ABORT); } catch (SemanticException e) { throw new InternalCompilerError("Unable to load the Abort class", e); } } /** * @param t * @param tv * @return */ private Stmt rewriteTry(Try t, TryVisitorState tvs) { Position pos = t.position(); Block fb = t.finallyBlock(); Stmt s = t.finallyBlock(null); if (t.catchBlocks().isEmpty()) { s = t.tryBlock(); } Name name = Name.makeFresh("throwable"); // [DC] throwing CheckedThrowable might be a problem here... but hopefully after exception checking so ok Type throwableType = ts.CheckedThrowable(); LocalDecl throwDecl = syn.createLocalDecl(pos, Flags.NONE, name, throwableType, syn.createLiteral(pos, null)); Block tryBody = syn.createBlock(pos, s, syn.createStaticCall(pos, Finalization(), PLAUSIBLE_THROW)); Formal f = syn.createFormal(pos, throwableType); Stmt assignment = syn.createAssignment( pos, syn.createLocal(pos, throwDecl), Assign.ASSIGN, syn.createLocal(pos, f), this ); Block catchBody = syn.createBlock(pos, assignment); Catch catchClause = syn.createCatch(pos, f, catchBody); Try wrappedTry = syn.createTry(pos, tryBody, catchClause); Stmt abortExit = (Stmt) handleAbortExit(pos, throwDecl, tvs).visit(this); Stmt abnormalExit = (Stmt) handleAbnormalExit(pos, throwDecl, tvs).visit(this); List<Stmt> stmts = new ArrayList<Stmt>(); stmts.add(throwDecl); stmts.add(wrappedTry); stmts.add(abortExit); stmts.add(protect(fb, ts)); stmts.add(abnormalExit); return syn.createBlock(pos, stmts); } /** * @param pos * @param throwDecl * @param tvs * @return */ private Stmt handleAbortExit(Position pos, LocalDecl throwDecl, TryVisitorState tvs) { List<Stmt> stmts = new ArrayList<Stmt>(); ClassType Abort = Abort(); Expr cond = syn.createInstanceof(pos, syn.createLocal(pos, throwDecl), Abort); Stmt cons = syn.createThrow(pos, syn.createLocal(pos, throwDecl)); Stmt stmt = syn.createIf(pos, cond, cons, null); stmts.add(stmt); cond = syn.createNotNull(pos, syn.createLocal(pos, throwDecl), this); cons = syn.createBlock(pos, stmts); stmt = syn.createIf(pos, cond, cons, null); return stmt; } /** * @param pos * @param throwDecl * @param tvs * @return */ private Stmt handleAbnormalExit(Position pos, LocalDecl throwDecl, TryVisitorState tvs) { List<Stmt> stmts = new ArrayList<Stmt>(); // handle throw ClassType Finalization = Finalization(); Expr cond = syn.createNotInstanceof(pos, syn.createLocal(pos, throwDecl), Finalization, this); Stmt cons = syn.createThrow(pos, syn.createLocal(pos, throwDecl)); Stmt stmt = syn.createIf(pos, cond, cons, null); stmts.add(stmt); // handle return and branch Name fin = Name.makeFresh("fin"); LocalDecl finDecl = null; if (tvs.hasReturn || tvs.hasBreak || tvs.hasContinue) { Expr expr = syn.createUncheckedCast(pos, syn.createLocal(pos, throwDecl), Finalization); finDecl = syn.createLocalDecl(pos, Flags.FINAL, fin, expr); stmts.add(finDecl); } // handle return if (tvs.hasReturn) { cond = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), IS_RETURN); if (this.fes.returnType.type().isVoid()) { cons = syn.createReturn(pos); } else { Expr expr = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), VALUE); cons = syn.createReturn(pos, syn.createUncheckedCast(pos, expr, this.fes.returnType.type())); } stmt = syn.createIf(pos, cond, cons, null); stmts.add(stmt); } // handle break if (tvs.hasBreak) { cond = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), IS_BREAK); List<Stmt> ss = new ArrayList<Stmt>(); Field f = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), LABEL); Expr cd = syn.createIsNull(pos, f, this); Stmt cs = syn.createBreak(pos); stmt = syn.createIf(pos, cd, cs, null); ss.add(stmt); for (String label : tvs.breakLabels) { if (null == label) continue; f = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), LABEL); cd = syn.createInstanceCall(pos, syn.createStringLit(label), EQUALS, context(), f); cs = syn.createBreak(pos, label); stmt = syn.createIf(pos, cd, cs, null); ss.add(stmt); } cons = syn.createBlock(pos, ss); stmt = syn.createIf(pos, cond, cons, null); stmts.add(stmt); } // handle continue if (tvs.hasContinue) { cond = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), IS_BREAK); List<Stmt> ss = new ArrayList<Stmt>(); Field f = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), LABEL); Expr cd = syn.createIsNull(pos, f, this); Stmt cs = syn.createContinue(pos); stmt = syn.createIf(pos, cd, cs, null); ss.add(stmt); for (String label : tvs.continueLabels) { if (null == label) continue; f = syn.createFieldRef(pos, syn.createLocal(pos, finDecl), LABEL); cd = syn.createInstanceCall(pos, syn.createStringLit(label), EQUALS, context(), f); cs = syn.createContinue(pos, label); stmt = syn.createIf(pos, cd, cs, null); ss.add(stmt); } cons = syn.createBlock(pos, ss); stmt = syn.createIf(pos, cond, cons, null); stmts.add(stmt); } // TODO: handle error condition // Check that execution does not reach the end of stmts // Code synthesized here will only be reached if either: // 1) there is a bug in the x10 compiler // 2) the x10 programmer concocts and throws an x10.compiler.Finalization. // Both, are in error. // handle abnormal exit (brining it all together) cond = syn.createNotNull(pos, syn.createLocal(pos, throwDecl), this); cons = syn.createBlock(pos, stmts); stmt = syn.createIf(pos, cond, cons, null); return stmt; } /* * A visitor for Try nodes with Finally clauses. * The Finally clause is visited by an outer visitor. * Normal execution and unmarked throws are routed to the Finally block. * Return's and Branch's (Break's and Continue's) execute the Finally block either by * replication (and reinstantiation) of the Finally block, or * throwing a Finalization exception. * Internal Try nodes with Finally clauses get their own TryVisitor's, * but this one visits their Finally blocks. */ private class TryVisitor extends FinallyEliminator { private final TryVisitorState tvs; /** * @param ov the outer visitor (will visit the root's Finally block) */ public TryVisitor(FinallyEliminator ov, Block fb) { super(ov.job, ov.ts, ov.nodeFactory()); tvs = new TryVisitorState(ov, fb); this.fes.returnType = ov.fes.returnType; } /* (non-Javadoc) * @see x10.visit.FinallyEliminator#enterCall(polyglot.ast.Node) */ @Override protected NodeVisitor enterCall(Node parent, Node n) { if (n == tvs.finallyBlock) return tvs.outerVisitor; if (n instanceof Labeled) { Labeled l = (Labeled) n; Id id = l.labelNode(); Name name = null != id ? id.id() : Name.make(""); String label = name.toString(); tvs.ignoreLabels.add(label); } if (n instanceof Loop || n instanceof Switch) { tvs.loopNesting++; } return super.enterCall(parent, n); } /** * Make sure that exits from the try by Return or Branch (Break or Continue) are routed through the finally block. * Two approaches are possible: * 1) Replicate and reinstantiate the finally block before the exit, and * 2) Create and throw a custom Finalization which will be caught before the finally block and processed after it. * * @see x10.visit.FinallyEliminator#leaveCall(polyglot.ast.Node) */ @Override protected Node leaveCall(Node parent, Node old, Node n, NodeVisitor v) { Position pos = n.position(); List<Stmt> stmts = new ArrayList<Stmt>(); ClassType Finalization = Finalization(); if (n instanceof Return) { Return r = (Return) n; if (INLINE_FOR_RETURN) { if (null == r.expr()) { stmts.add(replicate(pos, tvs.finallyBlock)); stmts.add(r); } else { LocalDecl temp = syn.createLocalDecl(pos, Flags.FINAL, Name.makeFresh(), r.expr()); stmts.add(temp); stmts.add(replicate(pos, tvs.finallyBlock)); stmts.add(r.expr(syn.createLocal(pos, temp))); } return syn.createBlock(pos, stmts); } else { tvs.hasReturn = true; if (null == r.expr()) { return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_RETURN)); } else { return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_RETURN, r.expr())); } } } else if (n instanceof Branch) { Branch b = (Branch) n; Id id = b.labelNode(); if (null == id && 0 < tvs.loopNesting) return super.leaveCall(parent, old, n, v); Name name = null != id ? id.id() : null; String label = null != name ? name.toString() : null; if (null != label && tvs.ignoreLabels.contains(label)) return super.leaveCall(parent, old, n, v); StringLit lit = null != label ? syn.createStringLit(label) : null; if (INLINE_FOR_BRANCH) { stmts.add(replicate(pos, tvs.finallyBlock)); stmts.add(b); return syn.createBlock(pos, stmts); } else { if (b.kind() == Branch.BREAK) { tvs.hasBreak = true; if (null != label) tvs.breakLabels.add(label); if (null == lit) return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_BREAK)); return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_BREAK, lit)); } else { tvs.hasContinue = true; tvs.continueLabels.add(label); if (null == lit) return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_CONTINUE)); return syn.createEval(syn.createStaticCall(pos, Finalization, THROW_CONTINUE, lit)); } } } if (n instanceof Loop || n instanceof Switch) { tvs.loopNesting++; } return super.leaveCall(parent, old, n, v); } /** * @param block * @return */ private Stmt replicate(Position pos, Block block) { Block b = (Block) block.copy(); Reinstantiator reinstantiator= new Reinstantiator(TypeParamSubst.IDENTITY); ContextVisitor visitor= new NodeTransformingVisitor(job, ts, nf, reinstantiator).context(context()); b = (Block) b.visit(visitor); // reinstantiate locals in the body return b; } } class FinallyEliminatorState { private TypeNode returnType; } class TryVisitorState { public FinallyEliminator outerVisitor; public Block finallyBlock; public Set<String> breakLabels = CollectionFactory.newHashSet(); public Set<String> continueLabels = CollectionFactory.newHashSet(); public Set<String> ignoreLabels = CollectionFactory.newHashSet(); public boolean hasReturn = false; public boolean hasBreak = false; public boolean hasContinue = false; public int loopNesting = 0; TryVisitorState (FinallyEliminator ov, Block fb) { outerVisitor = ov; finallyBlock = fb; } } }