diff options
Diffstat (limited to 'src/cmd/gc/select.c')
-rw-r--r-- | src/cmd/gc/select.c | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/src/cmd/gc/select.c b/src/cmd/gc/select.c new file mode 100644 index 000000000..909ad3aa4 --- /dev/null +++ b/src/cmd/gc/select.c @@ -0,0 +1,351 @@ +// 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 "go.h" + +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 OSELRECV(x, <-c) with ntest=ok + if(n->right->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->right = n->rlist->n; + break; + + case ORECV: + // convert <-c into OSELRECV(N, <-c) + n = nod(OSELRECV, N, n); + 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, *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; + 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: + 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; + + case OSELRECV2: + r = n->right; + ch = cheapexpr(r->left, &l); + r->left = ch; + + a = nod(OAS2, N, N); + a->list = n->list; + a->rlist = n->rlist; + n = a; + 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; + setlineno(cas); + n = cas->left; + if(n == N) + continue; + switch(n->op) { + case OSELRECV: + case OSELRECV2: + 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->op == OSELRECV2) { + if(n->ntest == N || isblank(n->ntest)) + n->ntest = nodnil(); + else if(n->ntest->op == ONAME && + (!n->colas || (n->ntest->class&PHEAP) == 0) && + convertop(types[TBOOL], n->ntest->type, nil) == OCONVNOP) { + n->ntest = nod(OADDR, n->ntest, N); + n->ntest->etype = 1; // pointer does not escape + typecheck(&n->ntest, Erv); + } else { + tmp = nod(OXXX, N, N); + tempname(tmp, types[TBOOL]); + a = nod(OADDR, tmp, N); + a->etype = 1; // pointer does not escape + typecheck(&a, Erv); + r = nod(OAS, n->ntest, tmp); + typecheck(&r, Etop); + cas->nbody = concat(list1(r), cas->nbody); + n->ntest = a; + } + } + + if(n->left == N || isblank(n->left)) + n->left = nodnil(); + else if(n->left->op == ONAME && + (!n->colas || (n->left->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(list1(r), cas->nbody); + n->left = a; + } + + cas->nbody = concat(n->ninit, cas->nbody); + n->ninit = nil; + 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 c != nil && selectnbsend(c, v) { body } else { default body } + ch = cheapexpr(n->left, &r->ninit); + 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 = cheapexpr(n->right->left, &r->ninit); + 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 = cheapexpr(n->right->left, &r->ninit); + 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); + var = nod(OXXX, N, N); + tempname(var, ptrto(types[TUINT8])); + r = nod(OAS, var, mkcall("newselect", var->type, nil, 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->nbody = cas->ninit; + cas->ninit = nil; + if(n != nil) { + r->nbody = concat(r->nbody, n->ninit); + n->ninit = nil; + } + if(n == nil) { + // selectdefault(sel *byte); + r->ntest = mkcall("selectdefault", types[TBOOL], &init, var); + } else { + switch(n->op) { + default: + fatal("select %O", n->op); + + case OSEND: + // selectsend(sel *byte, hchan *chan any, elem *any) (selected bool); + n->left = safeexpr(n->left, &r->ninit); + n->right = localexpr(n->right, n->left->type->type, &r->ninit); + n->right = nod(OADDR, n->right, N); + n->right->etype = 1; // pointer does not escape + typecheck(&n->right, Erv); + 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; + + 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], + &init, var, n->right->left, n->left, n->ntest); + break; + } + } + 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; +} |