blob: 537d0ca928e5b00e41271453b8d38e64e6a5c4dc [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.
/*
* select
*/
#include <u.h>
#include <libc.h>
#include "go.h"
static Type* selecttype(int32 size);
void
typecheckselect(Node *sel)
{
Node *ncase, *n, *def;
NodeList *l;
int lno, count;
def = nil;
lno = setlineno(sel);
count = 0;
typechecklist(sel->ninit, Etop);
for(l=sel->list; l; l=l->next) {
count++;
ncase = l->n;
setlineno(ncase);
if(ncase->op != OXCASE)
fatal("typecheckselect %O", ncase->op);
if(ncase->list == nil) {
// default
if(def != N)
yyerror("multiple defaults in select (first at %L)", def->lineno);
else
def = ncase;
} else if(ncase->list->next) {
yyerror("select cases cannot be lists");
} else {
n = typecheck(&ncase->list->n, Etop);
ncase->left = n;
ncase->list = nil;
setlineno(n);
switch(n->op) {
default:
yyerror("select case must be receive, send or assign recv");
break;
case OAS:
// convert x = <-c into OSELRECV(x, <-c).
// remove implicit conversions; the eventual assignment
// will reintroduce them.
if((n->right->op == OCONVNOP || n->right->op == OCONVIFACE) && n->right->implicit)
n->right = n->right->left;
if(n->right->op != ORECV) {
yyerror("select assignment must have receive on right hand side");
break;
}
n->op = OSELRECV;
break;
case OAS2RECV:
// convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok
if(n->rlist->n->op != ORECV) {
yyerror("select assignment must have receive on right hand side");
break;
}
n->op = OSELRECV2;
n->left = n->list->n;
n->ntest = n->list->next->n;
n->list = nil;
n->right = n->rlist->n;
n->rlist = nil;
break;
case ORECV:
// convert <-c into OSELRECV(N, <-c)
n = nod(OSELRECV, N, n);
n->typecheck = 1;
ncase->left = n;
break;
case OSEND:
break;
}
}
typechecklist(ncase->nbody, Etop);
}
sel->xoffset = count;
lineno = lno;
}
void
walkselect(Node *sel)
{
int lno, i;
Node *n, *r, *a, *var, *selv, *cas, *dflt, *ch;
NodeList *l, *init;
if(sel->list == nil && sel->xoffset != 0)
fatal("double walkselect"); // already rewrote
lno = setlineno(sel);
i = count(sel->list);
// optimization: zero-case select
if(i == 0) {
sel->nbody = list1(mkcall("block", nil, nil));
goto out;
}
// optimization: one-case select: single op.
// TODO(rsc): Reenable optimization once order.c can handle it.
// golang.org/issue/7672.
if(i == 1) {
cas = sel->list->n;
setlineno(cas);
l = cas->ninit;
if(cas->left != N) { // not default:
n = cas->left;
l = concat(l, n->ninit);
n->ninit = nil;
switch(n->op) {
default:
fatal("select %O", n->op);
case OSEND:
// ok already
ch = n->left;
break;
case OSELRECV:
case OSELRECV2:
ch = n->right->left;
if(n->op == OSELRECV || n->ntest == N) {
if(n->left == N)
n = n->right;
else
n->op = OAS;
break;
}
if(n->left == N) {
typecheck(&nblank, Erv | Easgn);
n->left = nblank;
}
n->op = OAS2;
n->list = list(list1(n->left), n->ntest);
n->rlist = list1(n->right);
n->right = N;
n->left = N;
n->ntest = N;
n->typecheck = 0;
typecheck(&n, Etop);
break;
}
// if ch == nil { block() }; n;
a = nod(OIF, N, N);
a->ntest = nod(OEQ, ch, nodnil());
a->nbody = list1(mkcall("block", nil, &l));
typecheck(&a, Etop);
l = list(l, a);
l = list(l, n);
}
l = concat(l, cas->nbody);
sel->nbody = l;
goto out;
}
// convert case value arguments to addresses.
// this rewrite is used by both the general code and the next optimization.
for(l=sel->list; l; l=l->next) {
cas = l->n;
setlineno(cas);
n = cas->left;
if(n == N)
continue;
switch(n->op) {
case OSEND:
n->right = nod(OADDR, n->right, N);
typecheck(&n->right, Erv);
break;
case OSELRECV:
case OSELRECV2:
if(n->op == OSELRECV2 && n->ntest == N)
n->op = OSELRECV;
if(n->op == OSELRECV2) {
n->ntest = nod(OADDR, n->ntest, N);
typecheck(&n->ntest, Erv);
}
if(n->left == N)
n->left = nodnil();
else {
n->left = nod(OADDR, n->left, N);
typecheck(&n->left, Erv);
}
break;
}
}
// optimization: two-case select but one is default: single non-blocking op.
if(i == 2 && (sel->list->n->left == nil || sel->list->next->n->left == nil)) {
if(sel->list->n->left == nil) {
cas = sel->list->next->n;
dflt = sel->list->n;
} else {
dflt = sel->list->next->n;
cas = sel->list->n;
}
n = cas->left;
setlineno(n);
r = nod(OIF, N, N);
r->ninit = cas->ninit;
switch(n->op) {
default:
fatal("select %O", n->op);
case OSEND:
// if selectnbsend(c, v) { body } else { default body }
ch = n->left;
r->ntest = mkcall1(chanfn("selectnbsend", 2, ch->type),
types[TBOOL], &r->ninit, typename(ch->type), ch, n->right);
break;
case OSELRECV:
// if c != nil && selectnbrecv(&v, c) { body } else { default body }
r = nod(OIF, N, N);
r->ninit = cas->ninit;
ch = n->right->left;
r->ntest = mkcall1(chanfn("selectnbrecv", 2, ch->type),
types[TBOOL], &r->ninit, typename(ch->type), n->left, ch);
break;
case OSELRECV2:
// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
r = nod(OIF, N, N);
r->ninit = cas->ninit;
ch = n->right->left;
r->ntest = mkcall1(chanfn("selectnbrecv2", 2, ch->type),
types[TBOOL], &r->ninit, typename(ch->type), n->left, n->ntest, ch);
break;
}
typecheck(&r->ntest, Erv);
r->nbody = cas->nbody;
r->nelse = concat(dflt->ninit, dflt->nbody);
sel->nbody = list1(r);
goto out;
}
init = sel->ninit;
sel->ninit = nil;
// generate sel-struct
setlineno(sel);
selv = temp(selecttype(sel->xoffset));
r = nod(OAS, selv, N);
typecheck(&r, Etop);
init = list(init, r);
var = conv(conv(nod(OADDR, selv, N), types[TUNSAFEPTR]), ptrto(types[TUINT8]));
r = mkcall("newselect", T, nil, var, nodintconst(selv->type->width), nodintconst(sel->xoffset));
typecheck(&r, Etop);
init = list(init, r);
// register cases
for(l=sel->list; l; l=l->next) {
cas = l->n;
setlineno(cas);
n = cas->left;
r = nod(OIF, N, N);
r->ninit = cas->ninit;
cas->ninit = nil;
if(n != nil) {
r->ninit = concat(r->ninit, n->ninit);
n->ninit = nil;
}
if(n == nil) {
// selectdefault(sel *byte);
r->ntest = mkcall("selectdefault", types[TBOOL], &r->ninit, var);
} else {
switch(n->op) {
default:
fatal("select %O", n->op);
case OSEND:
// selectsend(sel *byte, hchan *chan any, elem *any) (selected bool);
r->ntest = mkcall1(chanfn("selectsend", 2, n->left->type), types[TBOOL],
&r->ninit, var, n->left, n->right);
break;
case OSELRECV:
// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
r->ntest = mkcall1(chanfn("selectrecv", 2, n->right->left->type), types[TBOOL],
&r->ninit, var, n->right->left, n->left);
break;
case OSELRECV2:
// selectrecv2(sel *byte, hchan *chan any, elem *any, received *bool) (selected bool);
r->ntest = mkcall1(chanfn("selectrecv2", 2, n->right->left->type), types[TBOOL],
&r->ninit, var, n->right->left, n->left, n->ntest);
break;
}
}
// selv is no longer alive after use.
r->nbody = list(r->nbody, nod(OVARKILL, selv, N));
r->nbody = concat(r->nbody, cas->nbody);
r->nbody = list(r->nbody, nod(OBREAK, N, N));
init = list(init, r);
}
// run the select
setlineno(sel);
init = list(init, mkcall("selectgo", T, nil, var));
sel->nbody = init;
out:
sel->list = nil;
walkstmtlist(sel->nbody);
lineno = lno;
}
// Keep in sync with src/runtime/chan.h.
static Type*
selecttype(int32 size)
{
Node *sel, *sudog, *scase, *arr;
// TODO(dvyukov): it's possible to generate SudoG and Scase only once
// and then cache; and also cache Select per size.
sudog = nod(OTSTRUCT, N, N);
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("g")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("selectdone")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("next")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("prev")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("elem")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("releasetime")), typenod(types[TUINT64])));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("nrelease")), typenod(types[TINT32])));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("waitlink")), typenod(ptrto(types[TUINT8]))));
typecheck(&sudog, Etype);
sudog->type->noalg = 1;
sudog->type->local = 1;
scase = nod(OTSTRUCT, N, N);
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("elem")), typenod(ptrto(types[TUINT8]))));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("chan")), typenod(ptrto(types[TUINT8]))));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("pc")), typenod(types[TUINTPTR])));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("kind")), typenod(types[TUINT16])));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("so")), typenod(types[TUINT16])));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("receivedp")), typenod(ptrto(types[TUINT8]))));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("releasetime")), typenod(types[TUINT64])));
typecheck(&scase, Etype);
scase->type->noalg = 1;
scase->type->local = 1;
sel = nod(OTSTRUCT, N, N);
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("tcase")), typenod(types[TUINT16])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("ncase")), typenod(types[TUINT16])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("pollorder")), typenod(ptrto(types[TUINT8]))));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("lockorder")), typenod(ptrto(types[TUINT8]))));
arr = nod(OTARRAY, nodintconst(size), scase);
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("scase")), arr));
arr = nod(OTARRAY, nodintconst(size), typenod(ptrto(types[TUINT8])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("lockorderarr")), arr));
arr = nod(OTARRAY, nodintconst(size), typenod(types[TUINT16]));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("pollorderarr")), arr));
typecheck(&sel, Etype);
sel->type->noalg = 1;
sel->type->local = 1;
return sel->type;
}