Skip to content

Commit

Permalink
runtime: eliminate scase.kind field
Browse files Browse the repository at this point in the history
Currently, we include a "kind" field on scase to distinguish the three
kinds of cases in a select statement: sends, receives, and defaults.

This commit removes by kind field by instead arranging for the
compiler to always place sends before receives, and to provide their
counts separately. It also passes an explicit "block bool" parameter
to avoid needing to include a default case in the array.

It's safe to shuffle cases like this because the runtime will
randomize the order they're polled in anyway.

Fixes #40410.

Change-Id: Iaeaed4cf7bddd576d78f2c863bd91a03a5c82df2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245125
Reviewed-by: Keith Randall <[email protected]>
  • Loading branch information
mdempsky committed Aug 18, 2020
1 parent d36bc7d commit fe23ba4
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 101 deletions.
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/gc/builtin.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/cmd/compile/internal/gc/builtin/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func selectnbrecv(elem *any, hchan <-chan any) bool
func selectnbrecv2(elem *any, received *bool, hchan <-chan any) bool

func selectsetpc(pc *uintptr)
func selectgo(cas0 *byte, order0 *byte, pc0 *uintptr, ncases int) (int, bool)
func selectgo(cas0 *byte, order0 *byte, pc0 *uintptr, nsends int, nrecvs int, block bool) (int, bool)
func block()

func makeslice(typ *byte, len int, cap int) unsafe.Pointer
Expand Down
108 changes: 55 additions & 53 deletions src/cmd/compile/internal/gc/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,16 @@ func walkselect(sel *Node) {
}

func walkselectcases(cases *Nodes) []*Node {
n := cases.Len()
ncas := cases.Len()
sellineno := lineno

// optimization: zero-case select
if n == 0 {
if ncas == 0 {
return []*Node{mkcall("block", nil, nil)}
}

// optimization: one-case select: single op.
// TODO(rsc): Reenable optimization once order.go can handle it.
// golang.org/issue/7672.
if n == 1 {
if ncas == 1 {
cas := cases.First()
setlineno(cas)
l := cas.Ninit.Slice()
Expand Down Expand Up @@ -178,10 +176,12 @@ func walkselectcases(cases *Nodes) []*Node {

// 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 {
Expand All @@ -202,15 +202,10 @@ func walkselectcases(cases *Nodes) []*Node {
}

// optimization: two-case select but one is default: single non-blocking op.
if n == 2 && (cases.First().Left == nil || cases.Second().Left == nil) {
var cas *Node
var dflt *Node
if cases.First().Left == nil {
if ncas == 2 && dflt != nil {
cas := cases.First()
if cas == dflt {
cas = cases.Second()
dflt = cases.First()
} else {
dflt = cases.Second()
cas = cases.First()
}

n := cas.Left
Expand Down Expand Up @@ -257,74 +252,73 @@ func walkselectcases(cases *Nodes) []*Node {
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(n)))
selv := temp(types.NewArray(scasetype(), int64(ncas)))
r := nod(OAS, selv, nil)
r = typecheck(r, ctxStmt)
init = append(init, r)

order := temp(types.NewArray(types.Types[TUINT16], 2*int64(n)))
order := temp(types.NewArray(types.Types[TUINT16], 2*int64(ncas)))
r = nod(OAS, order, nil)
r = typecheck(r, ctxStmt)
init = append(init, r)

var pc0, pcs *Node
if flag_race {
pcs = temp(types.NewArray(types.Types[TUINTPTR], int64(n)))
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 i, cas := range cases.Slice() {
for _, cas := range cases.Slice() {
setlineno(cas)

init = append(init, cas.Ninit.Slice()...)
cas.Ninit.Set(nil)

// Keep in sync with runtime/select.go.
const (
caseNil = iota
caseRecv
caseSend
caseDefault
)
n := cas.Left
if n == nil { // default:
continue
}

var i int
var c, elem *Node
var kind int64 = caseDefault

if n := cas.Left; n != nil {
init = append(init, n.Ninit.Slice()...)

switch n.Op {
default:
Fatalf("select %v", n.Op)
case OSEND:
kind = caseSend
c = n.Left
elem = n.Right
case OSELRECV, OSELRECV2:
kind = caseRecv
c = n.Right.Left
elem = n.Left
}
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)
}

setField("kind", nodintconst(kind))
if c != nil {
c = convnop(c, types.Types[TUNSAFEPTR])
setField("c", c)
}
c = convnop(c, types.Types[TUNSAFEPTR])
setField("c", c)
if elem != nil {
elem = convnop(elem, types.Types[TUNSAFEPTR])
setField("elem", elem)
Expand All @@ -337,6 +331,9 @@ func walkselectcases(cases *Nodes) []*Node {
init = append(init, r)
}
}
if nsends+nrecvs != ncas {
Fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas)
}

// run the select
lineno = sellineno
Expand All @@ -345,7 +342,7 @@ func walkselectcases(cases *Nodes) []*Node {
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(n))))
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)

Expand All @@ -357,14 +354,11 @@ func walkselectcases(cases *Nodes) []*Node {
}

// dispatch cases
for i, cas := range cases.Slice() {
setlineno(cas)

cond := nod(OEQ, chosen, nodintconst(int64(i)))
dispatch := func(cond, cas *Node) {
cond = typecheck(cond, ctxExpr)
cond = defaultlit(cond, nil)

r = nod(OIF, cond, nil)
r := nod(OIF, cond, nil)

if n := cas.Left; n != nil && n.Op == OSELRECV2 {
x := nod(OAS, n.List.First(), recvOK)
Expand All @@ -377,6 +371,15 @@ func walkselectcases(cases *Nodes) []*Node {
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
}

Expand All @@ -395,7 +398,6 @@ func scasetype() *types.Type {
scase = tostruct([]*Node{
namedfield("c", types.Types[TUNSAFEPTR]),
namedfield("elem", types.Types[TUNSAFEPTR]),
namedfield("kind", types.Types[TUINT16]),
})
scase.SetNoalg(true)
}
Expand Down
8 changes: 8 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,14 @@ func TestSelectMaxCases(t *testing.T) {
_, _, _ = Select(sCases)
}

func TestSelectNop(t *testing.T) {
// "select { default: }" should always return the default case.
chosen, _, _ := Select([]SelectCase{{Dir: SelectDefault}})
if chosen != 0 {
t.Fatalf("expected Select to return 0, but got %#v", chosen)
}
}

func BenchmarkSelect(b *testing.B) {
channel := make(chan int)
close(channel)
Expand Down
Loading

0 comments on commit fe23ba4

Please sign in to comment.