Currently, code like:
select {
case c1 <- v1: f1()
case v2 = <-c2: f2()
case v3, ok = <-c3: f3()
default: f4()
}
gets rewritten by cmd/compile into:
var sel struct {
tcase, ncase uint16
pollorder, lockorder *uint8
scase [4]runtime.scase
lockorderarr, pollorderarr [4]uint8
}
runtime.newselect(&sel, unsafe.Sizeof(sel), 4)
if runtime.selectsend(&sel, c1, &v1) { f1(); goto after }
if runtime.selectrecv(&sel, c2, &v2) { f2(); goto after }
if runtime.selectrecv2(&sel, c3, &v3, &ok) { f3(); goto after }
if runtime.selectdefault(&sel) { f4(); goto after }
runtime.selectgo(&sel)
after:
The select{send,recv,recv2,default} functions always return false the first time they're called, but internally they save the caller's PC into &sel. runtime.selectgo never returns; instead it waits for a channel operation that can succeed, and then returns to the appropriate PC, behaving as though the function call returned twice.
(To make a C analogy, select{send,recv,recv2,default} are like setjmp, and selectgo is like longjmp.)
This proposal is to instead compile it as (something like):
var sel = struct{...}{tcase: 4, scase: [4]runtime.scase{
{elem: &v1, chan: c1, kind: runtime.caseSend},
{elem: &v2, chan: c2, kind: runtime.caseRecv},
{elem: &v3, chan: c3, kind: runtime.caseRecv, receivedp: &ok},
{kind: runtime.caseDefault},
}}
switch runtime.select(&sel) {
case 0: f1()
case 1: f2()
case 2: f3()
case 3: f4()
default: undef
}
Pros:
-
The returns-twice and returns-never logic complicates the CFG. For example, the liveness analysis pass needs to traverse these implicit edges by recognizing runtime.selectfoo function calls. It has also caused bugs in SSA optimizations (for example https://go-review.googlesource.com/#/c/37376/).
-
gccgo already does this according to @ianlancetaylor
-
I wouldn't be surprised if the compiler is able to more efficiently initialize the select data structure as a straight forward composite literal, than as a bunch of runtime calls.
-
We can eliminate a few fields. For example, hselect.ncase and scase.{pc,so}. Potentially more simplifications.
Cons:
- Currently switch statements are compiled into binary searches, whereas the current select implementation is able to directly jump to the appropriate destination PC. We could optimize switch statements to use jump tables though, at least for the special case of lowered select statements.
@ianlancetaylor @randall77 @rsc @aclements
Currently, code like:
gets rewritten by cmd/compile into:
The select{send,recv,recv2,default} functions always return false the first time they're called, but internally they save the caller's PC into &sel. runtime.selectgo never returns; instead it waits for a channel operation that can succeed, and then returns to the appropriate PC, behaving as though the function call returned twice.
(To make a C analogy, select{send,recv,recv2,default} are like setjmp, and selectgo is like longjmp.)
This proposal is to instead compile it as (something like):
Pros:
The returns-twice and returns-never logic complicates the CFG. For example, the liveness analysis pass needs to traverse these implicit edges by recognizing runtime.selectfoo function calls. It has also caused bugs in SSA optimizations (for example https://go-review.googlesource.com/#/c/37376/).
gccgo already does this according to @ianlancetaylor
I wouldn't be surprised if the compiler is able to more efficiently initialize the select data structure as a straight forward composite literal, than as a bunch of runtime calls.
We can eliminate a few fields. For example, hselect.ncase and scase.{pc,so}. Potentially more simplifications.
Cons:
@ianlancetaylor @randall77 @rsc @aclements