Skip to content

Commit

Permalink
cmd/compile, runtime: simplify multiway select implementation
Browse files Browse the repository at this point in the history
This commit reworks multiway select statements to use normal control
flow primitives instead of the previous setjmp/longjmp-like behavior.
This simplifies liveness analysis and should prevent issues around
"returns twice" function calls within SSA passes.

test/live.go is updated because liveness analysis's CFG is more
representative of actual control flow. The case bodies are the only
real successors of the selectgo call, but previously the selectsend,
selectrecv, etc. calls were included in the successors list too.

Updates #19331.

Change-Id: I7f879b103a4b85e62fc36a270d812f54c0aa3e83
Reviewed-on: https://go-review.googlesource.com/37661
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
  • Loading branch information
mdempsky committed Mar 7, 2017
1 parent 5ed9523 commit c310c68
Show file tree
Hide file tree
Showing 17 changed files with 124 additions and 302 deletions.
13 changes: 6 additions & 7 deletions src/cmd/compile/internal/gc/builtin.go
Expand Up @@ -100,11 +100,10 @@ var runtimeDecls = [...]struct {
{"selectnbrecv", funcTag, 82},
{"selectnbrecv2", funcTag, 84},
{"newselect", funcTag, 85},
{"selectsend", funcTag, 81},
{"selectrecv", funcTag, 72},
{"selectrecv2", funcTag, 86},
{"selectdefault", funcTag, 87},
{"selectgo", funcTag, 56},
{"selectsend", funcTag, 74},
{"selectrecv", funcTag, 86},
{"selectdefault", funcTag, 56},
{"selectgo", funcTag, 87},
{"block", funcTag, 5},
{"makeslice", funcTag, 89},
{"makeslice64", funcTag, 90},
Expand Down Expand Up @@ -227,8 +226,8 @@ func runtimeTypes() []*Type {
typs[83] = typPtr(typs[11])
typs[84] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[83]), anonfield(typs[70])}, []*Node{anonfield(typs[11])})
typs[85] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[8])}, nil)
typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[70]), anonfield(typs[3]), anonfield(typs[83])}, []*Node{anonfield(typs[11])})
typs[87] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[11])})
typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[70]), anonfield(typs[3]), anonfield(typs[83])}, nil)
typs[87] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[32])})
typs[88] = typSlice(typs[2])
typs[89] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[32])}, []*Node{anonfield(typs[88])})
typs[90] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[88])})
Expand Down
9 changes: 4 additions & 5 deletions src/cmd/compile/internal/gc/builtin/runtime.go
Expand Up @@ -134,11 +134,10 @@ func selectnbrecv(chanType *byte, elem *any, hchan <-chan any) bool
func selectnbrecv2(chanType *byte, elem *any, received *bool, hchan <-chan any) bool

func newselect(sel *byte, selsize int64, size int32)
func selectsend(sel *byte, hchan chan<- any, elem *any) (selected bool)
func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool)
func selectrecv2(sel *byte, hchan <-chan any, elem *any, received *bool) (selected bool)
func selectdefault(sel *byte) (selected bool)
func selectgo(sel *byte)
func selectsend(sel *byte, hchan chan<- any, elem *any)
func selectrecv(sel *byte, hchan <-chan any, elem *any, received *bool)
func selectdefault(sel *byte)
func selectgo(sel *byte) int
func block()

func makeslice(typ *byte, len int, cap int) (ary []any)
Expand Down
109 changes: 0 additions & 109 deletions src/cmd/compile/internal/gc/plive.go
Expand Up @@ -306,49 +306,6 @@ func iscall(prog *obj.Prog, name *obj.LSym) bool {
return name == prog.To.Sym
}

// Returns true for instructions that call a runtime function implementing a
// select communication clause.

var selectNames [4]*obj.LSym

func isselectcommcasecall(prog *obj.Prog) bool {
if selectNames[0] == nil {
selectNames[0] = Linksym(Pkglookup("selectsend", Runtimepkg))
selectNames[1] = Linksym(Pkglookup("selectrecv", Runtimepkg))
selectNames[2] = Linksym(Pkglookup("selectrecv2", Runtimepkg))
selectNames[3] = Linksym(Pkglookup("selectdefault", Runtimepkg))
}

for _, name := range selectNames {
if iscall(prog, name) {
return true
}
}
return false
}

// Returns true for call instructions that target runtime·newselect.

var isnewselect_sym *obj.LSym

func isnewselect(prog *obj.Prog) bool {
if isnewselect_sym == nil {
isnewselect_sym = Linksym(Pkglookup("newselect", Runtimepkg))
}
return iscall(prog, isnewselect_sym)
}

// Returns true for call instructions that target runtime·selectgo.

var isselectgocall_sym *obj.LSym

func isselectgocall(prog *obj.Prog) bool {
if isselectgocall_sym == nil {
isselectgocall_sym = Linksym(Pkglookup("selectgo", Runtimepkg))
}
return iscall(prog, isselectgocall_sym)
}

var isdeferreturn_sym *obj.LSym

func isdeferreturn(prog *obj.Prog) bool {
Expand All @@ -358,52 +315,6 @@ func isdeferreturn(prog *obj.Prog) bool {
return iscall(prog, isdeferreturn_sym)
}

// Walk backwards from a runtime·selectgo call up to its immediately dominating
// runtime·newselect call. Any successor nodes of communication clause nodes
// are implicit successors of the runtime·selectgo call node. The goal of this
// analysis is to add these missing edges to complete the control flow graph.
func addselectgosucc(selectgo *BasicBlock) {
pred := selectgo
for {
if len(pred.pred) == 0 {
Fatalf("selectgo does not have a newselect")
}
pred = pred.pred[0]
if blockany(pred, isselectcommcasecall) {
// A select comm case block should have exactly one
// successor.
if len(pred.succ) != 1 {
Fatalf("select comm case has too many successors")
}
succ := pred.succ[0]

// Its successor should have exactly two successors.
// The drop through should flow to the selectgo block
// and the branch should lead to the select case
// statements block.
if len(succ.succ) != 2 {
Fatalf("select comm case successor has too many successors")
}

// Add the block as a successor of the selectgo block.
addedge(selectgo, succ)
}

if blockany(pred, isnewselect) {
// Reached the matching newselect.
break
}
}
}

// The entry point for the missing selectgo control flow algorithm. Takes a
// slice of *BasicBlocks containing selectgo calls.
func fixselectgo(selectgo []*BasicBlock) {
for _, bb := range selectgo {
addselectgosucc(bb)
}
}

// Constructs a control flow graph from a sequence of instructions. This
// procedure is complicated by various sources of implicit control flow that are
// not accounted for using the standard cfg construction algorithm. Returns a
Expand All @@ -418,10 +329,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
p.Opt = nil
}

// Allocate a slice to remember where we have seen selectgo calls.
// These blocks will be revisited to add successor control flow edges.
var selectgo []*BasicBlock

// Loop through all instructions identifying branch targets
// and fall-throughs and allocate basic blocks.
var cfg []*BasicBlock
Expand All @@ -442,12 +349,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
p.Link.Opt = newblock(p.Link)
cfg = append(cfg, p.Link.Opt.(*BasicBlock))
}
} else if isselectcommcasecall(p) || isselectgocall(p) {
// Accommodate implicit selectgo control flow.
if p.Link.Opt == nil {
p.Link.Opt = newblock(p.Link)
cfg = append(cfg, p.Link.Opt.(*BasicBlock))
}
}
}

Expand All @@ -468,11 +369,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
// generate any unreachable RET instructions.
break
}

// Collect basic blocks with selectgo calls.
if isselectgocall(p) {
selectgo = append(selectgo, bb)
}
}

if bb.last.To.Type == obj.TYPE_BRANCH {
Expand Down Expand Up @@ -502,11 +398,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
}
}

// Add missing successor edges to the selectgo blocks.
if len(selectgo) != 0 {
fixselectgo(selectgo)
}

// Find a depth-first order and assign a depth-first number to
// all basic blocks.
for _, bb := range cfg {
Expand Down
77 changes: 42 additions & 35 deletions src/cmd/compile/internal/gc/select.go
Expand Up @@ -101,6 +101,7 @@ func walkselect(sel *Node) {
var n *Node
var var_ *Node
var selv *Node
var chosen *Node
if i == 0 {
sel.Nbody.Set1(mkcall("block", nil, nil))
goto out
Expand Down Expand Up @@ -165,6 +166,7 @@ func walkselect(sel *Node) {
}

l = append(l, cas.Nbody.Slice()...)
l = append(l, nod(OBREAK, nil, nil))
sel.Nbody.Set(l)
goto out
}
Expand Down Expand Up @@ -220,24 +222,21 @@ func walkselect(sel *Node) {
default:
Fatalf("select %v", n.Op)

// if selectnbsend(c, v) { body } else { default body }
case OSEND:
// if selectnbsend(c, v) { body } else { default body }
ch := n.Left

r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), ch, n.Right)

// if c != nil && selectnbrecv(&v, c) { body } else { default body }
case OSELRECV:
// if c != nil && selectnbrecv(&v, c) { body } else { default body }
r = nod(OIF, nil, nil)

r.Ninit.Set(cas.Ninit.Slice())
ch := n.Right.Left
r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), n.Left, ch)

// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
case OSELRECV2:
// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
r = nod(OIF, nil, nil)

r.Ninit.Set(cas.Ninit.Slice())
ch := n.Right.Left
r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), n.Left, n.List.First(), ch)
Expand All @@ -246,7 +245,7 @@ func walkselect(sel *Node) {
r.Left = typecheck(r.Left, Erv)
r.Nbody.Set(cas.Nbody.Slice())
r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
sel.Nbody.Set1(r)
sel.Nbody.Set2(r, nod(OBREAK, nil, nil))
goto out
}

Expand All @@ -255,7 +254,6 @@ func walkselect(sel *Node) {

// generate sel-struct
setlineno(sel)

selv = temp(selecttype(int32(sel.Xoffset)))
r = nod(OAS, selv, nil)
r = typecheck(r, Etop)
Expand All @@ -264,52 +262,62 @@ func walkselect(sel *Node) {
r = mkcall("newselect", nil, nil, var_, nodintconst(selv.Type.Width), nodintconst(sel.Xoffset))
r = typecheck(r, Etop)
init = append(init, r)

// register cases
for _, cas := range sel.List.Slice() {
setlineno(cas)
n = cas.Left
r = nod(OIF, nil, nil)
r.Ninit.Set(cas.Ninit.Slice())

init = append(init, cas.Ninit.Slice()...)
cas.Ninit.Set(nil)
if n != nil {
r.Ninit.AppendNodes(&n.Ninit)
n.Ninit.Set(nil)
}

if n == nil {
// selectdefault(sel *byte);
r.Left = mkcall("selectdefault", Types[TBOOL], &r.Ninit, var_)
} else {
var x *Node
if n := cas.Left; n != nil {
init = append(init, n.Ninit.Slice()...)

switch n.Op {
default:
Fatalf("select %v", n.Op)

// selectsend(sel *byte, hchan *chan any, elem *any) (selected bool);
case OSEND:
r.Left = mkcall1(chanfn("selectsend", 2, n.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Left, n.Right)

// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
// selectsend(sel *byte, hchan *chan any, elem *any)
x = mkcall1(chanfn("selectsend", 2, n.Left.Type), nil, nil, var_, n.Left, n.Right)
case OSELRECV:
r.Left = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Right.Left, n.Left)

// selectrecv2(sel *byte, hchan *chan any, elem *any, received *bool) (selected bool);
// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, nodnil())
case OSELRECV2:
r.Left = mkcall1(chanfn("selectrecv2", 2, n.Right.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Right.Left, n.Left, n.List.First())
// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, n.List.First())
}
} else {
// selectdefault(sel *byte)
x = mkcall("selectdefault", nil, nil, var_)
}

// selv is no longer alive after use.
r.Nbody.Append(nod(OVARKILL, selv, nil))
init = append(init, x)
}

// run the select
setlineno(sel)
chosen = temp(Types[TINT])
r = nod(OAS, chosen, mkcall("selectgo", Types[TINT], nil, var_))
r = typecheck(r, Etop)
init = append(init, r)

// selv is no longer alive after selectgo.
init = append(init, nod(OVARKILL, selv, nil))

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

cond := nod(OEQ, chosen, nodintconst(int64(i)))
cond = typecheck(cond, Erv)

r = nod(OIF, cond, nil)
r.Nbody.AppendNodes(&cas.Nbody)
r.Nbody.Append(nod(OBREAK, nil, nil))
init = append(init, r)
}

// run the select
setlineno(sel)

init = append(init, mkcall("selectgo", nil, nil, var_))
sel.Nbody.Set(init)

out:
Expand All @@ -328,7 +336,6 @@ func selecttype(size int32) *Type {
scase.List.Append(nod(ODCLFIELD, newname(lookup("chan")), typenod(ptrto(Types[TUINT8]))))
scase.List.Append(nod(ODCLFIELD, newname(lookup("pc")), typenod(Types[TUINTPTR])))
scase.List.Append(nod(ODCLFIELD, newname(lookup("kind")), typenod(Types[TUINT16])))
scase.List.Append(nod(ODCLFIELD, newname(lookup("so")), typenod(Types[TUINT16])))
scase.List.Append(nod(ODCLFIELD, newname(lookup("receivedp")), typenod(ptrto(Types[TUINT8]))))
scase.List.Append(nod(ODCLFIELD, newname(lookup("releasetime")), typenod(Types[TUINT64])))
scase = typecheck(scase, Etype)
Expand Down
15 changes: 8 additions & 7 deletions src/cmd/compile/internal/gc/ssa.go
Expand Up @@ -527,7 +527,7 @@ func (s *state) stmt(n *Node) {
s.call(n, callNormal)
if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC {
if fn := n.Left.Sym.Name; compiling_runtime && fn == "throw" ||
n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "selectgo" || fn == "block") {
n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block") {
m := s.mem()
b := s.endBlock()
b.Kind = ssa.BlockExit
Expand Down Expand Up @@ -921,12 +921,13 @@ func (s *state) stmt(n *Node) {
lab.breakTarget = nil
}

// OSWITCH never falls through (s.curBlock == nil here).
// OSELECT does not fall through if we're calling selectgo.
// OSELECT does fall through if we're calling selectnb{send,recv}[2].
// In those latter cases, go to the code after the select.
if b := s.endBlock(); b != nil {
b.AddEdgeTo(bEnd)
// walk adds explicit OBREAK nodes to the end of all reachable code paths.
// If we still have a current block here, then mark it unreachable.
if s.curBlock != nil {
m := s.mem()
b := s.endBlock()
b.Kind = ssa.BlockExit
b.SetControl(m)
}
s.startBlock(bEnd)

Expand Down
6 changes: 0 additions & 6 deletions src/runtime/asm_386.s
Expand Up @@ -799,12 +799,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$4-8
MOVL AX, ret+4(FP)
RET

TEXT runtime·setcallerpc(SB),NOSPLIT,$4-8
MOVL argp+0(FP),AX // addr of first arg
MOVL pc+4(FP), BX
MOVL BX, -4(AX) // set calling pc
RET

// func cputicks() int64
TEXT runtime·cputicks(SB),NOSPLIT,$0-8
TESTL $0x4000000, runtime·cpuid_edx(SB) // no sse2, no mfence
Expand Down

0 comments on commit c310c68

Please sign in to comment.