// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // The inlining facility makes 2 passes: first caninl determines which // functions are suitable for inlining, and for those that are it // saves a copy of the body. Then inlcalls walks each function body to // expand calls to inlinable functions. // // The debug['l'] flag controls the agressiveness. Note that main() swaps level 0 and 1, // making 1 the default and -l disable. -ll and more is useful to flush out bugs. // These additional levels (beyond -l) may be buggy and are not supported. // 0: disabled // 1: 40-nodes leaf functions, oneliners, lazy typechecking (default) // 2: early typechecking of all imported bodies // 3: allow variadic functions // 4: allow non-leaf functions , (breaks runtime.Caller) // 5: transitive inlining // // At some point this may get another default and become switch-offable with -N. // // The debug['m'] flag enables diagnostic output. a single -m is useful for verifying // which calls get inlined or not, more is for debugging, and may go away at any point. // // TODO: // - inline functions with ... args // - handle T.meth(f()) with func f() (t T, arg, arg, ) #include #include #include "go.h" // Used by caninl. static Node* inlcopy(Node *n); static NodeList* inlcopylist(NodeList *ll); static int ishairy(Node *n, int *budget); static int ishairylist(NodeList *ll, int *budget); // Used by inlcalls static void inlnodelist(NodeList *l); static void inlnode(Node **np); static void mkinlcall(Node **np, Node *fn, int isddd); static Node* inlvar(Node *n); static Node* retvar(Type *n, int i); static Node* argvar(Type *n, int i); static Node* newlabel(void); static Node* inlsubst(Node *n); static NodeList* inlsubstlist(NodeList *l); static void setlno(Node*, int); // Used during inlsubst[list] static Node *inlfn; // function currently being inlined static Node *inlretlabel; // target of the goto substituted in place of a return static NodeList *inlretvars; // temp out variables // Get the function's package. For ordinary functions it's on the ->sym, but for imported methods // the ->sym can be re-used in the local package, so peel it off the receiver's type. static Pkg* fnpkg(Node *fn) { Type *rcvr; if(fn->type->thistuple) { // method rcvr = getthisx(fn->type)->type->type; if(isptr[rcvr->etype]) rcvr = rcvr->type; if(!rcvr->sym) fatal("receiver with no sym: [%S] %lN (%T)", fn->sym, fn, rcvr); return rcvr->sym->pkg; } // non-method return fn->sym->pkg; } // Lazy typechecking of imported bodies. For local functions, caninl will set ->typecheck // because they're a copy of an already checked body. void typecheckinl(Node *fn) { Node *savefn; Pkg *pkg; int save_safemode, lno; lno = setlineno(fn); // typecheckinl is only for imported functions; // their bodies may refer to unsafe as long as the package // was marked safe during import (which was checked then). // the ->inl of a local function has been typechecked before caninl copied it. pkg = fnpkg(fn); if (pkg == localpkg || pkg == nil) return; // typecheckinl on local function if (debug['m']>2) print("typecheck import [%S] %lN { %#H }\n", fn->sym, fn, fn->inl); save_safemode = safemode; safemode = 0; savefn = curfn; curfn = fn; typechecklist(fn->inl, Etop); curfn = savefn; safemode = save_safemode; lineno = lno; } // Caninl determines whether fn is inlineable. // If so, caninl saves fn->nbody in fn->inl and substitutes it with a copy. // fn and ->nbody will already have been typechecked. void caninl(Node *fn) { Node *savefn; Type *t; int budget; if(fn->op != ODCLFUNC) fatal("caninl %N", fn); if(!fn->nname) fatal("caninl no nname %+N", fn); // If fn has no body (is defined outside of Go), cannot inline it. if(fn->nbody == nil) return; if(fn->typecheck == 0) fatal("caninl on non-typechecked function %N", fn); // can't handle ... args yet if(debug['l'] < 3) for(t=fn->type->type->down->down->type; t; t=t->down) if(t->isddd) return; budget = 40; // allowed hairyness if(ishairylist(fn->nbody, &budget)) return; savefn = curfn; curfn = fn; fn->nname->inl = fn->nbody; fn->nbody = inlcopylist(fn->nname->inl); fn->nname->inldcl = inlcopylist(fn->nname->defn->dcl); // hack, TODO, check for better way to link method nodes back to the thing with the ->inl // this is so export can find the body of a method fn->type->nname = fn->nname; if(debug['m'] > 1) print("%L: can inline %#N as: %#T { %#H }\n", fn->lineno, fn->nname, fn->type, fn->nname->inl); else if(debug['m']) print("%L: can inline %N\n", fn->lineno, fn->nname); curfn = savefn; } // Look for anything we want to punt on. static int ishairylist(NodeList *ll, int* budget) { for(;ll;ll=ll->next) if(ishairy(ll->n, budget)) return 1; return 0; } static int ishairy(Node *n, int *budget) { if(!n) return 0; // Things that are too hairy, irrespective of the budget switch(n->op) { case OCALL: case OCALLFUNC: case OCALLINTER: case OCALLMETH: case OPANIC: case ORECOVER: if(debug['l'] < 4) return 1; break; case OCLOSURE: case OCALLPART: case ORANGE: case OFOR: case OSELECT: case OSWITCH: case OPROC: case ODEFER: case ODCLTYPE: // can't print yet case ODCLCONST: // can't print yet case ORETJMP: return 1; break; } (*budget)--; return *budget < 0 || ishairy(n->left, budget) || ishairy(n->right, budget) || ishairylist(n->list, budget) || ishairylist(n->rlist, budget) || ishairylist(n->ninit, budget) || ishairy(n->ntest, budget) || ishairy(n->nincr, budget) || ishairylist(n->nbody, budget) || ishairylist(n->nelse, budget); } // Inlcopy and inlcopylist recursively copy the body of a function. // Any name-like node of non-local class is marked for re-export by adding it to // the exportlist. static NodeList* inlcopylist(NodeList *ll) { NodeList *l; l = nil; for(; ll; ll=ll->next) l = list(l, inlcopy(ll->n)); return l; } static Node* inlcopy(Node *n) { Node *m; if(n == N) return N; switch(n->op) { case ONAME: case OTYPE: case OLITERAL: return n; } m = nod(OXXX, N, N); *m = *n; m->inl = nil; m->left = inlcopy(n->left); m->right = inlcopy(n->right); m->list = inlcopylist(n->list); m->rlist = inlcopylist(n->rlist); m->ninit = inlcopylist(n->ninit); m->ntest = inlcopy(n->ntest); m->nincr = inlcopy(n->nincr); m->nbody = inlcopylist(n->nbody); m->nelse = inlcopylist(n->nelse); return m; } // Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any // calls made to inlineable functions. This is the external entry point. void inlcalls(Node *fn) { Node *savefn; savefn = curfn; curfn = fn; inlnode(&fn); if(fn != curfn) fatal("inlnode replaced curfn"); curfn = savefn; } // Turn an OINLCALL into a statement. static void inlconv2stmt(Node *n) { n->op = OBLOCK; // n->ninit stays n->list = n->nbody; n->nbody = nil; n->rlist = nil; } // Turn an OINLCALL into a single valued expression. static void inlconv2expr(Node **np) { Node *n, *r; n = *np; r = n->rlist->n; addinit(&r, concat(n->ninit, n->nbody)); *np = r; } // Turn the rlist (with the return values) of the OINLCALL in // n into an expression list lumping the ninit and body // containing the inlined statements on the first list element so // order will be preserved Used in return, oas2func and call // statements. static NodeList* inlconv2list(Node *n) { NodeList *l; if(n->op != OINLCALL || n->rlist == nil) fatal("inlconv2list %+N\n", n); l = n->rlist; addinit(&l->n, concat(n->ninit, n->nbody)); return l; } static void inlnodelist(NodeList *l) { for(; l; l=l->next) inlnode(&l->n); } // inlnode recurses over the tree to find inlineable calls, which will // be turned into OINLCALLs by mkinlcall. When the recursion comes // back up will examine left, right, list, rlist, ninit, ntest, nincr, // nbody and nelse and use one of the 4 inlconv/glue functions above // to turn the OINLCALL into an expression, a statement, or patch it // in to this nodes list or rlist as appropriate. // NOTE it makes no sense to pass the glue functions down the // recursion to the level where the OINLCALL gets created because they // have to edit /this/ n, so you'd have to push that one down as well, // but then you may as well do it here. so this is cleaner and // shorter and less complicated. static void inlnode(Node **np) { Node *n; NodeList *l; int lno; if(*np == nil) return; n = *np; switch(n->op) { case ODEFER: case OPROC: // inhibit inlining of their argument switch(n->left->op) { case OCALLFUNC: case OCALLMETH: n->left->etype = n->op; } case OCLOSURE: // TODO do them here (or earlier), // so escape analysis can avoid more heapmoves. return; } lno = setlineno(n); inlnodelist(n->ninit); for(l=n->ninit; l; l=l->next) if(l->n->op == OINLCALL) inlconv2stmt(l->n); inlnode(&n->left); if(n->left && n->left->op == OINLCALL) inlconv2expr(&n->left); inlnode(&n->right); if(n->right && n->right->op == OINLCALL) inlconv2expr(&n->right); inlnodelist(n->list); switch(n->op) { case OBLOCK: for(l=n->list; l; l=l->next) if(l->n->op == OINLCALL) inlconv2stmt(l->n); break; case ORETURN: case OCALLFUNC: case OCALLMETH: case OCALLINTER: case OAPPEND: case OCOMPLEX: // if we just replaced arg in f(arg()) or return arg with an inlined call // and arg returns multiple values, glue as list if(count(n->list) == 1 && n->list->n->op == OINLCALL && count(n->list->n->rlist) > 1) { n->list = inlconv2list(n->list->n); break; } // fallthrough default: for(l=n->list; l; l=l->next) if(l->n->op == OINLCALL) inlconv2expr(&l->n); } inlnodelist(n->rlist); switch(n->op) { case OAS2FUNC: if(n->rlist->n->op == OINLCALL) { n->rlist = inlconv2list(n->rlist->n); n->op = OAS2; n->typecheck = 0; typecheck(np, Etop); break; } // fallthrough default: for(l=n->rlist; l; l=l->next) if(l->n->op == OINLCALL) inlconv2expr(&l->n); } inlnode(&n->ntest); if(n->ntest && n->ntest->op == OINLCALL) inlconv2expr(&n->ntest); inlnode(&n->nincr); if(n->nincr && n->nincr->op == OINLCALL) inlconv2stmt(n->nincr); inlnodelist(n->nbody); for(l=n->nbody; l; l=l->next) if(l->n->op == OINLCALL) inlconv2stmt(l->n); inlnodelist(n->nelse); for(l=n->nelse; l; l=l->next) if(l->n->op == OINLCALL) inlconv2stmt(l->n); // with all the branches out of the way, it is now time to // transmogrify this node itself unless inhibited by the // switch at the top of this function. switch(n->op) { case OCALLFUNC: case OCALLMETH: if (n->etype == OPROC || n->etype == ODEFER) return; } switch(n->op) { case OCALLFUNC: if(debug['m']>3) print("%L:call to func %+N\n", n->lineno, n->left); if(n->left->inl) // normal case mkinlcall(np, n->left, n->isddd); else if(n->left->op == ONAME && n->left->left && n->left->left->op == OTYPE && n->left->right && n->left->right->op == ONAME) // methods called as functions if(n->left->sym->def) mkinlcall(np, n->left->sym->def, n->isddd); break; case OCALLMETH: if(debug['m']>3) print("%L:call to meth %lN\n", n->lineno, n->left->right); // typecheck should have resolved ODOTMETH->type, whose nname points to the actual function. if(n->left->type == T) fatal("no function type for [%p] %+N\n", n->left, n->left); if(n->left->type->nname == N) fatal("no function definition for [%p] %+T\n", n->left->type, n->left->type); mkinlcall(np, n->left->type->nname, n->isddd); break; } lineno = lno; } static void mkinlcall1(Node **np, Node *fn, int isddd); static void mkinlcall(Node **np, Node *fn, int isddd) { int save_safemode; Pkg *pkg; save_safemode = safemode; // imported functions may refer to unsafe as long as the // package was marked safe during import (already checked). pkg = fnpkg(fn); if(pkg != localpkg && pkg != nil) safemode = 0; mkinlcall1(np, fn, isddd); safemode = save_safemode; } static Node* tinlvar(Type *t) { if(t->nname && !isblank(t->nname)) { if(!t->nname->inlvar) fatal("missing inlvar for %N\n", t->nname); return t->nname->inlvar; } typecheck(&nblank, Erv | Easgn); return nblank; } static int inlgen; // if *np is a call, and fn is a function with an inlinable body, substitute *np with an OINLCALL. // On return ninit has the parameter assignments, the nbody is the // inlined function body and list, rlist contain the input, output // parameters. static void mkinlcall1(Node **np, Node *fn, int isddd) { int i; int chkargcount; Node *n, *call, *saveinlfn, *as, *m; NodeList *dcl, *ll, *ninit, *body; Type *t; // For variadic fn. int variadic, varargcount, multiret; Node *vararg; NodeList *varargs; Type *varargtype, *vararrtype; if (fn->inl == nil) return; if (fn == curfn || fn->defn == curfn) return; if(debug['l']<2) typecheckinl(fn); n = *np; // Bingo, we have a function node, and it has an inlineable body if(debug['m']>1) print("%L: inlining call to %S %#T { %#H }\n", n->lineno, fn->sym, fn->type, fn->inl); else if(debug['m']) print("%L: inlining call to %N\n", n->lineno, fn); if(debug['m']>2) print("%L: Before inlining: %+N\n", n->lineno, n); saveinlfn = inlfn; inlfn = fn; ninit = n->ninit; //dumplist("ninit pre", ninit); if(fn->defn) // local function dcl = fn->inldcl; else // imported function dcl = fn->dcl; inlretvars = nil; i = 0; // Make temp names to use instead of the originals for(ll = dcl; ll; ll=ll->next) { if(ll->n->class == PPARAMOUT) // return values handled below. continue; if(ll->n->op == ONAME) { ll->n->inlvar = inlvar(ll->n); // Typecheck because inlvar is not necessarily a function parameter. typecheck(&ll->n->inlvar, Erv); if ((ll->n->class&~PHEAP) != PAUTO) ninit = list(ninit, nod(ODCL, ll->n->inlvar, N)); // otherwise gen won't emit the allocations for heapallocs } } // temporaries for return values. for(t = getoutargx(fn->type)->type; t; t = t->down) { if(t != T && t->nname != N && !isblank(t->nname)) { m = inlvar(t->nname); typecheck(&m, Erv); t->nname->inlvar = m; } else { // anonymous return values, synthesize names for use in assignment that replaces return m = retvar(t, i++); } ninit = list(ninit, nod(ODCL, m, N)); inlretvars = list(inlretvars, m); } // assign receiver. if(fn->type->thistuple && n->left->op == ODOTMETH) { // method call with a receiver. t = getthisx(fn->type)->type; if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar) fatal("missing inlvar for %N\n", t->nname); if(!n->left->left) fatal("method call without receiver: %+N", n); if(t == T) fatal("method call unknown receiver type: %+N", n); as = nod(OAS, tinlvar(t), n->left->left); if(as != N) { typecheck(&as, Etop); ninit = list(ninit, as); } } // check if inlined function is variadic. variadic = 0; varargtype = T; varargcount = 0; for(t=fn->type->type->down->down->type; t; t=t->down) { if(t->isddd) { variadic = 1; varargtype = t->type; } } // but if argument is dotted too forget about variadicity. if(variadic && isddd) variadic = 0; // check if argument is actually a returned tuple from call. multiret = 0; if(n->list && !n->list->next) { switch(n->list->n->op) { case OCALL: case OCALLFUNC: case OCALLINTER: case OCALLMETH: if(n->list->n->left->type->outtuple > 1) multiret = n->list->n->left->type->outtuple-1; } } if(variadic) { varargcount = count(n->list) + multiret; if(n->left->op != ODOTMETH) varargcount -= fn->type->thistuple; varargcount -= fn->type->intuple - 1; } // assign arguments to the parameters' temp names as = nod(OAS2, N, N); as->rlist = n->list; ll = n->list; // TODO: if len(nlist) == 1 but multiple args, check that n->list->n is a call? if(fn->type->thistuple && n->left->op != ODOTMETH) { // non-method call to method if(!n->list) fatal("non-method call to method without first arg: %+N", n); // append receiver inlvar to LHS. t = getthisx(fn->type)->type; if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar) fatal("missing inlvar for %N\n", t->nname); if(t == T) fatal("method call unknown receiver type: %+N", n); as->list = list(as->list, tinlvar(t)); ll = ll->next; // track argument count. } // append ordinary arguments to LHS. chkargcount = n->list && n->list->next; vararg = N; // the slice argument to a variadic call varargs = nil; // the list of LHS names to put in vararg. if(!chkargcount) { // 0 or 1 expression on RHS. for(t = getinargx(fn->type)->type; t; t=t->down) { if(variadic && t->isddd) { vararg = tinlvar(t); for(i=0; ilist = list(as->list, m); } break; } as->list = list(as->list, tinlvar(t)); } } else { // match arguments except final variadic (unless the call is dotted itself) for(t = getinargx(fn->type)->type; t;) { if(!ll) break; if(variadic && t->isddd) break; as->list = list(as->list, tinlvar(t)); t=t->down; ll=ll->next; } // match varargcount arguments with variadic parameters. if(variadic && t && t->isddd) { vararg = tinlvar(t); for(i=0; ilist = list(as->list, m); ll=ll->next; } if(i==varargcount) t=t->down; } if(ll || t) fatal("arg count mismatch: %#T vs %,H\n", getinargx(fn->type), n->list); } if (as->rlist) { typecheck(&as, Etop); ninit = list(ninit, as); } // turn the variadic args into a slice. if(variadic) { as = nod(OAS, vararg, N); if(!varargcount) { as->right = nodnil(); as->right->type = varargtype; } else { vararrtype = typ(TARRAY); vararrtype->type = varargtype->type; vararrtype->bound = varargcount; as->right = nod(OCOMPLIT, N, typenod(varargtype)); as->right->list = varargs; as->right = nod(OSLICE, as->right, nod(OKEY, N, N)); } typecheck(&as, Etop); ninit = list(ninit, as); } // zero the outparams for(ll = inlretvars; ll; ll=ll->next) { as = nod(OAS, ll->n, N); typecheck(&as, Etop); ninit = list(ninit, as); } inlretlabel = newlabel(); inlgen++; body = inlsubstlist(fn->inl); body = list(body, nod(OGOTO, inlretlabel, N)); // avoid 'not used' when function doesnt have return body = list(body, nod(OLABEL, inlretlabel, N)); typechecklist(body, Etop); //dumplist("ninit post", ninit); call = nod(OINLCALL, N, N); call->ninit = ninit; call->nbody = body; call->rlist = inlretvars; call->type = n->type; call->typecheck = 1; setlno(call, n->lineno); //dumplist("call body", body); *np = call; inlfn = saveinlfn; // transitive inlining // TODO do this pre-expansion on fn->inl directly. requires // either supporting exporting statemetns with complex ninits // or saving inl and making inlinl if(debug['l'] >= 5) { body = fn->inl; fn->inl = nil; // prevent infinite recursion inlnodelist(call->nbody); for(ll=call->nbody; ll; ll=ll->next) if(ll->n->op == OINLCALL) inlconv2stmt(ll->n); fn->inl = body; } if(debug['m']>2) print("%L: After inlining %+N\n\n", n->lineno, *np); } // Every time we expand a function we generate a new set of tmpnames, // PAUTO's in the calling functions, and link them off of the // PPARAM's, PAUTOS and PPARAMOUTs of the called function. static Node* inlvar(Node *var) { Node *n; if(debug['m']>3) print("inlvar %+N\n", var); n = newname(var->sym); n->type = var->type; n->class = PAUTO; n->used = 1; n->curfn = curfn; // the calling function, not the called one n->addrtaken = var->addrtaken; // Esc pass wont run if we're inlining into a iface wrapper. // Luckily, we can steal the results from the target func. // If inlining a function defined in another package after // escape analysis is done, treat all local vars as escaping. // See issue 9537. if(var->esc == EscHeap || (inl_nonlocal && var->op == ONAME)) addrescapes(n); curfn->dcl = list(curfn->dcl, n); return n; } // Synthesize a variable to store the inlined function's results in. static Node* retvar(Type *t, int i) { Node *n; snprint(namebuf, sizeof(namebuf), "~r%d", i); n = newname(lookup(namebuf)); n->type = t->type; n->class = PAUTO; n->used = 1; n->curfn = curfn; // the calling function, not the called one curfn->dcl = list(curfn->dcl, n); return n; } // Synthesize a variable to store the inlined function's arguments // when they come from a multiple return call. static Node* argvar(Type *t, int i) { Node *n; snprint(namebuf, sizeof(namebuf), "~arg%d", i); n = newname(lookup(namebuf)); n->type = t->type; n->class = PAUTO; n->used = 1; n->curfn = curfn; // the calling function, not the called one curfn->dcl = list(curfn->dcl, n); return n; } static Node* newlabel(void) { Node *n; static int label; label++; snprint(namebuf, sizeof(namebuf), ".inlret%.6d", label); n = newname(lookup(namebuf)); n->etype = 1; // flag 'safe' for escape analysis (no backjumps) return n; } // inlsubst and inlsubstlist recursively copy the body of the saved // pristine ->inl body of the function while substituting references // to input/output parameters with ones to the tmpnames, and // substituting returns with assignments to the output. static NodeList* inlsubstlist(NodeList *ll) { NodeList *l; l = nil; for(; ll; ll=ll->next) l = list(l, inlsubst(ll->n)); return l; } static Node* inlsubst(Node *n) { char *p; Node *m, *as; NodeList *ll; if(n == N) return N; switch(n->op) { case ONAME: if(n->inlvar) { // These will be set during inlnode if (debug['m']>2) print ("substituting name %+N -> %+N\n", n, n->inlvar); return n->inlvar; } if (debug['m']>2) print ("not substituting name %+N\n", n); return n; case OLITERAL: case OTYPE: return n; case ORETURN: // Since we don't handle bodies with closures, this return is guaranteed to belong to the current inlined function. // dump("Return before substitution", n); m = nod(OGOTO, inlretlabel, N); m->ninit = inlsubstlist(n->ninit); if(inlretvars && n->list) { as = nod(OAS2, N, N); // shallow copy or OINLCALL->rlist will be the same list, and later walk and typecheck may clobber that. for(ll=inlretvars; ll; ll=ll->next) as->list = list(as->list, ll->n); as->rlist = inlsubstlist(n->list); typecheck(&as, Etop); m->ninit = list(m->ninit, as); } typechecklist(m->ninit, Etop); typecheck(&m, Etop); // dump("Return after substitution", m); return m; case OGOTO: case OLABEL: m = nod(OXXX, N, N); *m = *n; m->ninit = nil; p = smprint("%s·%d", n->left->sym->name, inlgen); m->left = newname(lookup(p)); free(p); return m; } m = nod(OXXX, N, N); *m = *n; m->ninit = nil; if(n->op == OCLOSURE) fatal("cannot inline function containing closure: %+N", n); m->left = inlsubst(n->left); m->right = inlsubst(n->right); m->list = inlsubstlist(n->list); m->rlist = inlsubstlist(n->rlist); m->ninit = concat(m->ninit, inlsubstlist(n->ninit)); m->ntest = inlsubst(n->ntest); m->nincr = inlsubst(n->nincr); m->nbody = inlsubstlist(n->nbody); m->nelse = inlsubstlist(n->nelse); return m; } // Plaster over linenumbers static void setlnolist(NodeList *ll, int lno) { for(;ll;ll=ll->next) setlno(ll->n, lno); } static void setlno(Node *n, int lno) { if(!n) return; // don't clobber names, unless they're freshly synthesized if(n->op != ONAME || n->lineno == 0) n->lineno = lno; setlno(n->left, lno); setlno(n->right, lno); setlnolist(n->list, lno); setlnolist(n->rlist, lno); setlnolist(n->ninit, lno); setlno(n->ntest, lno); setlno(n->nincr, lno); setlnolist(n->nbody, lno); setlnolist(n->nelse, lno); }