blob: 35b6d4b1b4e9bfeec1397615a6195d6afcb0df02 [file] [log] [blame]
// Copyright 2009 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.
/*
* function literals aka closures
*/
#include <u.h>
#include <libc.h>
#include "go.h"
void
closurehdr(Node *ntype)
{
Node *n, *name, *a;
NodeList *l;
n = nod(OCLOSURE, N, N);
n->ntype = ntype;
n->funcdepth = funcdepth;
n->outerfunc = curfn;
funchdr(n);
// steal ntype's argument names and
// leave a fresh copy in their place.
// references to these variables need to
// refer to the variables in the external
// function declared below; see walkclosure.
n->list = ntype->list;
n->rlist = ntype->rlist;
ntype->list = nil;
ntype->rlist = nil;
for(l=n->list; l; l=l->next) {
name = l->n->left;
if(name)
name = newname(name->sym);
a = nod(ODCLFIELD, name, l->n->right);
a->isddd = l->n->isddd;
if(name)
name->isddd = a->isddd;
ntype->list = list(ntype->list, a);
}
for(l=n->rlist; l; l=l->next) {
name = l->n->left;
if(name)
name = newname(name->sym);
ntype->rlist = list(ntype->rlist, nod(ODCLFIELD, name, l->n->right));
}
}
Node*
closurebody(NodeList *body)
{
Node *func, *v;
NodeList *l;
if(body == nil)
body = list1(nod(OEMPTY, N, N));
func = curfn;
func->nbody = body;
func->endlineno = lineno;
funcbody(func);
// closure-specific variables are hanging off the
// ordinary ones in the symbol table; see oldname.
// unhook them.
// make the list of pointers for the closure call.
for(l=func->cvars; l; l=l->next) {
v = l->n;
v->closure->closure = v->outer;
v->outerexpr = oldname(v->sym);
}
return func;
}
static Node* makeclosure(Node *func);
void
typecheckclosure(Node *func, int top)
{
Node *oldfn, *n;
NodeList *l;
int olddd;
for(l=func->cvars; l; l=l->next) {
n = l->n->closure;
if(!n->captured) {
n->captured = 1;
if(n->decldepth == 0)
fatal("typecheckclosure: var %hN does not have decldepth assigned", n);
// Ignore assignments to the variable in straightline code
// preceding the first capturing by a closure.
if(n->decldepth == decldepth)
n->assigned = 0;
}
}
for(l=func->dcl; l; l=l->next)
if(l->n->op == ONAME && (l->n->class == PPARAM || l->n->class == PPARAMOUT))
l->n->decldepth = 1;
oldfn = curfn;
typecheck(&func->ntype, Etype);
func->type = func->ntype->type;
func->top = top;
// Type check the body now, but only if we're inside a function.
// At top level (in a variable initialization: curfn==nil) we're not
// ready to type check code yet; we'll check it later, because the
// underlying closure function we create is added to xtop.
if(curfn && func->type != T) {
curfn = func;
olddd = decldepth;
decldepth = 1;
typechecklist(func->nbody, Etop);
decldepth = olddd;
curfn = oldfn;
}
// Create top-level function
xtop = list(xtop, makeclosure(func));
}
// closurename returns name for OCLOSURE n.
// It is not as simple as it ought to be, because we typecheck nested closures
// starting from the innermost one. So when we check the inner closure,
// we don't yet have name for the outer closure. This function uses recursion
// to generate names all the way up if necessary.
static Sym*
closurename(Node *n)
{
static int closgen;
char *outer, *prefix;
int gen;
if(n->sym != S)
return n->sym;
gen = 0;
outer = nil;
prefix = nil;
if(n->outerfunc == N) {
// Global closure.
outer = "glob";
prefix = "func";
gen = ++closgen;
} else if(n->outerfunc->op == ODCLFUNC) {
// The outermost closure inside of a named function.
outer = n->outerfunc->nname->sym->name;
prefix = "func";
// Yes, functions can be named _.
// Can't use function closgen in such case,
// because it would lead to name clashes.
if(!isblank(n->outerfunc->nname))
gen = ++n->outerfunc->closgen;
else
gen = ++closgen;
} else if(n->outerfunc->op == OCLOSURE) {
// Nested closure, recurse.
outer = closurename(n->outerfunc)->name;
prefix = "";
gen = ++n->outerfunc->closgen;
} else
fatal("closurename called for %hN", n);
snprint(namebuf, sizeof namebuf, "%s.%s%d", outer, prefix, gen);
n->sym = lookup(namebuf);
return n->sym;
}
static Node*
makeclosure(Node *func)
{
Node *xtype, *xfunc;
/*
* wrap body in external function
* that begins by reading closure parameters.
*/
xtype = nod(OTFUNC, N, N);
xtype->list = func->list;
xtype->rlist = func->rlist;
// create the function
xfunc = nod(ODCLFUNC, N, N);
xfunc->nname = newname(closurename(func));
xfunc->nname->sym->flags |= SymExported; // disable export
xfunc->nname->ntype = xtype;
xfunc->nname->defn = xfunc;
declare(xfunc->nname, PFUNC);
xfunc->nname->funcdepth = func->funcdepth;
xfunc->funcdepth = func->funcdepth;
xfunc->endlineno = func->endlineno;
xfunc->nbody = func->nbody;
xfunc->dcl = concat(func->dcl, xfunc->dcl);
if(xfunc->nbody == nil)
fatal("empty body - won't generate any code");
typecheck(&xfunc, Etop);
xfunc->closure = func;
func->closure = xfunc;
func->nbody = nil;
func->list = nil;
func->rlist = nil;
return xfunc;
}
// capturevars is called in a separate phase after all typechecking is done.
// It decides whether each variable captured by a closure should be captured
// by value or by reference.
// We use value capturing for values <= 128 bytes that are never reassigned
// after capturing (effectively constant).
void
capturevars(Node *xfunc)
{
Node *func, *v, *outer;
NodeList *l;
int lno;
lno = lineno;
lineno = xfunc->lineno;
func = xfunc->closure;
func->enter = nil;
for(l=func->cvars; l; l=l->next) {
v = l->n;
if(v->type == T) {
// if v->type is nil, it means v looked like it was
// going to be used in the closure but wasn't.
// this happens because when parsing a, b, c := f()
// the a, b, c gets parsed as references to older
// a, b, c before the parser figures out this is a
// declaration.
v->op = OXXX;
continue;
}
// type check the & of closed variables outside the closure,
// so that the outer frame also grabs them and knows they escape.
dowidth(v->type);
outer = v->outerexpr;
v->outerexpr = N;
// out parameters will be assigned to implicitly upon return.
if(outer->class != PPARAMOUT && !v->closure->addrtaken && !v->closure->assigned && v->type->width <= 128)
v->byval = 1;
else {
v->closure->addrtaken = 1;
outer = nod(OADDR, outer, N);
}
if(debug['m'] > 1) {
Sym *name;
char *how;
name = nil;
if(v->curfn && v->curfn->nname)
name = v->curfn->nname->sym;
how = "ref";
if(v->byval)
how = "value";
warnl(v->lineno, "%S capturing by %s: %S (addr=%d assign=%d width=%d)",
name, how,
v->sym, v->closure->addrtaken, v->closure->assigned, (int32)v->type->width);
}
typecheck(&outer, Erv);
func->enter = list(func->enter, outer);
}
lineno = lno;
}
// transformclosure is called in a separate phase after escape analysis.
// It transform closure bodies to properly reference captured variables.
void
transformclosure(Node *xfunc)
{
Node *func, *cv, *addr, *v, *f;
NodeList *l, *body;
Type **param, *fld;
vlong offset;
int lno, nvar;
lno = lineno;
lineno = xfunc->lineno;
func = xfunc->closure;
if(func->top&Ecall) {
// If the closure is directly called, we transform it to a plain function call
// with variables passed as args. This avoids allocation of a closure object.
// Here we do only a part of the transformation. Walk of OCALLFUNC(OCLOSURE)
// will complete the transformation later.
// For illustration, the following closure:
// func(a int) {
// println(byval)
// byref++
// }(42)
// becomes:
// func(a int, byval int, &byref *int) {
// println(byval)
// (*&byref)++
// }(42, byval, &byref)
// f is ONAME of the actual function.
f = xfunc->nname;
// Get pointer to input arguments and rewind to the end.
// We are going to append captured variables to input args.
param = &getinargx(f->type)->type;
for(; *param; param = &(*param)->down) {
}
for(l=func->cvars; l; l=l->next) {
v = l->n;
if(v->op == OXXX)
continue;
fld = typ(TFIELD);
fld->funarg = 1;
if(v->byval) {
// If v is captured by value, we merely downgrade it to PPARAM.
v->class = PPARAM;
v->ullman = 1;
fld->nname = v;
} else {
// If v of type T is captured by reference,
// we introduce function param &v *T
// and v remains PPARAMREF with &v heapaddr
// (accesses will implicitly deref &v).
snprint(namebuf, sizeof namebuf, "&%s", v->sym->name);
addr = newname(lookup(namebuf));
addr->type = ptrto(v->type);
addr->class = PPARAM;
v->heapaddr = addr;
fld->nname = addr;
}
fld->type = fld->nname->type;
fld->sym = fld->nname->sym;
// Declare the new param and append it to input arguments.
xfunc->dcl = list(xfunc->dcl, fld->nname);
*param = fld;
param = &fld->down;
}
// Recalculate param offsets.
if(f->type->width > 0)
fatal("transformclosure: width is already calculated");
dowidth(f->type);
xfunc->type = f->type; // update type of ODCLFUNC
} else {
// The closure is not called, so it is going to stay as closure.
nvar = 0;
body = nil;
offset = widthptr;
for(l=func->cvars; l; l=l->next) {
v = l->n;
if(v->op == OXXX)
continue;
nvar++;
// cv refers to the field inside of closure OSTRUCTLIT.
cv = nod(OCLOSUREVAR, N, N);
cv->type = v->type;
if(!v->byval)
cv->type = ptrto(v->type);
offset = rnd(offset, cv->type->align);
cv->xoffset = offset;
offset += cv->type->width;
if(v->byval && v->type->width <= 2*widthptr && thearch.thechar == '6') {
// If it is a small variable captured by value, downgrade it to PAUTO.
// This optimization is currently enabled only for amd64, see:
// https://github.com/golang/go/issues/9865
v->class = PAUTO;
v->ullman = 1;
xfunc->dcl = list(xfunc->dcl, v);
body = list(body, nod(OAS, v, cv));
} else {
// Declare variable holding addresses taken from closure
// and initialize in entry prologue.
snprint(namebuf, sizeof namebuf, "&%s", v->sym->name);
addr = newname(lookup(namebuf));
addr->ntype = nod(OIND, typenod(v->type), N);
addr->class = PAUTO;
addr->used = 1;
addr->curfn = xfunc;
xfunc->dcl = list(xfunc->dcl, addr);
v->heapaddr = addr;
if(v->byval)
cv = nod(OADDR, cv, N);
body = list(body, nod(OAS, addr, cv));
}
}
typechecklist(body, Etop);
walkstmtlist(body);
xfunc->enter = body;
xfunc->needctxt = nvar > 0;
}
lineno = lno;
}
Node*
walkclosure(Node *func, NodeList **init)
{
Node *clos, *typ, *typ1, *v;
NodeList *l;
// If no closure vars, don't bother wrapping.
if(func->cvars == nil)
return func->closure->nname;
// Create closure in the form of a composite literal.
// supposing the closure captures an int i and a string s
// and has one float64 argument and no results,
// the generated code looks like:
//
// clos = &struct{.F uintptr; i *int; s *string}{func.1, &i, &s}
//
// The use of the struct provides type information to the garbage
// collector so that it can walk the closure. We could use (in this case)
// [3]unsafe.Pointer instead, but that would leave the gc in the dark.
// The information appears in the binary in the form of type descriptors;
// the struct is unnamed so that closures in multiple packages with the
// same struct type can share the descriptor.
typ = nod(OTSTRUCT, N, N);
typ->list = list1(nod(ODCLFIELD, newname(lookup(".F")), typenod(types[TUINTPTR])));
for(l=func->cvars; l; l=l->next) {
v = l->n;
if(v->op == OXXX)
continue;
typ1 = typenod(v->type);
if(!v->byval)
typ1 = nod(OIND, typ1, N);
typ->list = list(typ->list, nod(ODCLFIELD, newname(v->sym), typ1));
}
clos = nod(OCOMPLIT, N, nod(OIND, typ, N));
clos->esc = func->esc;
clos->right->implicit = 1;
clos->list = concat(list1(nod(OCFUNC, func->closure->nname, N)), func->enter);
// Force type conversion from *struct to the func type.
clos = nod(OCONVNOP, clos, N);
clos->type = func->type;
typecheck(&clos, Erv);
// typecheck will insert a PTRLIT node under CONVNOP,
// tag it with escape analysis result.
clos->left->esc = func->esc;
// non-escaping temp to use, if any.
// orderexpr did not compute the type; fill it in now.
if(func->alloc != N) {
func->alloc->type = clos->left->left->type;
func->alloc->orig->type = func->alloc->type;
clos->left->right = func->alloc;
func->alloc = N;
}
walkexpr(&clos, init);
return clos;
}
static Node *makepartialcall(Node*, Type*, Node*);
void
typecheckpartialcall(Node *fn, Node *sym)
{
switch(fn->op) {
case ODOTINTER:
case ODOTMETH:
break;
default:
fatal("invalid typecheckpartialcall");
}
// Create top-level function.
fn->nname = makepartialcall(fn, fn->type, sym);
fn->right = sym;
fn->op = OCALLPART;
fn->type = fn->nname->type;
}
static Node*
makepartialcall(Node *fn, Type *t0, Node *meth)
{
Node *ptr, *n, *fld, *call, *xtype, *xfunc, *cv, *savecurfn;
Type *rcvrtype, *basetype, *t;
NodeList *body, *l, *callargs, *retargs;
char *p;
Sym *sym;
Pkg *spkg;
static Pkg* gopkg;
int i, ddd;
rcvrtype = fn->left->type;
if(exportname(meth->sym->name))
p = smprint("(%-hT).%s-fm", rcvrtype, meth->sym->name);
else
p = smprint("(%-hT).(%-S)-fm", rcvrtype, meth->sym);
basetype = rcvrtype;
if(isptr[rcvrtype->etype])
basetype = basetype->type;
if(basetype->etype != TINTER && basetype->sym == S)
fatal("missing base type for %T", rcvrtype);
spkg = nil;
if(basetype->sym != S)
spkg = basetype->sym->pkg;
if(spkg == nil) {
if(gopkg == nil)
gopkg = mkpkg(newstrlit("go"));
spkg = gopkg;
}
sym = pkglookup(p, spkg);
free(p);
if(sym->flags & SymUniq)
return sym->def;
sym->flags |= SymUniq;
savecurfn = curfn;
curfn = N;
xtype = nod(OTFUNC, N, N);
i = 0;
l = nil;
callargs = nil;
ddd = 0;
xfunc = nod(ODCLFUNC, N, N);
curfn = xfunc;
for(t = getinargx(t0)->type; t; t = t->down) {
snprint(namebuf, sizeof namebuf, "a%d", i++);
n = newname(lookup(namebuf));
n->class = PPARAM;
xfunc->dcl = list(xfunc->dcl, n);
callargs = list(callargs, n);
fld = nod(ODCLFIELD, n, typenod(t->type));
if(t->isddd) {
fld->isddd = 1;
ddd = 1;
}
l = list(l, fld);
}
xtype->list = l;
i = 0;
l = nil;
retargs = nil;
for(t = getoutargx(t0)->type; t; t = t->down) {
snprint(namebuf, sizeof namebuf, "r%d", i++);
n = newname(lookup(namebuf));
n->class = PPARAMOUT;
xfunc->dcl = list(xfunc->dcl, n);
retargs = list(retargs, n);
l = list(l, nod(ODCLFIELD, n, typenod(t->type)));
}
xtype->rlist = l;
xfunc->dupok = 1;
xfunc->nname = newname(sym);
xfunc->nname->sym->flags |= SymExported; // disable export
xfunc->nname->ntype = xtype;
xfunc->nname->defn = xfunc;
declare(xfunc->nname, PFUNC);
// Declare and initialize variable holding receiver.
body = nil;
xfunc->needctxt = 1;
cv = nod(OCLOSUREVAR, N, N);
cv->xoffset = widthptr;
cv->type = rcvrtype;
if(cv->type->align > widthptr)
cv->xoffset = cv->type->align;
ptr = nod(ONAME, N, N);
ptr->sym = lookup("rcvr");
ptr->class = PAUTO;
ptr->addable = 1;
ptr->ullman = 1;
ptr->used = 1;
ptr->curfn = xfunc;
xfunc->dcl = list(xfunc->dcl, ptr);
if(isptr[rcvrtype->etype] || isinter(rcvrtype)) {
ptr->ntype = typenod(rcvrtype);
body = list(body, nod(OAS, ptr, cv));
} else {
ptr->ntype = typenod(ptrto(rcvrtype));
body = list(body, nod(OAS, ptr, nod(OADDR, cv, N)));
}
call = nod(OCALL, nod(OXDOT, ptr, meth), N);
call->list = callargs;
call->isddd = ddd;
if(t0->outtuple == 0) {
body = list(body, call);
} else {
n = nod(OAS2, N, N);
n->list = retargs;
n->rlist = list1(call);
body = list(body, n);
n = nod(ORETURN, N, N);
body = list(body, n);
}
xfunc->nbody = body;
typecheck(&xfunc, Etop);
sym->def = xfunc;
xtop = list(xtop, xfunc);
curfn = savecurfn;
return xfunc;
}
Node*
walkpartialcall(Node *n, NodeList **init)
{
Node *clos, *typ;
// Create closure in the form of a composite literal.
// For x.M with receiver (x) type T, the generated code looks like:
//
// clos = &struct{F uintptr; R T}{M.T·f, x}
//
// Like walkclosure above.
if(isinter(n->left->type)) {
// Trigger panic for method on nil interface now.
// Otherwise it happens in the wrapper and is confusing.
n->left = cheapexpr(n->left, init);
checknil(n->left, init);
}
typ = nod(OTSTRUCT, N, N);
typ->list = list1(nod(ODCLFIELD, newname(lookup("F")), typenod(types[TUINTPTR])));
typ->list = list(typ->list, nod(ODCLFIELD, newname(lookup("R")), typenod(n->left->type)));
clos = nod(OCOMPLIT, N, nod(OIND, typ, N));
clos->esc = n->esc;
clos->right->implicit = 1;
clos->list = list1(nod(OCFUNC, n->nname->nname, N));
clos->list = list(clos->list, n->left);
// Force type conversion from *struct to the func type.
clos = nod(OCONVNOP, clos, N);
clos->type = n->type;
typecheck(&clos, Erv);
// typecheck will insert a PTRLIT node under CONVNOP,
// tag it with escape analysis result.
clos->left->esc = n->esc;
// non-escaping temp to use, if any.
// orderexpr did not compute the type; fill it in now.
if(n->alloc != N) {
n->alloc->type = clos->left->left->type;
n->alloc->orig->type = n->alloc->type;
clos->left->right = n->alloc;
n->alloc = N;
}
walkexpr(&clos, init);
return clos;
}