/* * 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 2006-2010. */ package x10.optimizations.inlining; import java.util.ArrayList; import java.util.List; import java.util.Stack; import polyglot.ast.Block; import polyglot.ast.Call; import polyglot.ast.ClassDecl; import polyglot.ast.CodeBlock; import polyglot.ast.ConstructorCall; import polyglot.ast.Expr; import polyglot.ast.Formal; import polyglot.ast.Local; import polyglot.ast.LocalDecl; import polyglot.ast.MethodDecl; import polyglot.ast.Node; import polyglot.ast.NodeFactory; import polyglot.ast.ProcedureCall; import polyglot.ast.ProcedureDecl; import polyglot.ast.Return; import polyglot.ast.SourceFile; import polyglot.ast.Special; import polyglot.ast.Stmt; import polyglot.ast.TypeNode; import polyglot.ast.Variable; import polyglot.frontend.Job; import polyglot.frontend.Source; import polyglot.types.ConstructorInstance; import polyglot.types.Context; import polyglot.types.Flags; import polyglot.types.LocalDef; import polyglot.types.MemberDef; import polyglot.types.MemberInstance; import polyglot.types.Name; import polyglot.types.ProcedureDef; import polyglot.types.ProcedureInstance; import polyglot.types.Ref; import polyglot.types.SemanticException; import polyglot.types.Type; import polyglot.types.TypeSystem; import polyglot.types.Types; import polyglot.util.InternalCompilerError; import polyglot.util.Position; import polyglot.visit.ContextVisitor; import polyglot.visit.NodeVisitor; import x10.Configuration; import x10.ExtensionInfo; import x10.X10CompilerOptions; import x10.ast.Closure; import x10.ast.ClosureCall; import x10.ast.InlinableCall; import x10.ast.ParExpr; import x10.ast.StmtExpr; import x10.ast.X10ClassDecl; import x10.ast.X10Formal; import x10.ast.X10MethodDecl; import x10.config.ConfigurationError; import x10.config.OptionError; import x10.errors.Warnings; import x10.types.ParameterType; import x10.types.TypeParamSubst; import x10.types.X10ClassType; import x10.types.X10CodeDef; import x10.types.X10LocalDef; import x10.types.X10ProcedureDef; import x10.types.constants.ConstantValue; import x10.types.constants.StringValue; import x10.types.constraints.ConstraintManager; import x10.util.AltSynthesizer; import x10.util.AnnotationUtils; import x10.visit.ConstantPropagator; import x10.visit.ExpressionFlattener; import x10.visit.NodeTransformingVisitor; import x10.visit.X10AlphaRenamer; import x10cuda.ast.CUDAKernel; /** * This visitor inlines calls to methods and closures under the following * conditions: * <ul> * <li>The exact class of the method target is known. * <li>The method being invoked is annotated @X10.compiler.Inline or the method * is found to be "small". * <li>The closure call target is a literal closure. * </ul> * * @author nystrom * @author igor * @author Bowen Alpern */ @SuppressWarnings("unchecked") public class Inliner extends ContextVisitor { static final boolean DEBUG = false; private static final int VERBOSITY = 0; private static final int INITIAL_RECURSION_DEPTH = 0; private static final int RECURSION_DEPTH_LIMIT = 2; // max number of calls to the same procedure to be inlined at once private static final int INLINE_DEPTH_LIMIT = 10; // max number of calls to be inlined at once private boolean inliningRequired; // Should the current call be inlined no matter how big it is? /** * Explicit inlining (via annotations) and inlining of small methods (if * enabled) is done to an arbitrary depth for non-recursive calls. There is * a somewhat hoaky mechanism for limiting the number of recursive calls * that are inlined. * * TODO: implement a space budget based policy mechanism for controling inlining. */ private final Stack<String> inlineInstances = new Stack<String>(); private final int recursionDepth[] = new int[1]; // number of calls to the same procedure currently being inlined private final AltSynthesizer syn; private DeclStore repository; private InlineUtils utils; private final boolean closuresOnly; /** * Create a new Inliner. * * @param job the Job whose procedures (methods, constructors, and closures) are to have their procedure calls inlined * @param ts the current TypeSystem * @param nf the current NodeFactory * @param closuresOnly are we running a restricted inliner that is just cleaning up apply on literal closures? */ public Inliner(Job job, TypeSystem ts, NodeFactory nf, boolean closuresOnly) { super(job, ts, nf); syn = new AltSynthesizer(ts, nf); ExtensionInfo extInfo = (ExtensionInfo) job.extensionInfo(); Configuration config = ((X10CompilerOptions) extInfo.getOptions()).x10_config; this.closuresOnly = closuresOnly; } /** * Initialize this Inliner. * Some of this initialization cannot be done in the constructor or in would introduce cycles in the schedule. * * @see polyglot.visit.ContextVisitor#begin() */ @Override public NodeVisitor begin() { if (!closuresOnly) { repository = job.compiler().getInlinerData(job, ts, nf); } utils = new InlineUtils(job); recursionDepth[0] = INITIAL_RECURSION_DEPTH; timer(); return super.begin(); } /** * Don't inline calls within a Node annotated "@NoInline". * * @param node the Node possibly annotated * @return null, if node is to be walked for inlinable calls, or * node, if inlining should not be performed within it */ public Node override(Node node) { Position pos = node.position(); // DEBUG if (2 <= VERBOSITY && node instanceof SourceFile) { Source s = ((SourceFile) node).source(); Warnings.issue(this.job, "(begin inlining)", pos); } if (ExpressionFlattener.cannotFlatten(node)) { // TODO: check that flattening is actually required if (DEBUG) debug("Cannot flatten: short-circuiting inlining for children of " + node, node); return node; // don't inline inside Nodes that cannot be Flattened } if (node instanceof X10MethodDecl) { return null; // @NoInline annotation means something else } if (node instanceof InlinableCall) { return null; // will handle @NoInline annotation seperately } if (node instanceof CUDAKernel) { return node; // disable inlining within a CUDAKernal XTENLANG-2824. } if (node instanceof ClassDecl) { // Don't try to inline native classes if (utils.inliningProhibited(((X10ClassDecl) node).classDef())) { return node; } } if (utils.inliningProhibited(node)) { // allows whole AST's to be ignored (primarily DEBUG only (remove this) if (DEBUG) debug("Explicit @NoInline annotation: short-circuiting inlining for children of " + node, node); return node; } return null; } /** * Rewrite procedure (method, constructor, closure) call nodes, inlining the body of the callee. * * @see polyglot.visit.ErrorHandlingVisitor#leaveCall(polyglot.ast.Node, polyglot.ast.Node, polyglot.ast.Node, polyglot.visit.NodeVisitor) */ public Node leaveCall(Node parent, Node old, Node n, NodeVisitor v) throws SemanticException { if (!closuresOnly && n instanceof X10MethodDecl && AnnotationUtils.hasAnnotation(((X10MethodDecl) n).methodDef(), utils.InlineOnlyType)) return null; if (!(n instanceof ProcedureCall)) return n; Position pos = n.position(); inliningRequired = false; reasons.clear(); Node result = null; if (n instanceof ClosureCall) { if (4 <= VERBOSITY) Warnings.issue(job, "? inline level " +inlineInstances.size()+ " closure " +n, pos); result = inlineClosureCall((ClosureCall) n); } else if (!closuresOnly && n instanceof InlinableCall) { result = inlineConstant((InlinableCall) n); if (null != result) return result; if (4 <= VERBOSITY) Warnings.issue(job, "? inline level " +inlineInstances.size()+ " call " +n, pos); result = inlineCall((InlinableCall) n); } else { return n; } if (null == result) { // cannot inline this call if (5 <= VERBOSITY || inliningRequired) { String msg = "NOT Inlining: " +n+ " (level " +inlineInstances.size()+ ")"; if (!reasons.isEmpty()) { msg += "\n\tbecause "; for (int i=0; i<reasons.size(); i++) { msg += reasons.get(i); if (i+2 < reasons.size()) msg += ", "; else if (i+2 == reasons.size()) msg += ", and "; else msg += "."; } } Warnings.issue(job, msg, pos); } return n; } if (n instanceof ConstructorCall && result instanceof StmtExpr) { // evaluate the expr // method calls in Stmt context are already sowrapped result = syn.createEval((StmtExpr) result); } if (n != result && 3 <= VERBOSITY) Warnings.issue(job, "INLINING: " +n+ " (level " +inlineInstances.size()+ ")", pos); return result; } private Expr inlineClosureCall(ClosureCall c) { Closure lit = getClosure(c.target()); if (null == lit) { report("of non literal closure call target " +c.target(), c); return null; } lit = (Closure) instantiate(lit, c); if (null == lit) { report("of failure to instatiate closure", c); return null; } lit = (Closure) lit.visit(new X10AlphaRenamer(this)); // Ensure that the last statement of the body is the only return in the closure Block body = InliningRewriter.rewriteClosureBody(lit, job(), context()); if (null == body) { report("normalized closure has no body", c); return null; } List<Expr> args = new ArrayList<Expr>(); int i = 0; for (Expr a : c.arguments()) { args.add(syn.createUncheckedCast(a.position(), a, c.closureInstance().formalTypes().get(i++), context())); } Expr result = rewriteInlinedBody(c.position(), lit.returnType().type(), lit.formals(), body, null, args); if (null == result) { report("of failure to rewrite closure body", c); return null; } result = (Expr) result.visit(this); return result; } private Closure getClosure(Expr target) { if (target instanceof Closure) return (Closure) target; if (target instanceof ParExpr) return getClosure(((ParExpr) target).expr()); return null; } /** * @param call * @return */ private Node inlineConstant(InlinableCall call) { x10.ExtensionInfo x10Info = (x10.ExtensionInfo) job().extensionInfo(); x10Info.stats.startTiming("ConstantPropagator", "ConstantPropagator"); try { X10ProcedureDef def = repository.getDef(call); Type type = def.returnType().get(); List<Type> a = AnnotationUtils.getAnnotations(def, utils.ConstantType); X10CompilerOptions opts = (X10CompilerOptions) job.extensionInfo().getOptions(); ConstantValue cv = extractCompileTimeConstant(type, a, opts, context); if (cv == null) return null; Expr literal = cv.toLit(nf, ts, type, call.position()); return literal; } finally { x10Info.stats.stopTiming(); } } public static ConstantValue extractCompileTimeConstant(Type type, List<? extends Type> annotations, X10CompilerOptions opts, Context context) { if (annotations.isEmpty()) return null; TypeSystem ts = type.typeSystem(); Expr arg = ((X10ClassType) annotations.get(0)).propertyInitializer(0); if (!arg.isConstant() || !arg.type().isSubtype(ts.String(), context)) return null; String name = ((StringValue) arg.constantValue()).value(); Boolean negate = name.startsWith("!"); // hack to allow @CompileTimeConstant("!NO_CHECKS") if (negate) { assert ts.isBoolean(type); name = name.substring(1); } try { Object value = opts.x10_config.get(name); ConstantValue cv = ConstantValue.make(type, negate ? ! (Boolean) value : value); return cv; } catch (ConfigurationError e) { return null; } catch (OptionError e) { return null; } } private Expr inlineCall(InlinableCall call) { if (utils.inliningProhibited(call)) { report("of annotation at call site", call); return null; } ProcedureDecl decl = repository.retrieveDecl(call); if (null == decl) return null; String signature = makeSignatureString(decl); decl = (ProcedureDecl) instantiate(decl, call); if (null == decl) { report("instantiation failure for " + signature, call); return null; } decl = (ProcedureDecl) decl.visit(new X10AlphaRenamer(this)); Expr result = rewriteDecl(call, decl); if (null == result) { report("cannot rewrite decl", call); return null; } if (-1 == inlineInstances.search(signature) && inlineInstances.size() < INLINE_DEPTH_LIMIT) { // non recursive inlining of the inlined body inlineInstances.push(signature); result = (Expr) result.visit(this); inlineInstances.pop(); } else { recursionDepth[0]++; if (recursionDepth[0] <= RECURSION_DEPTH_LIMIT) { // recursive inlining of the inlined body result = (Expr) result.visit(this); } recursionDepth[0]--; } // TODO: tell context that the place of result is the same as that of its surrounding context return result; } /** * @param call * @param decl * @return */ public Expr rewriteDecl(InlinableCall call, ProcedureDecl decl) { LocalDecl thisArg = null; LocalDef thisDef = null; if (null != call.target() && call.target() instanceof Expr) { Expr target = (Expr) call.target(); if (target instanceof Local) { thisDef = ((Local) target).localInstance().def(); } else { if (call instanceof ConstructorCall) { Type type = ((ConstructorCall) call).constructorInstance().returnType(); if (ts.isStruct(type)) { report("cannot inline constructor call with non Local target for struct " + type, call); return null; } } thisArg = reifyThis(call); thisDef = thisArg.localDef(); } } Block body = InliningRewriter.rewriteProcedureBody(decl, thisDef, job(), context()); if (null == body) { return null; } Expr result = rewriteInlinedBody( call.position(), decl instanceof MethodDecl ? ((MethodDecl) decl).returnType().type() : null, decl.formals(), body, thisArg, call.arguments() ); return result; } /** * @param decl * @return */ private String makeSignatureString(ProcedureDecl decl) { String id = ""; id += decl.memberDef().container().get().fullName(); id += "."; id += decl.name(); id += "("; String c = ""; for (Formal f : decl.formals()) { id += c + f.type().nameString(); c = ","; } id += ")"; if (decl instanceof MethodDecl) id += ":" +((MethodDecl) decl).returnType().nameString(); return id; } private Node propagateConstants(Node n) { x10.ExtensionInfo x10Info = (x10.ExtensionInfo) job().extensionInfo(); x10Info.stats.startTiming("ConstantPropagator.context", "ConstantPropagator.context"); Node retNode = n.visit(new ConstantPropagator(job, ts, nf, true).context(context())); x10Info.stats.stopTiming(); return retNode; } private LocalDecl reifyThis(InlinableCall call) { assert call.target() instanceof Expr; Expr target = (Expr) call.target(); Position position = target.position(); if (target instanceof Special && ((Special) target).kind() == Special.SUPER) { target = rewriteSuperAsThis((Special) target); } ProcedureInstance<? extends ProcedureDef> pi = call instanceof Call ? ((Call) call).procedureInstance() : ((ConstructorCall) call).procedureInstance(); Type type = ((MemberDef) pi.def()).container().get(); type = makeTypeMap(pi, call.typeArguments()).reinstantiate(type); Expr expr = syn.createUncheckedCast(position, target, type, context()); LocalDecl thisDecl = syn.createLocalDecl(call.position(), Flags.FINAL, Name.makeFresh("this"), expr); return thisDecl; } /* * In general, when the body of one method is inlined into the body of * another, the keywords "this" and "super" loose their meanings. * InlineRewriter deals with the case of "this". It complains, if it * encounters "super". Rewriting "super" as "(this as ST)" won't work * because we lose the fact that the call is non-virtual. * * However, this rewrite can be used to handle the "this parameter" when * inlining calls of the form "super.foo()" (because the method instance has * already been resolved). (Java does not allow the bare keyword "super" to * occur where an expression is required. It does, of course, allow "this" * to be so used. It just needs to be coerced to the right type.) */ private Special rewriteSuperAsThis(Special special) { assert (special.kind() == Special.SUPER) : "Unexpected special kind: " +special; Special result = nf.Special(special.position(), Special.THIS, special.qualifier()); result = (Special) result.type(special.type()); return result; } private CodeBlock instantiate(final CodeBlock code, InlinableCall call) { try { if (DEBUG) debug("Instantiate " + code, call); TypeParamSubst typeMap = makeTypeMap(call.procedureInstance(), call.typeArguments()); InliningTypeTransformer transformer = new InliningTypeTransformer(typeMap, context().currentCode(), (X10CodeDef) code.codeDef(), call.position()); ContextVisitor visitor = new NodeTransformingVisitor(job, ts, nf, transformer).context(context()); CodeBlock visitedCode = (CodeBlock) code.visit(visitor); return visitedCode; } catch (Throwable e) { String message = "Exception during instantiation of " +code+ " for " +call+ ": " +e; Warnings.issue(job(), message, call.position()); return null; } } /** * @param decl * @return */ private TypeParamSubst makeTypeMap(ProcedureInstance<? extends ProcedureDef> instance, List<TypeNode> typeNodes) { List<Type> typeArgs = new ArrayList<Type>(); List<ParameterType> typeParms = new ArrayList<ParameterType>(); if (!(instance instanceof ConstructorInstance)) { // TODO: remove the condition, currently ConstructorInstances can have a mismatch between type parameters and type arguements but they shouldn't have either so we can ignore them if (typeNodes.isEmpty()) { typeArgs.addAll(instance.typeParameters()); } else { for (TypeNode tn : typeNodes) { typeArgs.add(tn.type()); } } typeParms.addAll(instance.def().typeParameters()); } X10ClassType container = (X10ClassType) ((MemberInstance<? extends ProcedureDef>) instance).container(); if (!instance.def().staticContext()) { List<Type> cTypeArgs = container.typeArguments(); if (cTypeArgs != null) { typeArgs.addAll(cTypeArgs); typeParms.addAll(container.x10Def().typeParameters()); } } if (false) { // TODO enable this path assert (typeArgs.size() == typeParms.size()); return new TypeParamSubst(ts, typeArgs, typeParms); } // NOTE: the rest of this method is a hack to handle a mismatch that should never occur if (typeArgs.size() == typeParms.size()) return new TypeParamSubst(ts, typeArgs, typeParms); String msg = "type args/parms mismatch in class " +instance.getClass(); System.err.println("\nDEBUG: " +msg); System.err.println("\n\tposition = " +instance.position()); System.err.println("\n\tinstance = " +instance); System.err.println("\n\tcontainer = " +container); System.err.println("\n\ttypeArgs = " +typeArgs); System.err.println("\n\ttypeParms = " +typeParms); System.err.println(); assert false; // remove if we ever get here (remove this path if we don't) throw new InternalCompilerError(instance.position(), msg); } private Expr rewriteInlinedBody(Position pos, Type retType, List<Formal> formals, Block body, LocalDecl thisDecl, List<Expr> args) { List<Stmt> statements = new ArrayList<Stmt>(); // declare locals for 'this', if there is one, and any formal parameters if (thisDecl != null) { // declare temporary for "this" arg statements.add(thisDecl); } assert (args.size() == formals.size()); for (int i = 0; i < args.size(); i++) { Expr arg = args.get(i); X10Formal formal = (X10Formal) formals.get(i); X10LocalDef localDef = formal.localDef(); Expr expr = syn.createUncheckedCast(arg.position(), arg, formal.type().type(), context()); localDef.setType(Types.ref(expr.type())); LocalDecl ld = syn.createLocalDecl(formal, expr); tieLocalDefToItself(ld.localDef()); statements.add(ld); } // Create statement expression from body. // body is in normal form: // 1) last statement in body is a return statement, and // 2) this is the only return in the body. List<Stmt> bodyStmts = body.statements(); if (!(bodyStmts.get(bodyStmts.size() - 1) instanceof Return)) { report("body cannot be normalized", body); return null; } Return ret = (Return) bodyStmts.get(bodyStmts.size() - 1); for (Stmt stmt : bodyStmts) { if (stmt != ret) { statements.add(stmt); } } Expr expr = ret.expr(); if (null != expr) { assert null != retType; // null retType => constructor call body (which shouldn't return an expr) expr = syn.createUncheckedCast(expr.position(), expr, retType, context()); } StmtExpr result = syn.createStmtExpr(pos, statements, expr); Expr result2 = (Expr)propagateConstants(result); return result2; } /** * @param d */ private void tieLocalDefToItself(LocalDef d) { tieLocalDef(d, d); } private void tieLocalDef(LocalDef d, LocalDef o) { Type type = Types.get(d.type()); type = Types.addSelfBinding(type, ConstraintManager.getConstraintSystem().makeLocal((X10LocalDef) o)); ((Ref<Type>) d.type()).update(type); } private long lastTime; private long timer () { long time = System.currentTimeMillis(); long delta = time - lastTime; lastTime = time; return delta; } static void debug (String msg, Node node) { debug(0L, msg, node); } private static void debug(long time, String msg, Node node) { try { Thread.sleep(1); System.out.print("DEBUG "); if (null != node) System.out.print(node.position() + ": "); System.out.print(msg); if (0 < time) { System.out.print(" (" +time+ ")"); } System.out.println(); Thread.sleep(1); } catch (InterruptedException e) { // Ignore exception (we are just trying to avoid stepping on writes to STDERR } } private List<String> reasons = new ArrayList<String>(); /** * @param msg * @return */ String report(String msg, Node n) { reasons.add(msg); if (DEBUG) debug("not inlining because " + msg, n); return msg; } }