diff options
Diffstat (limited to 'src/cmd/compile/internal/gc/select.go')
-rw-r--r-- | src/cmd/compile/internal/gc/select.go | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/gc/select.go b/src/cmd/compile/internal/gc/select.go new file mode 100644 index 0000000..97e0424 --- /dev/null +++ b/src/cmd/compile/internal/gc/select.go @@ -0,0 +1,387 @@ +// 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. + +package gc + +import "cmd/compile/internal/types" + +// select +func typecheckselect(sel *Node) { + var def *Node + lno := setlineno(sel) + typecheckslice(sel.Ninit.Slice(), ctxStmt) + for _, ncase := range sel.List.Slice() { + if ncase.Op != OCASE { + setlineno(ncase) + Fatalf("typecheckselect %v", ncase.Op) + } + + if ncase.List.Len() == 0 { + // default + if def != nil { + yyerrorl(ncase.Pos, "multiple defaults in select (first at %v)", def.Line()) + } else { + def = ncase + } + } else if ncase.List.Len() > 1 { + yyerrorl(ncase.Pos, "select cases cannot be lists") + } else { + ncase.List.SetFirst(typecheck(ncase.List.First(), ctxStmt)) + n := ncase.List.First() + ncase.Left = n + ncase.List.Set(nil) + switch n.Op { + default: + pos := n.Pos + if n.Op == ONAME { + // We don't have the right position for ONAME nodes (see #15459 and + // others). Using ncase.Pos for now as it will provide the correct + // line number (assuming the expression follows the "case" keyword + // on the same line). This matches the approach before 1.10. + pos = ncase.Pos + } + yyerrorl(pos, "select case must be receive, send or assign recv") + + // convert x = <-c into OSELRECV(x, <-c). + // remove implicit conversions; the eventual assignment + // will reintroduce them. + case OAS: + if (n.Right.Op == OCONVNOP || n.Right.Op == OCONVIFACE) && n.Right.Implicit() { + n.Right = n.Right.Left + } + + if n.Right.Op != ORECV { + yyerrorl(n.Pos, "select assignment must have receive on right hand side") + break + } + + n.Op = OSELRECV + + // convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok + case OAS2RECV: + if n.Right.Op != ORECV { + yyerrorl(n.Pos, "select assignment must have receive on right hand side") + break + } + + n.Op = OSELRECV2 + n.Left = n.List.First() + n.List.Set1(n.List.Second()) + + // convert <-c into OSELRECV(N, <-c) + case ORECV: + n = nodl(n.Pos, OSELRECV, nil, n) + + n.SetTypecheck(1) + ncase.Left = n + + case OSEND: + break + } + } + + typecheckslice(ncase.Nbody.Slice(), ctxStmt) + } + + lineno = lno +} + +func walkselect(sel *Node) { + lno := setlineno(sel) + if sel.Nbody.Len() != 0 { + Fatalf("double walkselect") + } + + init := sel.Ninit.Slice() + sel.Ninit.Set(nil) + + init = append(init, walkselectcases(&sel.List)...) + sel.List.Set(nil) + + sel.Nbody.Set(init) + walkstmtlist(sel.Nbody.Slice()) + + lineno = lno +} + +func walkselectcases(cases *Nodes) []*Node { + ncas := cases.Len() + sellineno := lineno + + // optimization: zero-case select + if ncas == 0 { + return []*Node{mkcall("block", nil, nil)} + } + + // optimization: one-case select: single op. + if ncas == 1 { + cas := cases.First() + setlineno(cas) + l := cas.Ninit.Slice() + if cas.Left != nil { // not default: + n := cas.Left + l = append(l, n.Ninit.Slice()...) + n.Ninit.Set(nil) + switch n.Op { + default: + Fatalf("select %v", n.Op) + + case OSEND: + // already ok + + case OSELRECV, OSELRECV2: + if n.Op == OSELRECV || n.List.Len() == 0 { + if n.Left == nil { + n = n.Right + } else { + n.Op = OAS + } + break + } + + if n.Left == nil { + nblank = typecheck(nblank, ctxExpr|ctxAssign) + n.Left = nblank + } + + n.Op = OAS2 + n.List.Prepend(n.Left) + n.Rlist.Set1(n.Right) + n.Right = nil + n.Left = nil + n.SetTypecheck(0) + n = typecheck(n, ctxStmt) + } + + l = append(l, n) + } + + l = append(l, cas.Nbody.Slice()...) + l = append(l, nod(OBREAK, nil, nil)) + return l + } + + // convert case value arguments to addresses. + // this rewrite is used by both the general code and the next optimization. + var dflt *Node + for _, cas := range cases.Slice() { + setlineno(cas) + n := cas.Left + if n == nil { + dflt = cas + continue + } + switch n.Op { + case OSEND: + n.Right = nod(OADDR, n.Right, nil) + n.Right = typecheck(n.Right, ctxExpr) + + case OSELRECV, OSELRECV2: + if n.Op == OSELRECV2 && n.List.Len() == 0 { + n.Op = OSELRECV + } + + if n.Left != nil { + n.Left = nod(OADDR, n.Left, nil) + n.Left = typecheck(n.Left, ctxExpr) + } + } + } + + // optimization: two-case select but one is default: single non-blocking op. + if ncas == 2 && dflt != nil { + cas := cases.First() + if cas == dflt { + cas = cases.Second() + } + + n := cas.Left + setlineno(n) + r := nod(OIF, nil, nil) + r.Ninit.Set(cas.Ninit.Slice()) + switch n.Op { + default: + Fatalf("select %v", n.Op) + + case OSEND: + // if selectnbsend(c, v) { body } else { default body } + ch := n.Left + r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right) + + case OSELRECV: + // if selectnbrecv(&v, c) { body } else { default body } + ch := n.Right.Left + elem := n.Left + if elem == nil { + elem = nodnil() + } + r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch) + + case OSELRECV2: + // if selectnbrecv2(&v, &received, c) { body } else { default body } + ch := n.Right.Left + elem := n.Left + if elem == nil { + elem = nodnil() + } + receivedp := nod(OADDR, n.List.First(), nil) + receivedp = typecheck(receivedp, ctxExpr) + r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, receivedp, ch) + } + + r.Left = typecheck(r.Left, ctxExpr) + r.Nbody.Set(cas.Nbody.Slice()) + r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...)) + return []*Node{r, nod(OBREAK, nil, nil)} + } + + if dflt != nil { + ncas-- + } + casorder := make([]*Node, ncas) + nsends, nrecvs := 0, 0 + + var init []*Node + + // generate sel-struct + lineno = sellineno + selv := temp(types.NewArray(scasetype(), int64(ncas))) + r := nod(OAS, selv, nil) + r = typecheck(r, ctxStmt) + init = append(init, r) + + // No initialization for order; runtime.selectgo is responsible for that. + order := temp(types.NewArray(types.Types[TUINT16], 2*int64(ncas))) + + var pc0, pcs *Node + if flag_race { + pcs = temp(types.NewArray(types.Types[TUINTPTR], int64(ncas))) + pc0 = typecheck(nod(OADDR, nod(OINDEX, pcs, nodintconst(0)), nil), ctxExpr) + } else { + pc0 = nodnil() + } + + // register cases + for _, cas := range cases.Slice() { + setlineno(cas) + + init = append(init, cas.Ninit.Slice()...) + cas.Ninit.Set(nil) + + n := cas.Left + if n == nil { // default: + continue + } + + var i int + var c, elem *Node + switch n.Op { + default: + Fatalf("select %v", n.Op) + case OSEND: + i = nsends + nsends++ + c = n.Left + elem = n.Right + case OSELRECV, OSELRECV2: + nrecvs++ + i = ncas - nrecvs + c = n.Right.Left + elem = n.Left + } + + casorder[i] = cas + + setField := func(f string, val *Node) { + r := nod(OAS, nodSym(ODOT, nod(OINDEX, selv, nodintconst(int64(i))), lookup(f)), val) + r = typecheck(r, ctxStmt) + init = append(init, r) + } + + c = convnop(c, types.Types[TUNSAFEPTR]) + setField("c", c) + if elem != nil { + elem = convnop(elem, types.Types[TUNSAFEPTR]) + setField("elem", elem) + } + + // TODO(mdempsky): There should be a cleaner way to + // handle this. + if flag_race { + r = mkcall("selectsetpc", nil, nil, nod(OADDR, nod(OINDEX, pcs, nodintconst(int64(i))), nil)) + init = append(init, r) + } + } + if nsends+nrecvs != ncas { + Fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas) + } + + // run the select + lineno = sellineno + chosen := temp(types.Types[TINT]) + recvOK := temp(types.Types[TBOOL]) + r = nod(OAS2, nil, nil) + r.List.Set2(chosen, recvOK) + fn := syslook("selectgo") + r.Rlist.Set1(mkcall1(fn, fn.Type.Results(), nil, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, nodintconst(int64(nsends)), nodintconst(int64(nrecvs)), nodbool(dflt == nil))) + r = typecheck(r, ctxStmt) + init = append(init, r) + + // selv and order are no longer alive after selectgo. + init = append(init, nod(OVARKILL, selv, nil)) + init = append(init, nod(OVARKILL, order, nil)) + if flag_race { + init = append(init, nod(OVARKILL, pcs, nil)) + } + + // dispatch cases + dispatch := func(cond, cas *Node) { + cond = typecheck(cond, ctxExpr) + cond = defaultlit(cond, nil) + + r := nod(OIF, cond, nil) + + if n := cas.Left; n != nil && n.Op == OSELRECV2 { + x := nod(OAS, n.List.First(), recvOK) + x = typecheck(x, ctxStmt) + r.Nbody.Append(x) + } + + r.Nbody.AppendNodes(&cas.Nbody) + r.Nbody.Append(nod(OBREAK, nil, nil)) + init = append(init, r) + } + + if dflt != nil { + setlineno(dflt) + dispatch(nod(OLT, chosen, nodintconst(0)), dflt) + } + for i, cas := range casorder { + setlineno(cas) + dispatch(nod(OEQ, chosen, nodintconst(int64(i))), cas) + } + + return init +} + +// bytePtrToIndex returns a Node representing "(*byte)(&n[i])". +func bytePtrToIndex(n *Node, i int64) *Node { + s := nod(OADDR, nod(OINDEX, n, nodintconst(i)), nil) + t := types.NewPtr(types.Types[TUINT8]) + return convnop(s, t) +} + +var scase *types.Type + +// Keep in sync with src/runtime/select.go. +func scasetype() *types.Type { + if scase == nil { + scase = tostruct([]*Node{ + namedfield("c", types.Types[TUNSAFEPTR]), + namedfield("elem", types.Types[TUNSAFEPTR]), + }) + scase.SetNoalg(true) + } + return scase +} |