1
0
mirror of https://github.com/golang/go synced 2024-10-04 20:21:22 -06:00
go/src/cmd/gc/inl.c
Russ Cox 25922c0658 cmd/gc: introduce hidden closure functions earlier
The original implementation of closures created the
underlying top-level function during walk, which is fairly
late in the compilation process and caused ordering-based
complications due to earlier stages that had to be repeated
any number of times.

Create the underlying function during typecheck, much
earlier, so that later stages can be run just once.

The result is a simpler compilation sequence.

R=ken2
CC=golang-dev
https://golang.org/cl/6279049
2012-06-04 17:07:59 -04:00

843 lines
20 KiB
C

// 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:
// 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 <u.h>
#include <libc.h>
#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);
static Node* inlvar(Node *n);
static Node* retvar(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;
if(fn->typecheck)
return;
lno = setlineno(fn);
if (debug['m']>2)
print("typecheck import [%S] %lN { %#H }\n", fn->sym, fn, fn->inl);
// typecheckinl is only used for imported functions;
// their bodies may refer to unsafe as long as the package
// was marked safe during import (which was checked then).
pkg = fnpkg(fn);
if (pkg == localpkg || pkg == nil)
fatal("typecheckinl on local function %lN", fn);
save_safemode = safemode;
safemode = 0;
savefn = curfn;
curfn = fn;
typechecklist(fn->inl, Etop);
fn->typecheck = 1;
curfn = savefn;
safemode = save_safemode;
lineno = lno;
}
// Caninl determines whether fn is inlineable. Currently that means:
// fn is exactly 1 statement, either a return or an assignment, and
// some temporary constraints marked TODO. If fn is inlineable, saves
// fn->nbody in fn->inl and substitutes it with a copy.
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;
// can't handle ... args yet
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);
// nbody will have been typechecked, so we can set this:
fn->typecheck = 1;
// 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 ORANGE:
case OFOR:
case OSELECT:
case OSWITCH:
case OPROC:
case ODEFER:
case ODCL: // declares locals as globals b/c of @"". qualification
case ODCLTYPE: // can't print yet
case ODCLCONST: // can't print yet
return 1;
break;
case OAS:
// x = <N> zero initializing assignments aren't representible in export yet.
// alternatively we may just skip them in printing and hope their DCL printed
// as a var will regenerate it
if(n->right == N)
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) instead of in walkcallclosure,
// 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:
// 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);
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);
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);
break;
}
lineno = lno;
}
static void mkinlcall1(Node **np, Node *fn);
static void
mkinlcall(Node **np, Node *fn)
{
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);
safemode = save_safemode;
}
// 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 i;
Node *n, *call, *saveinlfn, *as, *m;
NodeList *dcl, *ll, *ninit, *body;
Type *t;
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;
if (fn->defn) // local function
dcl = fn->defn->dcl;
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->op == ONAME) {
ll->n->inlvar = inlvar(ll->n);
ninit = list(ninit, nod(ODCL, ll->n->inlvar, N)); // otherwise gen won't emit the allocations for heapallocs
if (ll->n->class == PPARAMOUT) // we rely on the order being correct here
inlretvars = list(inlretvars, ll->n->inlvar);
}
// anonymous return values, synthesize names for use in assignment that replaces return
if(inlretvars == nil && fn->type->outtuple > 0)
for(t = getoutargx(fn->type)->type; t; t = t->down) {
m = retvar(t, i++);
ninit = list(ninit, nod(ODCL, m, N));
inlretvars = list(inlretvars, m);
}
// assign arguments to the parameters' temp names
as = N;
if(fn->type->thistuple) {
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->op == ODOTMETH) {
if(!n->left->left)
fatal("method call without receiver: %+N", n);
if(t == T)
fatal("method call unknown receiver type: %+N", n);
if(t->nname != N && !isblank(t->nname))
as = nod(OAS, t->nname->inlvar, n->left->left);
else
as = nod(OAS, temp(t->type), n->left->left);
} else { // non-method call to method
if (!n->list)
fatal("non-method call to method without first arg: %+N", n);
if(t != T && t->nname != N && !isblank(t->nname))
as = nod(OAS, t->nname->inlvar, n->list->n);
}
if(as != N) {
typecheck(&as, Etop);
ninit = list(ninit, as);
}
}
as = nod(OAS2, N, N);
if(fn->type->intuple > 1 && n->list && !n->list->next) {
// TODO check that n->list->n is a call?
// TODO: non-method call to T.meth(f()) where f returns t, args...
as->rlist = n->list;
for(t = getinargx(fn->type)->type; t; t=t->down) {
if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
} else {
as->list = list(as->list, temp(t->type));
}
}
} else {
ll = n->list;
if(fn->type->thistuple && n->left->op != ODOTMETH) // non method call to method
ll=ll->next; // was handled above in if(thistuple)
for(t = getinargx(fn->type)->type; t && ll; t=t->down) {
if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
as->rlist = list(as->rlist, ll->n);
}
ll=ll->next;
}
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);
}
// 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();
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);
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);
*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
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;
}
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)
{
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;
}
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);
}