// 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 walk import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" ) func walkSelect(sel *ir.SelectStmt) { lno := ir.SetPos(sel) if sel.Walked() { base.Fatalf("double walkSelect") } sel.SetWalked(true) init := ir.TakeInit(sel) init = append(init, walkSelectCases(sel.Cases)...) sel.Cases = nil sel.Compiled = init walkStmtList(sel.Compiled) base.Pos = lno } func walkSelectCases(cases []*ir.CommClause) []ir.Node { ncas := len(cases) sellineno := base.Pos // optimization: zero-case select if ncas == 0 { return []ir.Node{mkcallstmt("block")} } // optimization: one-case select: single op. if ncas == 1 { cas := cases[0] ir.SetPos(cas) l := cas.Init() if cas.Comm != nil { // not default: n := cas.Comm l = append(l, ir.TakeInit(n)...) switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: // already ok case ir.OSELRECV2: r := n.(*ir.AssignListStmt) if ir.IsBlank(r.Lhs[0]) && ir.IsBlank(r.Lhs[1]) { n = r.Rhs[0] break } r.SetOp(ir.OAS2RECV) } l = append(l, n) } l = append(l, cas.Body...) l = append(l, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) return l } // convert case value arguments to addresses. // this rewrite is used by both the general code and the next optimization. var dflt *ir.CommClause for _, cas := range cases { ir.SetPos(cas) n := cas.Comm if n == nil { dflt = cas continue } switch n.Op() { case ir.OSEND: n := n.(*ir.SendStmt) n.Value = typecheck.NodAddr(n.Value) n.Value = typecheck.Expr(n.Value) case ir.OSELRECV2: n := n.(*ir.AssignListStmt) if !ir.IsBlank(n.Lhs[0]) { n.Lhs[0] = typecheck.NodAddr(n.Lhs[0]) n.Lhs[0] = typecheck.Expr(n.Lhs[0]) } } } // optimization: two-case select but one is default: single non-blocking op. if ncas == 2 && dflt != nil { cas := cases[0] if cas == dflt { cas = cases[1] } n := cas.Comm ir.SetPos(n) r := ir.NewIfStmt(base.Pos, nil, nil, nil) *r.PtrInit() = cas.Init() var cond ir.Node switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: // if selectnbsend(c, v) { body } else { default body } n := n.(*ir.SendStmt) ch := n.Chan cond = mkcall1(chanfn("selectnbsend", 2, ch.Type()), types.Types[types.TBOOL], r.PtrInit(), ch, n.Value) case ir.OSELRECV2: n := n.(*ir.AssignListStmt) recv := n.Rhs[0].(*ir.UnaryExpr) ch := recv.X elem := n.Lhs[0] if ir.IsBlank(elem) { elem = typecheck.NodNil() } cond = typecheck.Temp(types.Types[types.TBOOL]) fn := chanfn("selectnbrecv", 2, ch.Type()) call := mkcall1(fn, fn.Type().Results(), r.PtrInit(), elem, ch) as := ir.NewAssignListStmt(r.Pos(), ir.OAS2, []ir.Node{cond, n.Lhs[1]}, []ir.Node{call}) r.PtrInit().Append(typecheck.Stmt(as)) } r.Cond = typecheck.Expr(cond) r.Body = cas.Body r.Else = append(dflt.Init(), dflt.Body...) return []ir.Node{r, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)} } if dflt != nil { ncas-- } casorder := make([]*ir.CommClause, ncas) nsends, nrecvs := 0, 0 var init []ir.Node // generate sel-struct base.Pos = sellineno selv := typecheck.Temp(types.NewArray(scasetype(), int64(ncas))) init = append(init, typecheck.Stmt(ir.NewAssignStmt(base.Pos, selv, nil))) // No initialization for order; runtime.selectgo is responsible for that. order := typecheck.Temp(types.NewArray(types.Types[types.TUINT16], 2*int64(ncas))) var pc0, pcs ir.Node if base.Flag.Race { pcs = typecheck.Temp(types.NewArray(types.Types[types.TUINTPTR], int64(ncas))) pc0 = typecheck.Expr(typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(0)))) } else { pc0 = typecheck.NodNil() } // register cases for _, cas := range cases { ir.SetPos(cas) init = append(init, ir.TakeInit(cas)...) n := cas.Comm if n == nil { // default: continue } var i int var c, elem ir.Node switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: n := n.(*ir.SendStmt) i = nsends nsends++ c = n.Chan elem = n.Value case ir.OSELRECV2: n := n.(*ir.AssignListStmt) nrecvs++ i = ncas - nrecvs recv := n.Rhs[0].(*ir.UnaryExpr) c = recv.X elem = n.Lhs[0] } casorder[i] = cas setField := func(f string, val ir.Node) { r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, ir.NewIndexExpr(base.Pos, selv, ir.NewInt(int64(i))), typecheck.Lookup(f)), val) init = append(init, typecheck.Stmt(r)) } c = typecheck.ConvNop(c, types.Types[types.TUNSAFEPTR]) setField("c", c) if !ir.IsBlank(elem) { elem = typecheck.ConvNop(elem, types.Types[types.TUNSAFEPTR]) setField("elem", elem) } // TODO(mdempsky): There should be a cleaner way to // handle this. if base.Flag.Race { r := mkcallstmt("selectsetpc", typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(int64(i))))) init = append(init, r) } } if nsends+nrecvs != ncas { base.Fatalf("walkSelectCases: miscount: %v + %v != %v", nsends, nrecvs, ncas) } // run the select base.Pos = sellineno chosen := typecheck.Temp(types.Types[types.TINT]) recvOK := typecheck.Temp(types.Types[types.TBOOL]) r := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) r.Lhs = []ir.Node{chosen, recvOK} fn := typecheck.LookupRuntime("selectgo") var fnInit ir.Nodes r.Rhs = []ir.Node{mkcall1(fn, fn.Type().Results(), &fnInit, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, ir.NewInt(int64(nsends)), ir.NewInt(int64(nrecvs)), ir.NewBool(dflt == nil))} init = append(init, fnInit...) init = append(init, typecheck.Stmt(r)) // selv and order are no longer alive after selectgo. init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, selv)) init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, order)) if base.Flag.Race { init = append(init, ir.NewUnaryExpr(base.Pos, ir.OVARKILL, pcs)) } // dispatch cases dispatch := func(cond ir.Node, cas *ir.CommClause) { cond = typecheck.Expr(cond) cond = typecheck.DefaultLit(cond, nil) r := ir.NewIfStmt(base.Pos, cond, nil, nil) if n := cas.Comm; n != nil && n.Op() == ir.OSELRECV2 { n := n.(*ir.AssignListStmt) if !ir.IsBlank(n.Lhs[1]) { x := ir.NewAssignStmt(base.Pos, n.Lhs[1], recvOK) r.Body.Append(typecheck.Stmt(x)) } } r.Body.Append(cas.Body.Take()...) r.Body.Append(ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) init = append(init, r) } if dflt != nil { ir.SetPos(dflt) dispatch(ir.NewBinaryExpr(base.Pos, ir.OLT, chosen, ir.NewInt(0)), dflt) } for i, cas := range casorder { ir.SetPos(cas) dispatch(ir.NewBinaryExpr(base.Pos, ir.OEQ, chosen, ir.NewInt(int64(i))), cas) } return init } // bytePtrToIndex returns a Node representing "(*byte)(&n[i])". func bytePtrToIndex(n ir.Node, i int64) ir.Node { s := typecheck.NodAddr(ir.NewIndexExpr(base.Pos, n, ir.NewInt(i))) t := types.NewPtr(types.Types[types.TUINT8]) return typecheck.ConvNop(s, t) } var scase *types.Type // Keep in sync with src/runtime/select.go. func scasetype() *types.Type { if scase == nil { scase = types.NewStruct(types.NoPkg, []*types.Field{ types.NewField(base.Pos, typecheck.Lookup("c"), types.Types[types.TUNSAFEPTR]), types.NewField(base.Pos, typecheck.Lookup("elem"), types.Types[types.TUNSAFEPTR]), }) scase.SetNoalg(true) } return scase }