mirror of
https://github.com/golang/go
synced 2024-11-20 10:14:43 -07:00
031b389ac1
Was describing an old implementation. R=ken2 CC=golang-dev https://golang.org/cl/6553066
840 lines
20 KiB
C
840 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.
|
|
// If so, caninl 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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);
|
|
as = nod(OAS, tinlvar(t), 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)
|
|
as = nod(OAS, tinlvar(t), 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)
|
|
as->list = list(as->list, tinlvar(t));
|
|
} 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) {
|
|
as->list = list(as->list, tinlvar(t));
|
|
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);
|
|
}
|