mirror of
https://github.com/golang/go
synced 2024-10-04 20:21:22 -06:00
25922c0658
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
843 lines
20 KiB
C
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);
|
|
}
|