root/src/cmd/gc/inl.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. fnpkg
  2. typecheckinl
  3. caninl
  4. ishairylist
  5. ishairy
  6. inlcopylist
  7. inlcopy
  8. inlcalls
  9. inlconv2stmt
  10. inlconv2expr
  11. inlconv2list
  12. inlnodelist
  13. inlnode
  14. mkinlcall
  15. tinlvar
  16. mkinlcall1
  17. inlvar
  18. retvar
  19. argvar
  20. newlabel
  21. inlsubstlist
  22. inlsubst
  23. setlnolist
  24. setlno

// 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 <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, 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; i<varargcount && ll; i++) {
                                        m = argvar(varargtype, i);
                                        varargs = list(varargs, m);
                                        as->list = 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; i<varargcount && ll; i++) {
                                m = argvar(varargtype, i);
                                varargs = list(varargs, m);
                                as->list = 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(var->esc == EscHeap)
                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);
}

/* [<][>][^][v][top][bottom][index][help] */