package x10.optimizations.inlining;
import java.util.ArrayList;
import java.util.List;
import polyglot.ast.AmbAssign;
import polyglot.ast.AmbExpr;
import polyglot.ast.AmbTypeNode;
import polyglot.ast.Assign;
import polyglot.ast.Block;
import polyglot.ast.Branch;
import polyglot.ast.Call;
import polyglot.ast.ConstructorDecl;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.FieldAssign;
import polyglot.ast.Formal;
import polyglot.ast.Id;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.ProcedureDecl;
import polyglot.ast.Return;
import polyglot.ast.Special;
import polyglot.ast.Stmt;
import polyglot.ast.Throw;
import polyglot.frontend.Job;
import polyglot.types.CodeDef;
import polyglot.types.ConstructorDef;
import polyglot.types.Context;
import polyglot.types.FieldInstance;
import polyglot.types.LocalDef;
import polyglot.types.LocalInstance;
import polyglot.types.Name;
import polyglot.types.ProcedureDef;
import polyglot.types.QName;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.types.VarDef;
import polyglot.types.VarInstance;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.ContextVisitor;
import polyglot.visit.NodeVisitor;
import x10.ast.AssignPropertyCall;
import x10.ast.Closure;
import x10.ast.Closure_c;
import x10.ast.StmtSeq;
import x10.ast.X10ConstructorCall;
import x10.ast.X10ConstructorDecl;
import x10.ast.X10MethodDecl;
import x10.ast.X10Special;
import x10.types.ClosureDef;
import x10.types.MethodInstance;
import x10.types.X10FieldInstance;
import x10.util.AltSynthesizer;
/**
* Rewrites a given method/closure body so that it has exactly one return
* statement at the end if the return type is not void, and no return
* statements if it's void. Also, replaces "this" parameter by a local
* variable.
*
* @author igor
*/
public class InliningRewriter extends ContextVisitor {
private final CodeDef def;
private final LocalDef ths;
private final LocalDef ret;
private final Name label;
private AltSynthesizer syn;
private boolean[] failed = new boolean[1];
public InliningRewriter(Closure closure, Job j, TypeSystem ts, NodeFactory nf, Context ctx) {
this(closure.closureDef(), null, closure.body().statements(), j, ts, nf, ctx);
}
public InliningRewriter(ProcedureDecl decl, LocalDef ths, Job j, TypeSystem ts, NodeFactory nf, Context ctx) {
this(decl.procedureInstance(), ths, decl.body().statements(), j, ts, nf, ctx);
}
private InliningRewriter(ProcedureDef def, LocalDef ths, List<Stmt> body, Job j, TypeSystem ts, NodeFactory nf, Context ctx) {
super(j, ts, nf);
this.context = ctx;
this.def = ctx.currentCode();
this.ths = ths;
this.syn = new AltSynthesizer(ts, nf);
if (def instanceof ConstructorDef) {
this.ret = null;
this.label = Name.makeFresh("__ret");
} else if (body.size() == 1 && body.get(0) instanceof Return) {
// Closure already has the right properties; make return rewriting a no-op
this.ret = null;
this.label = null;
} else {
Name rn = Name.makeFresh("ret");
Type rt = def.returnType().get();
this.ret = rt.isVoid() ? null : ts.localDef(def.position(), ts.NoFlags(), Types.ref(rt), rn);
this.label = Name.makeFresh("__ret");
}
failed[0] = false;
}
public Node override(Node n) {
if (failed[0])
return n; // abort visit
if (def == null)
return n;
return null;
}
// TODO: use override to short-circuit the traversal
public Node leaveCall(Node old, Node n, NodeVisitor v)
throws SemanticException {
if (v != this) {
}
if (n instanceof AmbExpr || n instanceof AmbAssign || n instanceof AmbTypeNode) {
throw new InternalCompilerError("Ambiguous node found: " + n, n.position());
}
if (n instanceof Closure)
return visitClosure((Closure) n);
if (n instanceof Return)
return visitReturn((Return) n);
if (n instanceof Throw)
return visitThrow((Throw) n);
if (n instanceof Field)
return visitField((Field) n);
if (n instanceof Call) {
return visitCall((Call) old, (Call) n);
}
if (n instanceof X10ConstructorCall)
return visitX10ConstructorCall((X10ConstructorCall) n);
if (n instanceof Special)
return visitSpecial((Special) n);
if (n instanceof AssignPropertyCall)
return visitAssignPropertyCall((AssignPropertyCall) n);
return n;
}
public Block rewriteBody(Position pos, Block body) {
if (body == null) {
return null;
}
if (failed[0]) {
return null;
}
if (label == null) {
return body;
}
List<Stmt> newBody = new ArrayList<Stmt>();
if (ret != null) {
newBody.add(nf.LocalDecl( pos,
nf.FlagsNode(pos, ts.NoFlags()),
nf.CanonicalTypeNode(pos, ret.type()),
nf.Id(pos, ret.name()) ).localDef(ret));
}
// A return at the end of the method will have been converted to a
// break. It's not needed. Turf it.
List<Stmt> bodyStmts = body.statements();
if (!bodyStmts.isEmpty() && (bodyStmts.get(bodyStmts.size() - 1) instanceof Branch)) {
Branch br = (Branch) bodyStmts.get(bodyStmts.size() - 1);
if (br.kind() == Branch.BREAK) {
Id breakLabel = br.labelNode();
if (breakLabel.id().equals(label)) {
List<Stmt> statements = new ArrayList<Stmt>();
for (Stmt stmt : bodyStmts) {
if (stmt != br) {
statements.add(stmt);
}
}
body = nf.Block(body.position(), statements);
}
}
}
newBody.add(nf.Labeled(pos, nf.Id(pos, label), body));
if (ret != null) {
Expr rval = nf.Local(pos, nf.Id(pos, ret.name())).localInstance(ret.asInstance()).type(ret.type().get());
newBody.add(nf.Return(pos, rval));
} else {
newBody.add(nf.Return(pos));
}
return nf.Block(body.position(), newBody);
}
public static Block rewriteProcedureBody(ProcedureDecl decl, LocalDef thisDef, Job job, Context cxt) {
cxt = cxt.pushBlock();
if (thisDef != null) {
cxt.addVariable(thisDef.asInstance());
}
for (Formal f : decl.formals()) {
cxt.addVariable(f.localDef().asInstance());
}
InliningRewriter rewriter = new InliningRewriter(decl, thisDef, job, job.extensionInfo().typeSystem(), job.extensionInfo().nodeFactory(), cxt);
Block body = (Block) decl.body().visit(rewriter);
return rewriter.rewriteBody(decl.position(), body);
}
// def m(`x:`T):R=S -> {r:R; L:{ S[return v/r=v; break L;]; }; return r;}
public static Block rewriteMethodBody(X10MethodDecl decl, LocalDef thisDef, Job job, Context cxt) {
return rewriteProcedureBody(decl, thisDef, job, cxt);
}
// def this(`x:`T){S} -> {L:{ S[return; / break L;] }; return;}
public static Block rewriteConstructorBody(X10ConstructorDecl decl, LocalDef thisDef, Job job, Context cxt) {
return rewriteProcedureBody(decl, thisDef, job, cxt);
}
// (`x:`T):R=>S -> {r:R; L:{ S[return v/r=v; break L;]; }; return r;}
public static Block rewriteClosureBody(Closure cl, Job job, Context cxt) {
cxt = cxt.pushBlock();
for (Formal f : cl.formals()) {
cxt.addVariable(f.localDef().asInstance());
}
InliningRewriter rewriter = new InliningRewriter(cl, job, job.extensionInfo().typeSystem(), job.extensionInfo().nodeFactory(), cxt);
Block body = (Block) cl.body().visit(rewriter);
return rewriter.rewriteBody(cl.position(), body); // Ensure that the last statement of the body is the only return in the closure
}
private Closure visitClosure(Closure n) {
// First propagate the captured environment outward
ClosureDef cd = n.closureDef();
// an inlined closure cannot capture anything but locals in the caller, so clean up the captured environment
if (ths != null) {
List<VarInstance<?>> env = new ArrayList<VarInstance<?>>(cd.capturedEnvironment().size());
for (VarInstance<?> vi : cd.capturedEnvironment()) {
if (vi instanceof LocalInstance) {
env.add(vi);
}
}
if (env.size() != cd.capturedEnvironment().size()) {
cd.setCapturedEnvironment(env); // The closure def should have been reinstantiated
}
}
Closure_c.propagateCapturedEnvironment(context, cd);
return n;
}
// return v; -> r=v; break L;
private Stmt visitReturn(Return n) {
// First check that we are within the right code body
if (!context.currentCode().equals(def)) return n;
if (label == null) return n;
assert ((ret == null) == (n.expr() == null));
Position pos = n.position();
List<Stmt> retSeq = new ArrayList<Stmt>();
if (ret != null) {
Type rt = ret.type().get();
Expr xl = nf.Local(pos, nf.Id(pos, ret.name())).localInstance(ret.asInstance()).type(rt);
retSeq.add(nf.Eval(pos, nf.Assign(pos, xl, Assign.ASSIGN, n.expr()).type(rt)));
}
retSeq.add(nf.Break(pos, nf.Id(pos, label)));
return nf.StmtSeq(pos, retSeq);
}
// throw e; -> if (true) throw e;
private Stmt visitThrow(Throw n) throws SemanticException {
// First check that we are within the right code body
if (!context.currentCode().equals(def)) return n;
if (label == null) return n;
return syn.createIf(n.position(), createOpaqueTrue(n.position()), n, null);
}
// property(e1, e2, ... en) -> { p1=e1; p2=e2; ... pn=en; }
private Node visitAssignPropertyCall(AssignPropertyCall n) {
// First check that we are within the right code body
if (!context.currentCode().equals(def)) return n;
List<Stmt> stmts = new ArrayList<Stmt>();
List<Expr> args = n.arguments();
List<X10FieldInstance> props = n.properties();
for (int i=0; i < args.size(); i++) {
Expr arg = args.get(i);
X10FieldInstance prop = props.get(i);
FieldAssign assign = syn.createFieldAssign(n.position(), getThis(n.position()), prop, arg, this);
stmts.add(syn.createEval(assign));
}
StmtSeq result = syn.createStmtSeq(n.position(), stmts);
return result;
}
/**
* @param pos
* @return
* @throws SemanticException
*/
private Expr createOpaqueTrue(Position pos) throws SemanticException {
QName qname = QName.make("x10.compiler.CompilerFlags");
Type container = typeSystem().forName(qname);
Name name = Name.make("TRUE");
Expr expr = syn.createStaticCall(pos, container, name);
return expr;
}
private Expr getThis(Position pos) {
LocalInstance li = ths.asInstance();
return nf.Local(pos, nf.Id(pos, li.name())).localInstance(li).type(li.type());
}
// f -> ths.f
private Field visitField(Field n) {
if (!n.isTargetImplicit()) return n;
FieldInstance fi = n.fieldInstance();
assert ((ths == null) == (fi.flags().isStatic()));
Position pos = n.position();
if (fi.flags().isStatic()) {
return n.target(nf.CanonicalTypeNode(pos, fi.container())).targetImplicit(false);
}
return n.target(getThis(pos)).targetImplicit(false);
}
// m(...) -> ths.m(...)
private Call visitCall(Call old, Call n) {
if (old.target() instanceof Special && ((Special) old.target()).kind() == X10Special.SUPER)
n = n.nonVirtual(true); // make calls to "super.foo()" non-virtual
if (!n.isTargetImplicit()) return n;
MethodInstance mi = n.methodInstance();
assert ((ths == null) == (mi.flags().isStatic()));
Position pos = n.position();
if (mi.flags().isStatic()) {
return n.target(nf.CanonicalTypeNode(pos, mi.container())).targetImplicit(false);
}
return n.target(getThis(pos)).targetImplicit(false);
}
// this(...) -> ths.this(...)
private Node visitX10ConstructorCall(X10ConstructorCall n) {
if (null != n.target()) return n;
return n.target(getThis(n.position()));
}
// this -> ths
private Expr visitSpecial(Special n) {
// First make sure ths is defined
if (null == ths) return n; // nothing to be done (e.g. "this" in a closure)
// Complicated cases don't get this far
assert (n.kind() == X10Special.SUPER || n.kind() == X10Special.THIS);
if (null != n.qualifier()) { // when the Inliner runs all outer classes have been stripped away, the qualifier should be redundant
if (Inliner.DEBUG) Inliner.debug("Inliner ignoring special qualifier " +n.qualifier(), n);
}
context.recordCapturedVariable(ths.asInstance());
// return a local for the inlined this
return getThis(n.position());
}
}