1
0
mirror of https://github.com/golang/go synced 2024-11-22 01:44:40 -07:00

gc: special case code for single-op blocking and non-blocking selects

R=ken2
CC=golang-dev
https://golang.org/cl/4004045
This commit is contained in:
Russ Cox 2011-01-30 16:07:57 -05:00
parent 7247d6b96f
commit 5038792837
7 changed files with 313 additions and 72 deletions

View File

@ -72,11 +72,14 @@ char *runtimeimport =
"func \"\".chansend2 (hchan chan<- any, elem any) bool\n"
"func \"\".closechan (hchan any)\n"
"func \"\".closedchan (hchan any) bool\n"
"func \"\".selectnbsend (hchan chan<- any, elem any) bool\n"
"func \"\".selectnbrecv (elem *any, hchan <-chan any) bool\n"
"func \"\".newselect (size int) *uint8\n"
"func \"\".selectsend (sel *uint8, hchan chan<- any, elem any) bool\n"
"func \"\".selectrecv (sel *uint8, hchan <-chan any, elem *any) bool\n"
"func \"\".selectdefault (sel *uint8) bool\n"
"func \"\".selectgo (sel *uint8)\n"
"func \"\".block ()\n"
"func \"\".makeslice (typ *uint8, nel int64, cap int64) []any\n"
"func \"\".sliceslice1 (old []any, lb uint64, width uint64) []any\n"
"func \"\".sliceslice (old []any, lb uint64, hb uint64, width uint64) []any\n"

View File

@ -48,6 +48,7 @@ exprfmt(Fmt *f, Node *n, int prec)
case ODOTMETH:
case ODOTTYPE:
case ODOTTYPE2:
case OXDOT:
case OARRAYBYTESTR:
case OCAP:
case OCLOSE:

View File

@ -99,11 +99,15 @@ func chansend2(hchan chan<- any, elem any) (pres bool)
func closechan(hchan any)
func closedchan(hchan any) bool
func selectnbsend(hchan chan<- any, elem any) bool
func selectnbrecv(elem *any, hchan <-chan any) bool
func newselect(size int) (sel *byte)
func selectsend(sel *byte, hchan chan<- any, elem any) (selected bool)
func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool)
func selectdefault(sel *byte) (selected bool)
func selectgo(sel *byte)
func block()
func makeslice(typ *byte, nel int64, cap int64) (ary []any)
func sliceslice1(old []any, lb uint64, width uint64) (ary []any)

View File

@ -45,27 +45,23 @@ typecheckselect(Node *sel)
break;
case OAS:
// convert x = <-c into OSELRECV(x, c)
// assignment might have introduced a
// conversion. throw it away.
// it will come back when the select code
// gets generated, because it always assigns
// through a temporary.
// 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;
n->right = n->right->left;
break;
case ORECV:
// convert <-c into OSELRECV(N, c)
n->op = OSELRECV;
n->right = n->left;
n->left = N;
// convert <-c into OSELRECV(N, <-c)
n = nod(OSELRECV, N, n);
ncase->left = n;
break;
case OSEND:
@ -81,11 +77,149 @@ typecheckselect(Node *sel)
void
walkselect(Node *sel)
{
int lno;
Node *n, *ncase, *r, *a, *tmp, *var;
int lno, i;
Node *n, *r, *a, *tmp, *var, *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.
if(i == 1) {
cas = sel->list->n;
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:
ch = cheapexpr(n->left, &l);
n->left = ch;
break;
case OSELRECV:
r = n->right;
ch = cheapexpr(r->left, &l);
r->left = ch;
if(n->left == N)
n = r;
else {
n = nod(OAS, n->left, r);
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;
}
// introduce temporary variables for OSELRECV where needed.
// this rewrite is used by both the general code and the next optimization.
for(l=sel->list; l; l=l->next) {
cas = l->n;
n = cas->left;
if(n == N)
continue;
switch(n->op) {
case OSELRECV:
ch = n->right->left;
// If we can use the address of the target without
// violating addressability or order of operations, do so.
// Otherwise introduce a temporary.
// Also introduce a temporary for := variables that escape,
// so that we can delay the heap allocation until the case
// is selected.
if(n->left == N || isblank(n->left))
n->left = nodnil();
else if(n->left->op == ONAME &&
(!n->colas || (n->class&PHEAP) == 0) &&
convertop(ch->type->type, n->left->type, nil) == OCONVNOP) {
n->left = nod(OADDR, n->left, N);
n->left->etype = 1; // pointer does not escape
typecheck(&n->left, Erv);
} else {
tmp = nod(OXXX, N, N);
tempname(tmp, ch->type->type);
a = nod(OADDR, tmp, N);
a->etype = 1; // pointer does not escape
typecheck(&a, Erv);
r = nod(OAS, n->left, tmp);
typecheck(&r, Etop);
cas->nbody = concat(n->ninit, cas->nbody);
n->ninit = nil;
cas->nbody = concat(list1(r), cas->nbody);
n->left = a;
}
}
}
// 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;
r = nod(OIF, N, N);
r->ninit = cas->ninit;
switch(n->op) {
default:
fatal("select %O", n->op);
case OSEND:
// if c != nil && selectnbsend(c, v) { body } else { default body }
ch = cheapexpr(n->left, &r->ninit);
r->ntest = nod(OANDAND, nod(ONE, ch, nodnil()),
mkcall1(chanfn("selectnbsend", 2, ch->type),
types[TBOOL], &r->ninit, 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 = cheapexpr(n->right->left, &r->ninit);
r->ntest = nod(OANDAND, nod(ONE, ch, nodnil()),
mkcall1(chanfn("selectnbrecv", 2, ch->type),
types[TBOOL], &r->ninit, n->left, 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;
@ -96,16 +230,13 @@ walkselect(Node *sel)
typecheck(&r, Etop);
init = list(init, r);
if(sel->list == nil && sel->xoffset != 0)
fatal("double walkselect"); // already rewrote
// register cases
for(l=sel->list; l; l=l->next) {
ncase = l->n;
n = ncase->left;
cas = l->n;
n = cas->left;
r = nod(OIF, N, N);
r->nbody = ncase->ninit;
ncase->ninit = nil;
r->nbody = cas->ninit;
cas->ninit = nil;
if(n != nil) {
r->nbody = concat(r->nbody, n->ninit);
n->ninit = nil;
@ -113,29 +244,24 @@ walkselect(Node *sel)
if(n == nil) {
// selectdefault(sel *byte);
r->ntest = mkcall("selectdefault", types[TBOOL], &init, var);
} else if(n->op == OSEND) {
// selectsend(sel *byte, hchan *chan any, elem any) (selected bool);
r->ntest = mkcall1(chanfn("selectsend", 2, n->left->type), types[TBOOL], &init, var, n->left, n->right);
} else if(n->op == OSELRECV) {
tmp = N;
if(n->left == N)
a = nodnil();
else {
// introduce temporary until we're sure this will succeed.
tmp = nod(OXXX, N, N);
tempname(tmp, n->right->type->type);
a = nod(OADDR, tmp, N);
}
// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
r->ntest = mkcall1(chanfn("selectrecv", 2, n->right->type), types[TBOOL], &init, var, n->right, a);
if(tmp != N) {
a = nod(OAS, n->left, tmp);
typecheck(&a, Etop);
r->nbody = list(r->nbody, a);
}
} else
} else {
switch(n->op) {
default:
fatal("select %O", n->op);
r->nbody = concat(r->nbody, ncase->nbody);
case OSEND:
// selectsend(sel *byte, hchan *chan any, elem any) (selected bool);
r->ntest = mkcall1(chanfn("selectsend", 2, n->left->type), types[TBOOL],
&init, 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],
&init, var, n->right->left, n->left);
break;
}
}
r->nbody = concat(r->nbody, cas->nbody);
r->nbody = list(r->nbody, nod(OBREAK, N, N));
init = list(init, r);
}
@ -143,8 +269,9 @@ walkselect(Node *sel)
// run the select
init = list(init, mkcall("selectgo", T, nil, var));
sel->nbody = init;
sel->list = nil;
walkstmtlist(init);
out:
sel->list = nil;
walkstmtlist(sel->nbody);
lineno = lno;
}

View File

@ -95,6 +95,7 @@ init1(Node *n, NodeList **out)
case OAS2MAPR:
case OAS2DOTTYPE:
case OAS2RECV:
case OAS2RECVCLOSED:
if(n->defn->initorder)
break;
n->defn->initorder = 1;

View File

@ -18,7 +18,7 @@ static int onearg(Node*, char*, ...);
static int twoarg(Node*);
static int lookdot(Node*, Type*, int);
static int looktypedot(Node*, Type*, int);
static void typecheckaste(int, int, Type*, NodeList*, char*);
static void typecheckaste(int, Node*, int, Type*, NodeList*, char*);
static Type* lookdot1(Sym *s, Type *t, Type *f, int);
static int nokeys(NodeList*);
static void typecheckcomplit(Node**);
@ -504,7 +504,7 @@ reswitch:
l = n->left;
if((t = l->type) == T)
goto error;
if(!(top & Eindir))
if(!(top & Eindir) && !n->etype)
addrescapes(n->left);
n->type = ptrto(t);
goto ret;
@ -668,6 +668,13 @@ reswitch:
goto ret;
case OSEND:
if(0 && top == Erv) {
// can happen because grammar for if header accepts
// simple_stmt for condition. Falling through would give
// an error "c <- v used as value" but we can do better.
yyerror("send statement %#N used as value; use select for non-blocking send", n);
goto error;
}
ok |= Etop | Erv;
l = typecheck(&n->left, Erv);
typecheck(&n->right, Erv);
@ -801,7 +808,7 @@ reswitch:
case ODOTMETH:
n->op = OCALLMETH;
typecheckaste(OCALL, 0, getthisx(t), list1(l->left), "method receiver");
typecheckaste(OCALL, n->left, 0, getthisx(t), list1(l->left), "method receiver");
break;
default:
@ -812,7 +819,7 @@ reswitch:
}
break;
}
typecheckaste(OCALL, n->isddd, getinargx(t), n->list, "function argument");
typecheckaste(OCALL, n->left, n->isddd, getinargx(t), n->list, "function argument");
ok |= Etop;
if(t->outtuple == 0)
goto ret;
@ -1246,7 +1253,7 @@ reswitch:
}
if(curfn->type->outnamed && n->list == nil)
goto ret;
typecheckaste(ORETURN, 0, getoutargx(curfn->type), n->list, "return argument");
typecheckaste(ORETURN, nil, 0, getoutargx(curfn->type), n->list, "return argument");
goto ret;
case OSELECT:
@ -1591,7 +1598,7 @@ nokeys(NodeList *l)
* typecheck assignment: type list = expression list
*/
static void
typecheckaste(int op, int isddd, Type *tstruct, NodeList *nl, char *desc)
typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char *desc)
{
Type *t, *tl, *tn;
Node *n;
@ -1610,16 +1617,24 @@ typecheckaste(int op, int isddd, Type *tstruct, NodeList *nl, char *desc)
if(tl->isddd) {
for(; tn; tn=tn->down) {
exportassignok(tn->type, desc);
if(assignop(tn->type, tl->type->type, &why) == 0)
if(assignop(tn->type, tl->type->type, &why) == 0) {
if(call != N)
yyerror("cannot use %T as type %T in argument to %#N%s", tn->type, tl->type->type, desc, call, why);
else
yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type->type, desc, why);
}
}
goto out;
}
if(tn == T)
goto notenough;
exportassignok(tn->type, desc);
if(assignop(tn->type, tl->type, &why) == 0)
if(assignop(tn->type, tl->type, &why) == 0) {
if(call != N)
yyerror("cannot use %T as type %T in argument to %#N%s", tn->type, tl->type, desc, call, why);
else
yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type, desc, why);
}
tn = tn->down;
}
if(tn != T)
@ -1664,18 +1679,28 @@ typecheckaste(int op, int isddd, Type *tstruct, NodeList *nl, char *desc)
}
if(nl != nil)
goto toomany;
if(isddd)
if(isddd) {
if(call != N)
yyerror("invalid use of ... in call to %#N", call);
else
yyerror("invalid use of ... in %#O", op);
}
out:
lineno = lno;
return;
notenough:
if(call != N)
yyerror("not enough arguments in call to %#N", call);
else
yyerror("not enough arguments to %#O", op);
goto out;
toomany:
if(call != N)
yyerror("too many arguments in call to %#N", call);
else
yyerror("too many arguments to %#O", op);
goto out;
}
@ -2360,6 +2385,8 @@ typecheckas2(Node *n)
case ORECV:
n->op = OAS2RECV;
goto common;
yyerror("cannot use multiple-value assignment for non-blocking receive; use select");
goto out;
case ODOTTYPE:
n->op = OAS2DOTTYPE;
r->op = ODOTTYPE2;

View File

@ -296,6 +296,7 @@ loop:
sg = dequeue(&c->sendq, c);
if(sg != nil) {
if(ep != nil)
c->elemalg->copy(c->elemsize, ep, sg->elem);
c->elemalg->copy(c->elemsize, sg->elem, nil);
@ -311,7 +312,6 @@ loop:
if(pres != nil) {
runtime·unlock(c);
c->elemalg->copy(c->elemsize, ep, nil);
*pres = false;
return;
}
@ -328,6 +328,7 @@ loop:
if(sg == nil)
goto loop;
if(ep != nil)
c->elemalg->copy(c->elemsize, ep, sg->elem);
c->elemalg->copy(c->elemsize, sg->elem, nil);
freesg(c, sg);
@ -341,7 +342,6 @@ asynch:
if(pres != nil) {
runtime·unlock(c);
c->elemalg->copy(c->elemsize, ep, nil);
*pres = false;
return;
}
@ -354,6 +354,7 @@ asynch:
runtime·lock(c);
goto asynch;
}
if(ep != nil)
c->elemalg->copy(c->elemsize, ep, c->recvdataq->elem);
c->elemalg->copy(c->elemsize, c->recvdataq->elem, nil);
c->recvdataq = c->recvdataq->link;
@ -377,6 +378,7 @@ asynch:
closed:
if(closed != nil)
*closed = true;
if(ep != nil)
c->elemalg->copy(c->elemsize, ep, nil);
c->closed |= Rclosed;
if(pres != nil)
@ -441,12 +443,18 @@ runtime·chanrecv2(Hchan* c, ...)
int32 o;
byte *ae, *ap;
if(c == nil)
runtime·panicstring("receive from nil channel");
o = runtime·rnd(sizeof(c), Structrnd);
ae = (byte*)&c + o;
o = runtime·rnd(o+c->elemsize, 1);
ap = (byte*)&c + o;
runtime·chanrecv(c, ae, ap, nil);
if(!*ap)
c->elemalg->copy(c->elemsize, ae, nil);
}
// chanrecv3(hchan *chan any) (elem any, closed bool);
@ -457,6 +465,9 @@ runtime·chanrecv3(Hchan* c, ...)
int32 o;
byte *ae, *ac;
if(c == nil)
runtime·panicstring("range over nil channel");
o = runtime·rnd(sizeof(c), Structrnd);
ae = (byte*)&c + o;
o = runtime·rnd(o+c->elemsize, 1);
@ -465,6 +476,66 @@ runtime·chanrecv3(Hchan* c, ...)
runtime·chanrecv(c, ae, nil, ac);
}
// func selectnbsend(c chan any, elem any) bool
//
// compiler implements
//
// select {
// case c <- v:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if c != nil && selectnbsend(c, v) {
// ... foo
// } else {
// ... bar
// }
//
#pragma textflag 7
void
runtime·selectnbsend(Hchan *c, ...)
{
int32 o;
byte *ae, *ap;
o = runtime·rnd(sizeof(c), c->elemalign);
ae = (byte*)&c + o;
o = runtime·rnd(o+c->elemsize, Structrnd);
ap = (byte*)&c + o;
runtime·chansend(c, ae, ap);
}
// func selectnbrecv(elem *any, c chan any) bool
//
// compiler implements
//
// select {
// case v = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if c != nil && selectnbrecv(&v, c) {
// ... foo
// } else {
// ... bar
// }
//
#pragma textflag 7
void
runtime·selectnbrecv(byte *v, Hchan *c, bool ok)
{
runtime·chanrecv(c, v, &ok, nil);
}
// newselect(size uint32) (sel *byte);
#pragma textflag 7
void
@ -625,6 +696,13 @@ selunlock(Select *sel)
}
}
void
runtime·block(void)
{
g->status = Gwaiting; // forever
runtime·gosched();
}
// selectgo(sel *byte);
//
// overwrites return pc on stack to signal which case of the select
@ -648,13 +726,13 @@ runtime·selectgo(Select *sel)
if(debug)
runtime·printf("select: sel=%p\n", sel);
if(sel->ncase < 2) {
if(sel->ncase < 1) {
g->status = Gwaiting; // forever
runtime·gosched();
}
// TODO: make special case of one.
}
// The compiler rewrites selects that statically have
// only 0 or 1 cases plus default into simpler constructs.
// The only way we can end up with such small sel->ncase
// values here is for a larger select in which most channels
// have been nilled out. The general code handles those
// cases correctly, and they are rare enough not to bother
// optimizing (and needing to test).
// generate permuted order
for(i=0; i<sel->ncase; i++)