Skip to content

cmd/compile,runtime: compile multiway select statements as switch statements #19331

Closed
@mdempsky

Description

@mdempsky

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:

  1. 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/).

  2. gccgo already does this according to @ianlancetaylor

  3. 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.

  4. We can eliminate a few fields. For example, hselect.ncase and scase.{pc,so}. Potentially more simplifications.

Cons:

  1. 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

Activity

cespare

cespare commented on Feb 28, 2017

@cespare
Contributor

(Related switch-optimization issues are #5496 and #15780.)

randall77

randall77 commented on Feb 28, 2017

@randall77
Contributor

A similar return-twice pattern happens for defer. If possible we should do a similar thing for defers.

mdempsky

mdempsky commented on Feb 28, 2017

@mdempsky
ContributorAuthor

@randall77 I think cleaning up deferproc is likely worth doing for similar reasons, but I think the details are distinct enough to track in a separate proposal.

josharian

josharian commented on Mar 1, 2017

@josharian
Contributor

I like it. Most select cases are small (https://github.com/josharian/countselectcases/blob/master/README.md has some old data), so it'll be a linear not binary search, but I'd wager it'll be imperceptible, particularly since we're eliminating a linear number of function calls.

aclements

aclements commented on Mar 1, 2017

@aclements
Member

I admit that I don't fully understand the reasons why it's done the way it is today, but I support this change, too. In addition to dramatically simplifying some very strange semantics in the runtime, this would eliminate the only use of setcallerpc in the runtime, which has been a thorn in my side before.

ianlancetaylor

ianlancetaylor commented on Mar 1, 2017

@ianlancetaylor
Contributor

I believe it would eliminate the only use of setcallerpc anywhere.

griesemer

griesemer commented on Mar 1, 2017

@griesemer
Contributor

I'm all for this. The suggestion approach seems much cleaner.

added this to the Proposal milestone on Mar 1, 2017
mdempsky

mdempsky commented on Mar 1, 2017

@mdempsky
ContributorAuthor

One minor complication I discovered while prototyping this: currently we perform race instrumentation within selectgo using the caller PCs recorded by select{send,recv,default}. This shows up when case ch <- v: races with close(ch) or case v = <-ch: races with another load/store of v.

We could continue instrumenting within selectgo, but it would mean any races would point to the entire select statement, rather than the individual case.

Alternatively, we can have the compiler insert instrumentation around the selectgo call, so they can have appropriate line numbers and precise race reports. This would amount to a raceread call for each send case, and just rewriting case v = <-ch: to case vtmp := <-ch: v = vtmp (which we already do at least in some cases) so the compiler's usual instrumentation pass can handle it.

gopherbot

gopherbot commented on Mar 2, 2017

@gopherbot
Contributor

CL https://golang.org/cl/37661 mentions this issue.

mdempsky

mdempsky commented on Mar 2, 2017

@mdempsky
ContributorAuthor

CL 37661 gets rid of the setjmp/longjmp control flow, but keeps the selectfoo calls in place for the purpose of race instrumentation. Post-walk, the AST now looks like:

var sel struct { ... }
runtime.newselect(&sel, unsafe.Sizeof(sel), 4)
runtime.selectsend(&sel, c1, &v1)
runtime.selectrecv(&sel, c2, &v2, nil)
runtime.selectrecv(&sel, c3, &v3, &ok)
runtime.selectdefault(&sel)
chosen := runtime.selectgo(&sel)
if chosen == 0 { f1(); goto after }
if chosen == 1 { f2(); goto after }
if chosen == 2 { f3(); goto after }
if chosen == 3 { f4(); goto after }
undef
after:

Further simplifications are still possible.

changed the title [-]proposal: cmd/compile,runtime: compile multiway select statements as switch statements[/-] [+]cmd/compile,runtime: compile multiway select statements as switch statements[/+] on Mar 6, 2017
added this to the Go1.9Early milestone on Mar 6, 2017

10 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @bradfitz@mdempsky@josharian@rsc@cespare

        Issue actions

          cmd/compile,runtime: compile multiway select statements as switch statements · Issue #19331 · golang/go