cmd/gc: shorten more temporary lifetimes

1. In functions with heap-allocated result variables or with
defer statements, the return sequence requires more than
just a single RET instruction. There is an optimization that
arranges for all returns to jump to a single copy of the return
epilogue in this case. Unfortunately, that optimization is
fundamentally incompatible with PC-based liveness information:
it takes PCs at many different points in the function and makes
them all land at one PC, making the combined liveness information
at that target PC a mess. Disable this optimization, so that each
return site gets its own copy of the 'call deferreturn' and the
copying of result variables back from the heap.
This removes quite a few spurious 'ambiguously live' variables.

2. Let orderexpr allocate temporaries that are passed by address
to a function call and then die on return, so that we can arrange
an appropriate VARKILL.

2a. Do this for ... slices.

2b. Do this for closure structs.

2c. Do this for runtime.concatstring, which is the implementation
of large string additions. Change representation of OADDSTR to
an explicit list in typecheck to avoid reconstructing list in both
walk and order.

3. Let orderexpr allocate the temporary variable copies used for
range loops, so that they can be killed when the loop is over.
Similarly, let it allocate the temporary holding the map iterator.

CL 81940043 reduced the number of ambiguously live temps
in the godoc binary from 860 to 711.

This CL reduces the number to 121. Still more to do, but another
good checkpoint.

Update #7345

LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/83090046
This commit is contained in:
Russ Cox 2014-04-01 20:02:54 -04:00
parent 2f3776ac27
commit daca06f2e3
15 changed files with 368 additions and 101 deletions

View file

@ -256,7 +256,9 @@ ginscall(Node *f, int proc)
nodconst(&con, types[TINT32], 0);
p = gins(ACMP, &con, N);
p->reg = 0;
patch(gbranch(ABNE, T, -1), retpc);
p = gbranch(ABEQ, T, +1);
cgen_ret(N);
patch(p, pc);
}
break;
}

View file

@ -232,7 +232,9 @@ ginscall(Node *f, int proc)
if(proc == 2) {
nodreg(&reg, types[TINT64], D_AX);
gins(ATESTQ, &reg, &reg);
patch(gbranch(AJNE, T, -1), retpc);
p = gbranch(AJEQ, T, +1);
cgen_ret(N);
patch(p, pc);
}
break;
}
@ -432,13 +434,13 @@ cgen_ret(Node *n)
{
Prog *p;
genlist(n->list); // copy out args
if(hasdefer || curfn->exit) {
gjmp(retpc);
return;
}
if(n != N)
genlist(n->list); // copy out args
if(hasdefer)
ginscall(deferreturn, 0);
genlist(curfn->exit);
p = gins(ARET, N, N);
if(n->op == ORETJMP) {
if(n != N && n->op == ORETJMP) {
p->to.type = D_EXTERN;
p->to.sym = linksym(n->left->sym);
}

View file

@ -259,7 +259,9 @@ ginscall(Node *f, int proc)
if(proc == 2) {
nodreg(&reg, types[TINT64], D_AX);
gins(ATESTL, &reg, &reg);
patch(gbranch(AJNE, T, -1), retpc);
p = gbranch(AJEQ, T, +1);
cgen_ret(N);
patch(p, pc);
}
break;
}

View file

@ -253,6 +253,14 @@ walkclosure(Node *func, NodeList **init)
// 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->left != N) {
func->left->type = clos->left->left->type;
func->left->orig->type = func->left->type;
clos->left->right = func->left;
func->left = N;
}
walkexpr(&clos, init);
return clos;

View file

@ -521,6 +521,7 @@ evconst(Node *n)
int wl, wr, lno, et;
Val v, rv;
Mpint b;
NodeList *l1, *l2;
// pick off just the opcodes that can be
// constant evaluated.
@ -528,7 +529,6 @@ evconst(Node *n)
default:
return;
case OADD:
case OADDSTR:
case OAND:
case OANDAND:
case OANDNOT:
@ -559,6 +559,47 @@ evconst(Node *n)
if(!okforconst[n->type->etype] && n->type->etype != TNIL)
return;
break;
case OADDSTR:
// merge adjacent constants in the argument list.
for(l1=n->list; l1 != nil; l1= l1->next) {
if(isconst(l1->n, CTSTR) && l1->next != nil && isconst(l1->next->n, CTSTR)) {
l2 = l1;
len = 0;
while(l2 != nil && isconst(l2->n, CTSTR)) {
nr = l2->n;
len += nr->val.u.sval->len;
l2 = l2->next;
}
// merge from l1 up to but not including l2
str = mal(sizeof(*str) + len);
str->len = len;
len = 0;
l2 = l1;
while(l2 != nil && isconst(l2->n, CTSTR)) {
nr = l2->n;
memmove(str->s+len, nr->val.u.sval->s, nr->val.u.sval->len);
len += nr->val.u.sval->len;
l2 = l2->next;
}
nl = nod(OXXX, N, N);
*nl = *l1->n;
nl->orig = nl;
nl->val.ctype = CTSTR;
nl->val.u.sval = str;
l1->n = nl;
l1->next = l2;
}
}
// fix list end pointer.
for(l2=n->list; l2 != nil; l2=l2->next)
n->list->end = l2;
// collapse single-constant list to single constant.
if(count(n->list) == 1 && isconst(n->list->n, CTSTR)) {
n->op = OLITERAL;
n->val = n->list->n->val;
}
return;
}
nl = n->left;
@ -861,15 +902,6 @@ evconst(Node *n)
if(cmpslit(nl, nr) > 0)
goto settrue;
goto setfalse;
case TUP(OADDSTR, CTSTR):
len = v.u.sval->len + rv.u.sval->len;
str = mal(sizeof(*str) + len);
str->len = len;
memcpy(str->s, v.u.sval->s, v.u.sval->len);
memcpy(str->s+v.u.sval->len, rv.u.sval->s, rv.u.sval->len);
str->len = len;
v.u.sval = str;
break;
case TUP(OOROR, CTBOOL):
if(v.u.bval || rv.u.bval)

View file

@ -811,24 +811,29 @@ escassign(EscState *e, Node *dst, Node *src)
lineno = lno;
}
static void
static int
escassignfromtag(EscState *e, Strlit *note, NodeList *dsts, Node *src)
{
int em;
int em, em0;
em = parsetag(note);
if(em == EscUnknown) {
escassign(e, &e->theSink, src);
return;
return em;
}
if(em == EscNone)
return em;
em0 = em;
for(em >>= EscBits; em && dsts; em >>= 1, dsts=dsts->next)
if(em & 1)
escassign(e, dsts->n, src);
if (em != 0 && dsts == nil)
fatal("corrupt esc tag %Z or messed up escretval list\n", note);
return em0;
}
// This is a bit messier than fortunate, pulled out of esc's big
@ -875,7 +880,7 @@ esccall(EscState *e, Node *n)
if(a->type->etype == TSTRUCT && a->type->funarg) // f(g()).
ll = a->escretval;
}
if(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody && fn->ntype && fn->defn->esc < EscFuncTagged) {
// function in same mutually recursive group. Incorporate into flow graph.
// print("esc local fn: %N\n", fn->ntype);
@ -895,6 +900,9 @@ esccall(EscState *e, Node *n)
if(lr->n->isddd && !n->isddd) {
// Introduce ODDDARG node to represent ... allocation.
src = nod(ODDDARG, N, N);
src->type = typ(TARRAY);
src->type->type = lr->n->type->type;
src->type->bound = count(ll);
src->escloopdepth = e->loopdepth;
src->lineno = n->lineno;
src->esc = EscNone; // until we find otherwise
@ -949,12 +957,32 @@ esccall(EscState *e, Node *n)
src = nod(ODDDARG, N, N);
src->escloopdepth = e->loopdepth;
src->lineno = n->lineno;
src->type = typ(TARRAY);
src->type->type = t->type->type;
src->type->bound = count(ll);
src->esc = EscNone; // until we find otherwise
e->noesc = list(e->noesc, src);
n->right = src;
}
if(haspointers(t->type))
escassignfromtag(e, t->note, n->escretval, src);
if(haspointers(t->type)) {
if(escassignfromtag(e, t->note, n->escretval, src) == EscNone) {
switch(src->op) {
case OCLOSURE:
case ODDDARG:
// The callee has already been analyzed, so its arguments have esc tags.
// The argument is marked as not escaping at all.
// Record that fact so that any temporary used for
// synthesizing this expression can be reclaimed when
// the function returns.
// This 'noescape' is even stronger than the usual esc == EscNone.
// src->esc == EscNone means that src does not escape the current function.
// src->noescape = 1 here means that src does not escape this statement
// in the current function.
src->noescape = 1;
break;
}
}
}
if(src != ll->n)
break;
t = t->down;

View file

@ -1339,7 +1339,6 @@ exprfmt(Fmt *f, Node *n, int prec)
// Binary
case OADD:
case OADDSTR:
case OAND:
case OANDAND:
case OANDNOT:
@ -1364,6 +1363,14 @@ exprfmt(Fmt *f, Node *n, int prec)
exprfmt(f, n->right, nprec+1);
return 0;
case OADDSTR:
for(l=n->list; l; l=l->next) {
if(l != n->list)
fmtprint(f, " + ");
exprfmt(f, l->n, nprec);
}
return 0;
case OCMPSTR:
case OCMPIFACE:
exprfmt(f, n->left, nprec);

View file

@ -1470,7 +1470,6 @@ EXTERN Prog* continpc;
EXTERN Prog* breakpc;
EXTERN Prog* pc;
EXTERN Prog* firstpc;
EXTERN Prog* retpc;
EXTERN Node* nodfp;
EXTERN int disable_checknil;

View file

@ -417,10 +417,12 @@ ordercallargs(NodeList **l, Order *order)
// Ordercall orders the call expression n.
// n->op is OCALLMETH/OCALLFUNC/OCALLINTER.
static void
ordercall(Node *n, Order *order)
ordercall(Node *n, Order *order, int special)
{
orderexpr(&n->left, order);
ordercallargs(&n->list, order);
if(!special)
orderexpr(&n->right, order); // ODDDARG temp
}
// Ordermapassign appends n to order->out, introducing temporaries
@ -547,7 +549,6 @@ orderstmt(Node *n, Order *order)
// a map index expression.
t = marktemp(order);
orderexpr(&n->left, order);
orderexpr(&n->right, order);
n->left = ordersafeexpr(n->left, order);
tmp1 = treecopy(n->left);
if(tmp1->op == OINDEXMAP)
@ -555,6 +556,7 @@ orderstmt(Node *n, Order *order)
tmp1 = ordercopyexpr(tmp1, n->left->type, order, 0);
n->right = nod(n->etype, tmp1, n->right);
typecheck(&n->right, Erv);
orderexpr(&n->right, order);
n->etype = 0;
n->op = OAS;
ordermapassign(n, order);
@ -577,7 +579,7 @@ orderstmt(Node *n, Order *order)
// Special: avoid copy of func call n->rlist->n.
t = marktemp(order);
orderexprlist(n->list, order);
ordercall(n->rlist->n, order);
ordercall(n->rlist->n, order, 0);
ordermapassign(n, order);
cleantemp(t, order);
break;
@ -628,7 +630,7 @@ orderstmt(Node *n, Order *order)
case OCALLMETH:
// Special: handle call arguments.
t = marktemp(order);
ordercall(n, order);
ordercall(n, order, 0);
order->out = list(order->out, n);
cleantemp(t, order);
break;
@ -649,7 +651,7 @@ orderstmt(Node *n, Order *order)
poptemp(t1, order);
break;
default:
ordercall(n->left, order);
ordercall(n->left, order, 1);
break;
}
order->out = list(order->out, n);
@ -682,12 +684,53 @@ orderstmt(Node *n, Order *order)
break;
case ORANGE:
// TODO(rsc): Clean temporaries.
// n->right is the expression being ranged over.
// order it, and then make a copy if we need one.
// We almost always do, to ensure that we don't
// see any value changes made during the loop.
// Usually the copy is cheap (e.g., array pointer, chan, slice, string are all tiny).
// The exception is ranging over an array value (not a slice, not a pointer to array),
// which must make a copy to avoid seeing updates made during
// the range body. Ranging over an array value is uncommon though.
t = marktemp(order);
orderexpr(&n->right, order);
switch(n->type->etype) {
default:
fatal("orderstmt range %T", n->type);
case TARRAY:
if(count(n->list) < 2 || isblank(n->list->next->n)) {
// for i := range x will only use x once, to compute len(x).
// No need to copy it.
break;
}
// fall through
case TCHAN:
case TSTRING:
// chan, string, slice, array ranges use value multiple times.
// make copy.
r = n->right;
if(r->type->etype == TSTRING && r->type != types[TSTRING]) {
r = nod(OCONV, r, N);
r->type = types[TSTRING];
typecheck(&r, Erv);
}
n->right = ordercopyexpr(r, r->type, order, 0);
break;
case TMAP:
// copy the map value in case it is a map literal.
// TODO(rsc): Make tmp = literal expressions reuse tmp.
// For maps tmp is just one word so it hardly matters.
r = n->right;
n->right = ordercopyexpr(r, r->type, order, 0);
// temp is the iterator instead of the map value.
n->left = ordertemp(hiter(n->right->type), order, 1);
break;
}
for(l=n->list; l; l=l->next)
orderexprinplace(&l->n, order);
orderblock(&n->nbody);
order->out = list(order->out, n);
cleantemp(t, order);
break;
case ORETURN:
@ -769,6 +812,7 @@ static void
orderexpr(Node **np, Order *order)
{
Node *n;
Type *t;
int lno;
n = *np;
@ -786,6 +830,19 @@ orderexpr(Node **np, Order *order)
orderexprlist(n->rlist, order);
break;
case OADDSTR:
// Addition of strings turns into a function call.
// Allocate a temporary to hold the strings.
// Fewer than 5 strings use direct runtime helpers.
orderexprlist(n->list, order);
if(count(n->list) > 5) {
t = typ(TARRAY);
t->bound = count(n->list);
t->type = types[TSTRING];
n->left = ordertemp(t, order, 0);
}
break;
case OINDEXMAP:
// key must be addressable
orderexpr(&n->left, order);
@ -809,10 +866,29 @@ orderexpr(Node **np, Order *order)
case OCALLINTER:
case OAPPEND:
case OCOMPLEX:
ordercall(n, order);
ordercall(n, order, 0);
n = ordercopyexpr(n, n->type, order, 0);
break;
case OCLOSURE:
if(n->noescape && n->cvars != nil) {
t = typ(TARRAY);
t->type = types[TUNSAFEPTR];
t->bound = 1+count(n->cvars);
n->left = ordertemp(t, order, 0);
}
break;
case ODDDARG:
if(n->noescape) {
// The ddd argument does not live beyond the call it is created for.
// Allocate a temporary that will be cleaned up when this statement
// completes. We could be more aggressive and try to arrange for it
// to be cleaned up when the call completes.
n->left = ordertemp(n->type, order, 0);
}
break;
case ORECV:
n = ordercopyexpr(n, n->type, order, 1);
break;

View file

@ -135,7 +135,7 @@ compile(Node *fn)
{
Plist *pl;
Node nod1, *n;
Prog *ptxt, *p, *p1;
Prog *ptxt, *p;
int32 lno;
Type *t;
Iter save;
@ -256,14 +256,6 @@ compile(Node *fn)
}
genlist(curfn->enter);
retpc = nil;
if(hasdefer || curfn->exit) {
p1 = gjmp(nil);
retpc = gjmp(nil);
patch(p1, pc);
}
genlist(curfn->nbody);
gclean();
checklabels();
@ -275,18 +267,15 @@ compile(Node *fn)
if(curfn->type->outtuple != 0)
ginscall(throwreturn, 0);
if(retpc)
patch(retpc, pc);
ginit();
// TODO: Determine when the final cgen_ret can be omitted. Perhaps always?
cgen_ret(nil);
if(hasdefer) {
ginscall(deferreturn, 0);
// deferreturn pretends to have one uintptr argument.
// Reserve space for it so stack scanner is happy.
if(maxarg < widthptr)
maxarg = widthptr;
}
if(curfn->exit)
genlist(curfn->exit);
gclean();
if(nerrors != 0)
goto ret;

View file

@ -122,34 +122,23 @@ walkrange(Node *n)
a = n->right;
lno = setlineno(a);
if(t->etype == TSTRING && !eqtype(t, types[TSTRING])) {
a = nod(OCONV, n->right, N);
a->type = types[TSTRING];
}
v1 = n->list->n;
v2 = N;
if(n->list->next)
if(n->list->next && !isblank(n->list->next->n))
v2 = n->list->next->n;
// n->list has no meaning anymore, clear it
// to avoid erroneous processing by racewalk.
n->list = nil;
hv2 = N;
if(v2 == N && t->etype == TARRAY) {
// will have just one reference to argument.
// no need to make a potentially expensive copy.
ha = a;
} else {
ha = temp(a->type);
init = list(init, nod(OAS, ha, a));
}
switch(t->etype) {
default:
fatal("walkrange");
case TARRAY:
// orderstmt arranged for a copy of the array/slice variable if needed.
ha = a;
hv1 = temp(types[TINT]);
hn = temp(types[TINT]);
hp = nil;
@ -193,10 +182,12 @@ walkrange(Node *n)
break;
case TMAP:
// allocate an iterator state structure on the stack
// orderstmt allocated the iterator for us.
// we only use a once, so no copy needed.
ha = a;
th = hiter(t);
hit = temp(th);
init = list(init, nod(OAS, hit, N));
hit = n->left;
n->left = N;
keyname = newname(th->type->sym); // depends on layout of iterator struct. See reflect.c:hiter
valname = newname(th->type->down->sym); // ditto
@ -226,6 +217,10 @@ walkrange(Node *n)
break;
case TCHAN:
// orderstmt arranged for a copy of the channel variable.
ha = a;
n->ntest = N;
hv1 = temp(t->type);
if(haspointers(t->type))
init = list(init, nod(OAS, hv1, N));
@ -241,6 +236,9 @@ walkrange(Node *n)
break;
case TSTRING:
// orderstmt arranged for a copy of the string variable.
ha = a;
ohv1 = temp(types[TINT]);
hv1 = temp(types[TINT]);

View file

@ -767,11 +767,23 @@ slicelit(int ctxt, Node *n, Node *var, NodeList **init)
vauto = temp(ptrto(t));
// set auto to point at new temp or heap (3 assign)
if(n->esc == EscNone) {
a = nod(OAS, temp(t), N);
typecheck(&a, Etop);
*init = list(*init, a); // zero new temp
a = nod(OADDR, a->left, N);
if(n->left != N) {
// temp allocated during order.c for dddarg
if(vstat == N) {
a = nod(OAS, n->left, N);
typecheck(&a, Etop);
*init = list(*init, a); // zero new temp
}
a = nod(OADDR, n->left, N);
} else if(n->esc == EscNone) {
a = temp(t);
if(vstat == N) {
a = nod(OAS, temp(t), N);
typecheck(&a, Etop);
*init = list(*init, a); // zero new temp
a = a->left;
}
a = nod(OADDR, a, N);
} else {
a = nod(ONEW, N, N);
a->list = list1(typenod(t));
@ -1022,12 +1034,16 @@ anylit(int ctxt, Node *n, Node *var, NodeList **init)
if(!isptr[t->etype])
fatal("anylit: not ptr");
r = nod(ONEW, N, N);
r->typecheck = 1;
r->type = t;
r->esc = n->esc;
if(n->right != N) {
r = nod(OADDR, n->right, N);
typecheck(&r, Erv);
} else {
r = nod(ONEW, N, N);
r->typecheck = 1;
r->type = t;
r->esc = n->esc;
}
walkexpr(&r, init);
a = nod(OAS, var, r);
typecheck(&a, Etop);

View file

@ -654,8 +654,20 @@ reswitch:
if(iscmp[n->op]) {
n->etype = n->op;
n->op = OCMPSTR;
} else if(n->op == OADD)
} else if(n->op == OADD) {
// create OADDSTR node with list of strings in x + y + z + (w + v) + ...
n->op = OADDSTR;
if(l->op == OADDSTR)
n->list = l->list;
else
n->list = list1(l);
if(r->op == OADDSTR)
n->list = concat(n->list, r->list);
else
n->list = list(n->list, r);
n->left = N;
n->right = N;
}
}
if(et == TINTER) {
if(l->op == OLITERAL && l->val.ctype == CTNIL) {

View file

@ -1190,9 +1190,10 @@ walkexpr(Node **np, NodeList **init)
// s + "badgerbadgerbadger" == "badgerbadgerbadger"
if((n->etype == OEQ || n->etype == ONE) &&
isconst(n->right, CTSTR) &&
n->left->op == OADDSTR && isconst(n->left->right, CTSTR) &&
cmpslit(n->right, n->left->right) == 0) {
r = nod(n->etype, nod(OLEN, n->left->left, N), nodintconst(0));
n->left->op == OADDSTR && count(n->left->list) == 2 &&
isconst(n->left->list->next->n, CTSTR) &&
cmpslit(n->right, n->left->list->next->n) == 0) {
r = nod(n->etype, nod(OLEN, n->left->list->n, N), nodintconst(0));
typecheck(&r, Erv);
walkexpr(&r, init);
r->type = n->type;
@ -1535,11 +1536,16 @@ ascompatet(int op, NodeList *nl, Type **nr, int fp, NodeList **init)
* package all the arguments that match a ... T parameter into a []T.
*/
static NodeList*
mkdotargslice(NodeList *lr0, NodeList *nn, Type *l, int fp, NodeList **init, int esc)
mkdotargslice(NodeList *lr0, NodeList *nn, Type *l, int fp, NodeList **init, Node *ddd)
{
Node *a, *n;
Type *tslice;
int esc;
esc = EscUnknown;
if(ddd != nil)
esc = ddd->esc;
tslice = typ(TARRAY);
tslice->type = l->type->type;
tslice->bound = -1;
@ -1549,6 +1555,8 @@ mkdotargslice(NodeList *lr0, NodeList *nn, Type *l, int fp, NodeList **init, int
n->type = tslice;
} else {
n = nod(OCOMPLIT, N, typenod(tslice));
if(ddd != nil)
n->left = ddd->left; // temporary to use
n->list = lr0;
n->esc = esc;
typecheck(&n, Erv);
@ -1620,7 +1628,6 @@ dumpnodetypes(NodeList *l, char *what)
static NodeList*
ascompatte(int op, Node *call, int isddd, Type **nl, NodeList *lr, int fp, NodeList **init)
{
int esc;
Type *l, *ll;
Node *r, *a;
NodeList *nn, *lr0, *alist;
@ -1683,10 +1690,7 @@ loop:
// normal case -- make a slice of all
// remaining arguments and pass it to
// the ddd parameter.
esc = EscUnknown;
if(call->right)
esc = call->right->esc;
nn = mkdotargslice(lr, nn, l, fp, init, esc);
nn = mkdotargslice(lr, nn, l, fp, init, call->right);
goto ret;
}
@ -2504,26 +2508,24 @@ static Node*
addstr(Node *n, NodeList **init)
{
Node *r, *cat, *slice;
NodeList *args;
int count;
NodeList *args, *l;
int c;
Type *t;
count = 0;
for(r=n; r->op == OADDSTR; r=r->left)
count++; // r->right
count++; // r
if(count < 2)
yyerror("addstr count %d too small", count);
// orderexpr rewrote OADDSTR to have a list of strings.
c = count(n->list);
if(c < 2)
yyerror("addstr count %d too small", c);
// build list of string arguments
args = nil;
for(r=n; r->op == OADDSTR; r=r->left)
args = concat(list1(conv(r->right, types[TSTRING])), args);
args = concat(list1(conv(r, types[TSTRING])), args);
for(l=n->list; l != nil; l=l->next)
args = list(args, conv(l->n, types[TSTRING]));
if(count <= 5) {
if(c <= 5) {
// small numbers of strings use direct runtime helpers.
snprint(namebuf, sizeof(namebuf), "concatstring%d", count);
// note: orderexpr knows this cutoff too.
snprint(namebuf, sizeof(namebuf), "concatstring%d", c);
} else {
// large numbers of strings are passed to the runtime as a slice.
strcpy(namebuf, "concatstrings");
@ -2531,6 +2533,7 @@ addstr(Node *n, NodeList **init)
t->type = types[TSTRING];
t->bound = -1;
slice = nod(OCOMPLIT, N, typenod(t));
slice->left = n->left;
slice->list = args;
slice->esc = EscNone;
args = list1(slice);

View file

@ -330,3 +330,96 @@ func f24() {
m2[[2]string{"x", "y"}] = nil // ERROR "live at call to mapassign1: autotmp_[0-9]+ autotmp_[0-9]+$"
m2[[2]string{"x", "y"}] = nil // ERROR "live at call to mapassign1: autotmp_[0-9]+ autotmp_[0-9]+$"
}
// defer should not cause spurious ambiguously live variables
func f25(b bool) {
defer g25()
if b {
return
}
var x string
_ = &x
x = g15() // ERROR "live at call to g15: x"
print(x) // ERROR "live at call to printstring: x"
} // ERROR "live at call to deferreturn: x"
func g25()
// non-escaping ... slices passed to function call should die on return,
// so that the temporaries do not stack and do not cause ambiguously
// live variables.
func f26(b bool) {
if b {
print26(1,2,3) // ERROR "live at call to print26: autotmp_[0-9]+$"
}
print26(4,5,6) // ERROR "live at call to print26: autotmp_[0-9]+$"
print26(7,8,9) // ERROR "live at call to print26: autotmp_[0-9]+$"
println()
}
//go:noescape
func print26(...interface{})
// non-escaping closures passed to function call should die on return
func f27(b bool) {
x := 0
if b {
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
}
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
}
//go:noescape
func call27(func())
// concatstring slice should die on return
var s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 string
func f28(b bool) {
if b {
print(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10) // ERROR "live at call to concatstrings: autotmp_[0-9]+$" "live at call to printstring: autotmp_[0-9]+$"
}
print(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10) // ERROR "live at call to concatstrings: autotmp_[0-9]+$" "live at call to printstring: autotmp_[0-9]+$"
print(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10) // ERROR "live at call to concatstrings: autotmp_[0-9]+$" "live at call to printstring: autotmp_[0-9]+$"
}
// map iterator should die on end of range loop
func f29(b bool) {
if b {
for k := range m { // ERROR "live at call to mapiterinit: autotmp_[0-9]+$" "live at call to mapiternext: autotmp_[0-9]+$"
print(k) // ERROR "live at call to printstring: autotmp_[0-9]+$"
}
}
for k := range m { // ERROR "live at call to mapiterinit: autotmp_[0-9]+$" "live at call to mapiternext: autotmp_[0-9]+$"
print(k) // ERROR "live at call to printstring: autotmp_[0-9]+$"
}
for k := range m { // ERROR "live at call to mapiterinit: autotmp_[0-9]+$" "live at call to mapiternext: autotmp_[0-9]+$"
print(k) // ERROR "live at call to printstring: autotmp_[0-9]+$"
}
}
// copy of array of pointers should die at end of range loop
var ptrarr [10]*int
func f30(b bool) {
// two live temps during print(p):
// the copy of ptrarr and the internal iterator pointer.
if b {
for _, p := range ptrarr {
print(p) // ERROR "live at call to printpointer: autotmp_[0-9]+ autotmp_[0-9]+$"
}
}
for _, p := range ptrarr {
print(p) // ERROR "live at call to printpointer: autotmp_[0-9]+ autotmp_[0-9]+$"
}
for _, p := range ptrarr {
print(p) // ERROR "live at call to printpointer: autotmp_[0-9]+ autotmp_[0-9]+$"
}
}