root/src/cmd/gc/select.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. typecheckselect
  2. walkselect

// 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"

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, *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:
                                ch = n->right->left;
                        Selrecv1:
                                if(n->left == N)
                                        n = n->right;
                                else
                                        n->op = OAS;
                                break;
                        
                        case OSELRECV2:
                                ch = n->right->left;
                                if(n->ntest == N)
                                        goto Selrecv1;
                                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);
        var = temp(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->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;
                        }
                }
                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;
}

/* [<][>][^][v][top][bottom][index][help] */