github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/cmd/compile/internal/gc/select.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gc
     6  
     7  import "cmd/compile/internal/types"
     8  
     9  // select
    10  func typecheckselect(sel *Node) {
    11  	var ncase *Node
    12  	var n *Node
    13  
    14  	var def *Node
    15  	lno := setlineno(sel)
    16  	count := 0
    17  	typecheckslice(sel.Ninit.Slice(), Etop)
    18  	for _, n1 := range sel.List.Slice() {
    19  		count++
    20  		ncase = n1
    21  		setlineno(ncase)
    22  		if ncase.Op != OXCASE {
    23  			Fatalf("typecheckselect %v", ncase.Op)
    24  		}
    25  
    26  		if ncase.List.Len() == 0 {
    27  			// default
    28  			if def != nil {
    29  				yyerror("multiple defaults in select (first at %v)", def.Line())
    30  			} else {
    31  				def = ncase
    32  			}
    33  		} else if ncase.List.Len() > 1 {
    34  			yyerror("select cases cannot be lists")
    35  		} else {
    36  			ncase.List.SetFirst(typecheck(ncase.List.First(), Etop))
    37  			n = ncase.List.First()
    38  			ncase.Left = n
    39  			ncase.List.Set(nil)
    40  			setlineno(n)
    41  			switch n.Op {
    42  			default:
    43  				yyerror("select case must be receive, send or assign recv")
    44  
    45  			// convert x = <-c into OSELRECV(x, <-c).
    46  			// remove implicit conversions; the eventual assignment
    47  			// will reintroduce them.
    48  			case OAS:
    49  				if (n.Right.Op == OCONVNOP || n.Right.Op == OCONVIFACE) && n.Right.Implicit() {
    50  					n.Right = n.Right.Left
    51  				}
    52  
    53  				if n.Right.Op != ORECV {
    54  					yyerror("select assignment must have receive on right hand side")
    55  					break
    56  				}
    57  
    58  				n.Op = OSELRECV
    59  
    60  				// convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok
    61  			case OAS2RECV:
    62  				if n.Rlist.First().Op != ORECV {
    63  					yyerror("select assignment must have receive on right hand side")
    64  					break
    65  				}
    66  
    67  				n.Op = OSELRECV2
    68  				n.Left = n.List.First()
    69  				n.List.Set1(n.List.Second())
    70  				n.Right = n.Rlist.First()
    71  				n.Rlist.Set(nil)
    72  
    73  				// convert <-c into OSELRECV(N, <-c)
    74  			case ORECV:
    75  				n = nod(OSELRECV, nil, n)
    76  
    77  				n.Typecheck = 1
    78  				ncase.Left = n
    79  
    80  			case OSEND:
    81  				break
    82  			}
    83  		}
    84  
    85  		typecheckslice(ncase.Nbody.Slice(), Etop)
    86  	}
    87  
    88  	sel.Xoffset = int64(count)
    89  	lineno = lno
    90  }
    91  
    92  func walkselect(sel *Node) {
    93  	if sel.List.Len() == 0 && sel.Xoffset != 0 {
    94  		Fatalf("double walkselect") // already rewrote
    95  	}
    96  
    97  	lno := setlineno(sel)
    98  	i := sel.List.Len()
    99  
   100  	// optimization: zero-case select
   101  	var init []*Node
   102  	var r *Node
   103  	var n *Node
   104  	var var_ *Node
   105  	var selv *Node
   106  	var chosen *Node
   107  	if i == 0 {
   108  		sel.Nbody.Set1(mkcall("block", nil, nil))
   109  		goto out
   110  	}
   111  
   112  	// optimization: one-case select: single op.
   113  	// TODO(rsc): Reenable optimization once order.go can handle it.
   114  	// golang.org/issue/7672.
   115  	if i == 1 {
   116  		cas := sel.List.First()
   117  		setlineno(cas)
   118  		l := cas.Ninit.Slice()
   119  		if cas.Left != nil { // not default:
   120  			n := cas.Left
   121  			l = append(l, n.Ninit.Slice()...)
   122  			n.Ninit.Set(nil)
   123  			var ch *Node
   124  			switch n.Op {
   125  			default:
   126  				Fatalf("select %v", n.Op)
   127  
   128  				// ok already
   129  			case OSEND:
   130  				ch = n.Left
   131  
   132  			case OSELRECV, OSELRECV2:
   133  				ch = n.Right.Left
   134  				if n.Op == OSELRECV || n.List.Len() == 0 {
   135  					if n.Left == nil {
   136  						n = n.Right
   137  					} else {
   138  						n.Op = OAS
   139  					}
   140  					break
   141  				}
   142  
   143  				if n.Left == nil {
   144  					nblank = typecheck(nblank, Erv|Easgn)
   145  					n.Left = nblank
   146  				}
   147  
   148  				n.Op = OAS2
   149  				n.List.Prepend(n.Left)
   150  				n.Rlist.Set1(n.Right)
   151  				n.Right = nil
   152  				n.Left = nil
   153  				n.Typecheck = 0
   154  				n = typecheck(n, Etop)
   155  			}
   156  
   157  			// if ch == nil { block() }; n;
   158  			a := nod(OIF, nil, nil)
   159  
   160  			a.Left = nod(OEQ, ch, nodnil())
   161  			var ln Nodes
   162  			ln.Set(l)
   163  			a.Nbody.Set1(mkcall("block", nil, &ln))
   164  			l = ln.Slice()
   165  			a = typecheck(a, Etop)
   166  			l = append(l, a)
   167  			l = append(l, n)
   168  		}
   169  
   170  		l = append(l, cas.Nbody.Slice()...)
   171  		l = append(l, nod(OBREAK, nil, nil))
   172  		sel.Nbody.Set(l)
   173  		goto out
   174  	}
   175  
   176  	// convert case value arguments to addresses.
   177  	// this rewrite is used by both the general code and the next optimization.
   178  	for _, cas := range sel.List.Slice() {
   179  		setlineno(cas)
   180  		n = cas.Left
   181  		if n == nil {
   182  			continue
   183  		}
   184  		switch n.Op {
   185  		case OSEND:
   186  			n.Right = nod(OADDR, n.Right, nil)
   187  			n.Right = typecheck(n.Right, Erv)
   188  
   189  		case OSELRECV, OSELRECV2:
   190  			if n.Op == OSELRECV2 && n.List.Len() == 0 {
   191  				n.Op = OSELRECV
   192  			}
   193  			if n.Op == OSELRECV2 {
   194  				n.List.SetFirst(nod(OADDR, n.List.First(), nil))
   195  				n.List.SetFirst(typecheck(n.List.First(), Erv))
   196  			}
   197  
   198  			if n.Left == nil {
   199  				n.Left = nodnil()
   200  			} else {
   201  				n.Left = nod(OADDR, n.Left, nil)
   202  				n.Left = typecheck(n.Left, Erv)
   203  			}
   204  		}
   205  	}
   206  
   207  	// optimization: two-case select but one is default: single non-blocking op.
   208  	if i == 2 && (sel.List.First().Left == nil || sel.List.Second().Left == nil) {
   209  		var cas *Node
   210  		var dflt *Node
   211  		if sel.List.First().Left == nil {
   212  			cas = sel.List.Second()
   213  			dflt = sel.List.First()
   214  		} else {
   215  			dflt = sel.List.Second()
   216  			cas = sel.List.First()
   217  		}
   218  
   219  		n := cas.Left
   220  		setlineno(n)
   221  		r := nod(OIF, nil, nil)
   222  		r.Ninit.Set(cas.Ninit.Slice())
   223  		switch n.Op {
   224  		default:
   225  			Fatalf("select %v", n.Op)
   226  
   227  		case OSEND:
   228  			// if selectnbsend(c, v) { body } else { default body }
   229  			ch := n.Left
   230  			r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right)
   231  
   232  		case OSELRECV:
   233  			// if c != nil && selectnbrecv(&v, c) { body } else { default body }
   234  			r = nod(OIF, nil, nil)
   235  			r.Ninit.Set(cas.Ninit.Slice())
   236  			ch := n.Right.Left
   237  			r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, n.Left, ch)
   238  
   239  		case OSELRECV2:
   240  			// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
   241  			r = nod(OIF, nil, nil)
   242  			r.Ninit.Set(cas.Ninit.Slice())
   243  			ch := n.Right.Left
   244  			r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, n.Left, n.List.First(), ch)
   245  		}
   246  
   247  		r.Left = typecheck(r.Left, Erv)
   248  		r.Nbody.Set(cas.Nbody.Slice())
   249  		r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
   250  		sel.Nbody.Set2(r, nod(OBREAK, nil, nil))
   251  		goto out
   252  	}
   253  
   254  	init = sel.Ninit.Slice()
   255  	sel.Ninit.Set(nil)
   256  
   257  	// generate sel-struct
   258  	setlineno(sel)
   259  	selv = temp(selecttype(sel.Xoffset))
   260  	r = nod(OAS, selv, nil)
   261  	r = typecheck(r, Etop)
   262  	init = append(init, r)
   263  	var_ = conv(conv(nod(OADDR, selv, nil), types.Types[TUNSAFEPTR]), types.NewPtr(types.Types[TUINT8]))
   264  	r = mkcall("newselect", nil, nil, var_, nodintconst(selv.Type.Width), nodintconst(sel.Xoffset))
   265  	r = typecheck(r, Etop)
   266  	init = append(init, r)
   267  
   268  	// register cases
   269  	for _, cas := range sel.List.Slice() {
   270  		setlineno(cas)
   271  
   272  		init = append(init, cas.Ninit.Slice()...)
   273  		cas.Ninit.Set(nil)
   274  
   275  		var x *Node
   276  		if n := cas.Left; n != nil {
   277  			init = append(init, n.Ninit.Slice()...)
   278  
   279  			switch n.Op {
   280  			default:
   281  				Fatalf("select %v", n.Op)
   282  			case OSEND:
   283  				// selectsend(sel *byte, hchan *chan any, elem *any)
   284  				x = mkcall1(chanfn("selectsend", 2, n.Left.Type), nil, nil, var_, n.Left, n.Right)
   285  			case OSELRECV:
   286  				// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
   287  				x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, nodnil())
   288  			case OSELRECV2:
   289  				// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
   290  				x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, n.List.First())
   291  			}
   292  		} else {
   293  			// selectdefault(sel *byte)
   294  			x = mkcall("selectdefault", nil, nil, var_)
   295  		}
   296  
   297  		init = append(init, x)
   298  	}
   299  
   300  	// run the select
   301  	setlineno(sel)
   302  	chosen = temp(types.Types[TINT])
   303  	r = nod(OAS, chosen, mkcall("selectgo", types.Types[TINT], nil, var_))
   304  	r = typecheck(r, Etop)
   305  	init = append(init, r)
   306  
   307  	// selv is no longer alive after selectgo.
   308  	init = append(init, nod(OVARKILL, selv, nil))
   309  
   310  	// dispatch cases
   311  	for i, cas := range sel.List.Slice() {
   312  		setlineno(cas)
   313  
   314  		cond := nod(OEQ, chosen, nodintconst(int64(i)))
   315  		cond = typecheck(cond, Erv)
   316  
   317  		r = nod(OIF, cond, nil)
   318  		r.Nbody.AppendNodes(&cas.Nbody)
   319  		r.Nbody.Append(nod(OBREAK, nil, nil))
   320  		init = append(init, r)
   321  	}
   322  
   323  	sel.Nbody.Set(init)
   324  
   325  out:
   326  	sel.List.Set(nil)
   327  	walkstmtlist(sel.Nbody.Slice())
   328  	lineno = lno
   329  }
   330  
   331  // Keep in sync with src/runtime/select.go.
   332  func selecttype(size int64) *types.Type {
   333  	// TODO(dvyukov): it's possible to generate Scase only once
   334  	// and then cache; and also cache Select per size.
   335  
   336  	scase := tostruct([]*Node{
   337  		namedfield("elem", types.NewPtr(types.Types[TUINT8])),
   338  		namedfield("chan", types.NewPtr(types.Types[TUINT8])),
   339  		namedfield("pc", types.Types[TUINTPTR]),
   340  		namedfield("kind", types.Types[TUINT16]),
   341  		namedfield("receivedp", types.NewPtr(types.Types[TUINT8])),
   342  		namedfield("releasetime", types.Types[TUINT64]),
   343  	})
   344  	scase.SetNoalg(true)
   345  	scase.SetLocal(true)
   346  
   347  	sel := tostruct([]*Node{
   348  		namedfield("tcase", types.Types[TUINT16]),
   349  		namedfield("ncase", types.Types[TUINT16]),
   350  		namedfield("pollorder", types.NewPtr(types.Types[TUINT8])),
   351  		namedfield("lockorder", types.NewPtr(types.Types[TUINT8])),
   352  		namedfield("scase", types.NewArray(scase, size)),
   353  		namedfield("lockorderarr", types.NewArray(types.Types[TUINT16], size)),
   354  		namedfield("pollorderarr", types.NewArray(types.Types[TUINT16], size)),
   355  	})
   356  	sel.SetNoalg(true)
   357  	sel.SetLocal(true)
   358  
   359  	return sel
   360  }