cmd/compile, runtime: simplify multiway select implementation

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>
This commit is contained in:
Matthew Dempsky 2017-03-01 15:50:57 -08:00
parent 5ed952368e
commit c310c688ff
17 changed files with 125 additions and 303 deletions

View file

@ -100,11 +100,10 @@ var runtimeDecls = [...]struct {
{"selectnbrecv", funcTag, 82}, {"selectnbrecv", funcTag, 82},
{"selectnbrecv2", funcTag, 84}, {"selectnbrecv2", funcTag, 84},
{"newselect", funcTag, 85}, {"newselect", funcTag, 85},
{"selectsend", funcTag, 81}, {"selectsend", funcTag, 74},
{"selectrecv", funcTag, 72}, {"selectrecv", funcTag, 86},
{"selectrecv2", funcTag, 86}, {"selectdefault", funcTag, 56},
{"selectdefault", funcTag, 87}, {"selectgo", funcTag, 87},
{"selectgo", funcTag, 56},
{"block", funcTag, 5}, {"block", funcTag, 5},
{"makeslice", funcTag, 89}, {"makeslice", funcTag, 89},
{"makeslice64", funcTag, 90}, {"makeslice64", funcTag, 90},
@ -227,8 +226,8 @@ func runtimeTypes() []*Type {
typs[83] = typPtr(typs[11]) 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[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[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[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[11])}) typs[87] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[32])})
typs[88] = typSlice(typs[2]) typs[88] = typSlice(typs[2])
typs[89] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[32])}, []*Node{anonfield(typs[88])}) 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])}) typs[90] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[88])})

View file

@ -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 selectnbrecv2(chanType *byte, elem *any, received *bool, hchan <-chan any) bool
func newselect(sel *byte, selsize int64, size int32) func newselect(sel *byte, selsize int64, size int32)
func selectsend(sel *byte, hchan chan<- any, elem *any) (selected bool) func selectsend(sel *byte, hchan chan<- any, elem *any)
func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool) func selectrecv(sel *byte, hchan <-chan any, elem *any, received *bool)
func selectrecv2(sel *byte, hchan <-chan any, elem *any, received *bool) (selected bool) func selectdefault(sel *byte)
func selectdefault(sel *byte) (selected bool) func selectgo(sel *byte) int
func selectgo(sel *byte)
func block() func block()
func makeslice(typ *byte, len int, cap int) (ary []any) func makeslice(typ *byte, len int, cap int) (ary []any)

View file

@ -306,49 +306,6 @@ func iscall(prog *obj.Prog, name *obj.LSym) bool {
return name == prog.To.Sym 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 var isdeferreturn_sym *obj.LSym
func isdeferreturn(prog *obj.Prog) bool { func isdeferreturn(prog *obj.Prog) bool {
@ -358,52 +315,6 @@ func isdeferreturn(prog *obj.Prog) bool {
return iscall(prog, isdeferreturn_sym) 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 // Constructs a control flow graph from a sequence of instructions. This
// procedure is complicated by various sources of implicit control flow that are // procedure is complicated by various sources of implicit control flow that are
// not accounted for using the standard cfg construction algorithm. Returns a // not accounted for using the standard cfg construction algorithm. Returns a
@ -418,10 +329,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
p.Opt = nil 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 // Loop through all instructions identifying branch targets
// and fall-throughs and allocate basic blocks. // and fall-throughs and allocate basic blocks.
var cfg []*BasicBlock var cfg []*BasicBlock
@ -442,12 +349,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
p.Link.Opt = newblock(p.Link) p.Link.Opt = newblock(p.Link)
cfg = append(cfg, p.Link.Opt.(*BasicBlock)) 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))
}
} }
} }
@ -468,11 +369,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
// generate any unreachable RET instructions. // generate any unreachable RET instructions.
break break
} }
// Collect basic blocks with selectgo calls.
if isselectgocall(p) {
selectgo = append(selectgo, bb)
}
} }
if bb.last.To.Type == obj.TYPE_BRANCH { if bb.last.To.Type == obj.TYPE_BRANCH {
@ -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 // Find a depth-first order and assign a depth-first number to
// all basic blocks. // all basic blocks.
for _, bb := range cfg { for _, bb := range cfg {

View file

@ -101,6 +101,7 @@ func walkselect(sel *Node) {
var n *Node var n *Node
var var_ *Node var var_ *Node
var selv *Node var selv *Node
var chosen *Node
if i == 0 { if i == 0 {
sel.Nbody.Set1(mkcall("block", nil, nil)) sel.Nbody.Set1(mkcall("block", nil, nil))
goto out goto out
@ -165,6 +166,7 @@ func walkselect(sel *Node) {
} }
l = append(l, cas.Nbody.Slice()...) l = append(l, cas.Nbody.Slice()...)
l = append(l, nod(OBREAK, nil, nil))
sel.Nbody.Set(l) sel.Nbody.Set(l)
goto out goto out
} }
@ -220,24 +222,21 @@ func walkselect(sel *Node) {
default: default:
Fatalf("select %v", n.Op) Fatalf("select %v", n.Op)
// if selectnbsend(c, v) { body } else { default body }
case OSEND: case OSEND:
// if selectnbsend(c, v) { body } else { default body }
ch := n.Left ch := n.Left
r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), ch, n.Right) 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: case OSELRECV:
// if c != nil && selectnbrecv(&v, c) { body } else { default body }
r = nod(OIF, nil, nil) r = nod(OIF, nil, nil)
r.Ninit.Set(cas.Ninit.Slice()) r.Ninit.Set(cas.Ninit.Slice())
ch := n.Right.Left ch := n.Right.Left
r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), n.Left, ch) 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: case OSELRECV2:
// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
r = nod(OIF, nil, nil) r = nod(OIF, nil, nil)
r.Ninit.Set(cas.Ninit.Slice()) r.Ninit.Set(cas.Ninit.Slice())
ch := n.Right.Left 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) r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), n.Left, n.List.First(), ch)
@ -246,7 +245,7 @@ func walkselect(sel *Node) {
r.Left = typecheck(r.Left, Erv) r.Left = typecheck(r.Left, Erv)
r.Nbody.Set(cas.Nbody.Slice()) r.Nbody.Set(cas.Nbody.Slice())
r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.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 goto out
} }
@ -255,7 +254,6 @@ func walkselect(sel *Node) {
// generate sel-struct // generate sel-struct
setlineno(sel) setlineno(sel)
selv = temp(selecttype(int32(sel.Xoffset))) selv = temp(selecttype(int32(sel.Xoffset)))
r = nod(OAS, selv, nil) r = nod(OAS, selv, nil)
r = typecheck(r, Etop) r = typecheck(r, Etop)
@ -264,52 +262,62 @@ func walkselect(sel *Node) {
r = mkcall("newselect", nil, nil, var_, nodintconst(selv.Type.Width), nodintconst(sel.Xoffset)) r = mkcall("newselect", nil, nil, var_, nodintconst(selv.Type.Width), nodintconst(sel.Xoffset))
r = typecheck(r, Etop) r = typecheck(r, Etop)
init = append(init, r) init = append(init, r)
// register cases // register cases
for _, cas := range sel.List.Slice() { for _, cas := range sel.List.Slice() {
setlineno(cas) setlineno(cas)
n = cas.Left
r = nod(OIF, nil, nil)
r.Ninit.Set(cas.Ninit.Slice())
cas.Ninit.Set(nil)
if n != nil {
r.Ninit.AppendNodes(&n.Ninit)
n.Ninit.Set(nil)
}
if n == nil { init = append(init, cas.Ninit.Slice()...)
// selectdefault(sel *byte); cas.Ninit.Set(nil)
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 { switch n.Op {
default: default:
Fatalf("select %v", n.Op) Fatalf("select %v", n.Op)
// selectsend(sel *byte, hchan *chan any, elem *any) (selected bool);
case OSEND: case OSEND:
r.Left = mkcall1(chanfn("selectsend", 2, n.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Left, n.Right) // selectsend(sel *byte, hchan *chan any, elem *any)
x = mkcall1(chanfn("selectsend", 2, n.Left.Type), nil, nil, var_, n.Left, n.Right)
// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
case OSELRECV: case OSELRECV:
r.Left = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Right.Left, n.Left) // 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())
// selectrecv2(sel *byte, hchan *chan any, elem *any, received *bool) (selected bool);
case OSELRECV2: 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. init = append(init, x)
r.Nbody.Append(nod(OVARKILL, selv, nil)) }
// 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.AppendNodes(&cas.Nbody)
r.Nbody.Append(nod(OBREAK, nil, nil)) r.Nbody.Append(nod(OBREAK, nil, nil))
init = append(init, r) init = append(init, r)
} }
// run the select
setlineno(sel)
init = append(init, mkcall("selectgo", nil, nil, var_))
sel.Nbody.Set(init) sel.Nbody.Set(init)
out: out:
@ -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("chan")), typenod(ptrto(Types[TUINT8]))))
scase.List.Append(nod(ODCLFIELD, newname(lookup("pc")), typenod(Types[TUINTPTR]))) 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("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("receivedp")), typenod(ptrto(Types[TUINT8]))))
scase.List.Append(nod(ODCLFIELD, newname(lookup("releasetime")), typenod(Types[TUINT64]))) scase.List.Append(nod(ODCLFIELD, newname(lookup("releasetime")), typenod(Types[TUINT64])))
scase = typecheck(scase, Etype) scase = typecheck(scase, Etype)

View file

@ -527,7 +527,7 @@ func (s *state) stmt(n *Node) {
s.call(n, callNormal) s.call(n, callNormal)
if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC { if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC {
if fn := n.Left.Sym.Name; compiling_runtime && fn == "throw" || 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() m := s.mem()
b := s.endBlock() b := s.endBlock()
b.Kind = ssa.BlockExit b.Kind = ssa.BlockExit
@ -921,12 +921,13 @@ func (s *state) stmt(n *Node) {
lab.breakTarget = nil lab.breakTarget = nil
} }
// OSWITCH never falls through (s.curBlock == nil here). // walk adds explicit OBREAK nodes to the end of all reachable code paths.
// OSELECT does not fall through if we're calling selectgo. // If we still have a current block here, then mark it unreachable.
// OSELECT does fall through if we're calling selectnb{send,recv}[2]. if s.curBlock != nil {
// In those latter cases, go to the code after the select. m := s.mem()
if b := s.endBlock(); b != nil { b := s.endBlock()
b.AddEdgeTo(bEnd) b.Kind = ssa.BlockExit
b.SetControl(m)
} }
s.startBlock(bEnd) s.startBlock(bEnd)

View file

@ -799,12 +799,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$4-8
MOVL AX, ret+4(FP) MOVL AX, ret+4(FP)
RET 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 // func cputicks() int64
TEXT runtime·cputicks(SB),NOSPLIT,$0-8 TEXT runtime·cputicks(SB),NOSPLIT,$0-8
TESTL $0x4000000, runtime·cpuid_edx(SB) // no sse2, no mfence TESTL $0x4000000, runtime·cpuid_edx(SB) // no sse2, no mfence

View file

@ -822,12 +822,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-16
MOVQ AX, ret+8(FP) MOVQ AX, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-16
MOVQ argp+0(FP),AX // addr of first arg
MOVQ pc+8(FP), BX
MOVQ BX, -8(AX) // set calling pc
RET
// func cputicks() int64 // func cputicks() int64
TEXT runtime·cputicks(SB),NOSPLIT,$0-0 TEXT runtime·cputicks(SB),NOSPLIT,$0-0
CMPB runtime·lfenceBeforeRdtsc(SB), $1 CMPB runtime·lfenceBeforeRdtsc(SB), $1

View file

@ -507,12 +507,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-12
MOVL AX, ret+8(FP) MOVL AX, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-8
MOVL argp+0(FP),AX // addr of first arg
MOVL pc+4(FP), BX // pc to set
MOVQ BX, -8(AX) // set calling pc
RET
// int64 runtime·cputicks(void) // int64 runtime·cputicks(void)
TEXT runtime·cputicks(SB),NOSPLIT,$0-0 TEXT runtime·cputicks(SB),NOSPLIT,$0-0
RDTSC RDTSC

View file

@ -682,11 +682,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$4-8
MOVW R0, ret+4(FP) MOVW R0, ret+4(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$4-8
MOVW pc+4(FP), R0
MOVW R0, 8(R13) // set LR in caller
RET
TEXT runtime·emptyfunc(SB),0,$0-0 TEXT runtime·emptyfunc(SB),0,$0-0
RET RET

View file

@ -709,11 +709,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-16
MOVD R0, ret+8(FP) MOVD R0, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-16
MOVD pc+8(FP), R0
MOVD R0, 16(RSP) // set LR in caller
RET
TEXT runtime·abort(SB),NOSPLIT,$-8-0 TEXT runtime·abort(SB),NOSPLIT,$-8-0
B (ZR) B (ZR)
UNDEF UNDEF

View file

@ -621,11 +621,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-16
MOVV R1, ret+8(FP) MOVV R1, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-16
MOVV pc+8(FP), R1
MOVV R1, 16(R29) // set LR in caller
RET
TEXT runtime·abort(SB),NOSPLIT,$-8-0 TEXT runtime·abort(SB),NOSPLIT,$-8-0
MOVW (R0), R0 MOVW (R0), R0
UNDEF UNDEF

View file

@ -624,11 +624,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$4-8
MOVW R1, ret+4(FP) MOVW R1, ret+4(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$4-8
MOVW pc+4(FP), R1
MOVW R1, 8(R29) // set LR in caller
RET
TEXT runtime·abort(SB),NOSPLIT,$0-0 TEXT runtime·abort(SB),NOSPLIT,$0-0
UNDEF UNDEF

View file

@ -719,11 +719,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-16
MOVD R3, ret+8(FP) MOVD R3, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-16
MOVD pc+8(FP), R3
MOVD R3, FIXED_FRAME+8(R1) // set LR in caller
RET
TEXT runtime·abort(SB),NOSPLIT|NOFRAME,$0-0 TEXT runtime·abort(SB),NOSPLIT|NOFRAME,$0-0
MOVW (R0), R0 MOVW (R0), R0
UNDEF UNDEF

View file

@ -661,11 +661,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$8-16
MOVD R3, ret+8(FP) MOVD R3, ret+8(FP)
RET RET
TEXT runtime·setcallerpc(SB),NOSPLIT,$8-16
MOVD pc+8(FP), R3
MOVD R3, 16(R15) // set LR in caller
RET
TEXT runtime·abort(SB),NOSPLIT|NOFRAME,$0-0 TEXT runtime·abort(SB),NOSPLIT|NOFRAME,$0-0
MOVW (R0), R0 MOVW (R0), R0
UNDEF UNDEF

View file

@ -11,11 +11,12 @@ import (
"unsafe" "unsafe"
) )
const ( const debugSelect = false
debugSelect = false
const (
// scase.kind // scase.kind
caseRecv = iota caseNil = iota
caseRecv
caseSend caseSend
caseDefault caseDefault
) )
@ -37,10 +38,9 @@ type hselect struct {
type scase struct { type scase struct {
elem unsafe.Pointer // data element elem unsafe.Pointer // data element
c *hchan // chan c *hchan // chan
pc uintptr // return pc pc uintptr // return pc (for race detector / msan)
kind uint16 kind uint16
so uint16 // vararg of selected bool receivedp *bool // pointer to received bool, if any
receivedp *bool // pointer to received bool (recv2)
releasetime int64 releasetime int64
} }
@ -72,92 +72,63 @@ func newselect(sel *hselect, selsize int64, size int32) {
} }
} }
//go:nosplit func selectsend(sel *hselect, c *hchan, elem unsafe.Pointer) {
func selectsend(sel *hselect, c *hchan, elem unsafe.Pointer) (selected bool) { pc := getcallerpc(unsafe.Pointer(&sel))
// nil cases do not compete
if c != nil {
selectsendImpl(sel, c, getcallerpc(unsafe.Pointer(&sel)), elem, uintptr(unsafe.Pointer(&selected))-uintptr(unsafe.Pointer(&sel)))
}
return
}
// cut in half to give stack a chance to split
func selectsendImpl(sel *hselect, c *hchan, pc uintptr, elem unsafe.Pointer, so uintptr) {
i := sel.ncase i := sel.ncase
if i >= sel.tcase { if i >= sel.tcase {
throw("selectsend: too many cases") throw("selectsend: too many cases")
} }
sel.ncase = i + 1 sel.ncase = i + 1
if c == nil {
return
}
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0]))) cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = pc cas.pc = pc
cas.c = c cas.c = c
cas.so = uint16(so)
cas.kind = caseSend cas.kind = caseSend
cas.elem = elem cas.elem = elem
if debugSelect { if debugSelect {
print("selectsend s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, " so=", cas.so, "\n") print("selectsend s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, "\n")
} }
} }
//go:nosplit func selectrecv(sel *hselect, c *hchan, elem unsafe.Pointer, received *bool) {
func selectrecv(sel *hselect, c *hchan, elem unsafe.Pointer) (selected bool) { pc := getcallerpc(unsafe.Pointer(&sel))
// nil cases do not compete
if c != nil {
selectrecvImpl(sel, c, getcallerpc(unsafe.Pointer(&sel)), elem, nil, uintptr(unsafe.Pointer(&selected))-uintptr(unsafe.Pointer(&sel)))
}
return
}
//go:nosplit
func selectrecv2(sel *hselect, c *hchan, elem unsafe.Pointer, received *bool) (selected bool) {
// nil cases do not compete
if c != nil {
selectrecvImpl(sel, c, getcallerpc(unsafe.Pointer(&sel)), elem, received, uintptr(unsafe.Pointer(&selected))-uintptr(unsafe.Pointer(&sel)))
}
return
}
func selectrecvImpl(sel *hselect, c *hchan, pc uintptr, elem unsafe.Pointer, received *bool, so uintptr) {
i := sel.ncase i := sel.ncase
if i >= sel.tcase { if i >= sel.tcase {
throw("selectrecv: too many cases") throw("selectrecv: too many cases")
} }
sel.ncase = i + 1 sel.ncase = i + 1
if c == nil {
return
}
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0]))) cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = pc cas.pc = pc
cas.c = c cas.c = c
cas.so = uint16(so)
cas.kind = caseRecv cas.kind = caseRecv
cas.elem = elem cas.elem = elem
cas.receivedp = received cas.receivedp = received
if debugSelect { if debugSelect {
print("selectrecv s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, " so=", cas.so, "\n") print("selectrecv s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, "\n")
} }
} }
//go:nosplit func selectdefault(sel *hselect) {
func selectdefault(sel *hselect) (selected bool) { pc := getcallerpc(unsafe.Pointer(&sel))
selectdefaultImpl(sel, getcallerpc(unsafe.Pointer(&sel)), uintptr(unsafe.Pointer(&selected))-uintptr(unsafe.Pointer(&sel)))
return
}
func selectdefaultImpl(sel *hselect, callerpc uintptr, so uintptr) {
i := sel.ncase i := sel.ncase
if i >= sel.tcase { if i >= sel.tcase {
throw("selectdefault: too many cases") throw("selectdefault: too many cases")
} }
sel.ncase = i + 1 sel.ncase = i + 1
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0]))) cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = callerpc cas.pc = pc
cas.c = nil cas.c = nil
cas.so = uint16(so)
cas.kind = caseDefault cas.kind = caseDefault
if debugSelect { if debugSelect {
print("selectdefault s=", sel, " pc=", hex(cas.pc), " so=", cas.so, "\n") print("selectdefault s=", sel, " pc=", hex(cas.pc), "\n")
} }
} }
@ -181,14 +152,11 @@ func selunlock(scases []scase, lockorder []uint16) {
// the G that calls select runnable again and schedules it for execution. // the G that calls select runnable again and schedules it for execution.
// When the G runs on another M, it locks all the locks and frees sel. // When the G runs on another M, it locks all the locks and frees sel.
// Now if the first M touches sel, it will access freed memory. // Now if the first M touches sel, it will access freed memory.
n := len(scases) for i := len(scases) - 1; i >= 0; i-- {
r := 0
// skip the default case
if n > 0 && scases[lockorder[0]].c == nil {
r = 1
}
for i := n - 1; i >= r; i-- {
c := scases[lockorder[i]].c c := scases[lockorder[i]].c
if c == nil {
break
}
if i > 0 && c == scases[lockorder[i-1]].c { if i > 0 && c == scases[lockorder[i-1]].c {
continue // will unlock it on the next iteration continue // will unlock it on the next iteration
} }
@ -229,23 +197,21 @@ func block() {
// *sel is on the current goroutine's stack (regardless of any // *sel is on the current goroutine's stack (regardless of any
// escaping in selectgo). // escaping in selectgo).
// //
// selectgo does not return. Instead, it overwrites its return PC and // selectgo returns the index of the chosen scase, which matches the
// returns directly to the triggered select case. Because of this, it // ordinal position of its respective select{recv,send,default} call.
// cannot appear at the top of a split stack.
//
//go:nosplit //go:nosplit
func selectgo(sel *hselect) { func selectgo(sel *hselect) int {
pc, offset := selectgoImpl(sel) return selectgoImpl(sel)
*(*bool)(add(unsafe.Pointer(&sel), uintptr(offset))) = true
setcallerpc(unsafe.Pointer(&sel), pc)
} }
// selectgoImpl returns scase.pc and scase.so for the select // Separate function to keep runtime/trace.TestTraceSymbolize happy.
// case which fired. func selectgoImpl(sel *hselect) int {
func selectgoImpl(sel *hselect) (uintptr, uint16) {
if debugSelect { if debugSelect {
print("select: sel=", sel, "\n") print("select: sel=", sel, "\n")
} }
if sel.ncase != sel.tcase {
throw("selectgo: case count mismatch")
}
scaseslice := slice{unsafe.Pointer(&sel.scase), int(sel.ncase), int(sel.ncase)} scaseslice := slice{unsafe.Pointer(&sel.scase), int(sel.ncase), int(sel.ncase)}
scases := *(*[]scase)(unsafe.Pointer(&scaseslice)) scases := *(*[]scase)(unsafe.Pointer(&scaseslice))
@ -338,13 +304,19 @@ func selectgoImpl(sel *hselect) (uintptr, uint16) {
loop: loop:
// pass 1 - look for something already waiting // pass 1 - look for something already waiting
var dfli int
var dfl *scase var dfl *scase
var casi int
var cas *scase var cas *scase
for i := 0; i < int(sel.ncase); i++ { for i := 0; i < int(sel.ncase); i++ {
cas = &scases[pollorder[i]] casi = int(pollorder[i])
cas = &scases[casi]
c = cas.c c = cas.c
switch cas.kind { switch cas.kind {
case caseNil:
continue
case caseRecv: case caseRecv:
sg = c.sendq.dequeue() sg = c.sendq.dequeue()
if sg != nil { if sg != nil {
@ -373,12 +345,14 @@ loop:
} }
case caseDefault: case caseDefault:
dfli = casi
dfl = cas dfl = cas
} }
} }
if dfl != nil { if dfl != nil {
selunlock(scases, lockorder) selunlock(scases, lockorder)
casi = dfli
cas = dfl cas = dfl
goto retc goto retc
} }
@ -391,7 +365,11 @@ loop:
} }
nextp = &gp.waiting nextp = &gp.waiting
for _, casei := range lockorder { for _, casei := range lockorder {
cas = &scases[casei] casi = int(casei)
cas = &scases[casi]
if cas.kind == caseNil {
continue
}
c = cas.c c = cas.c
sg := acquireSudog() sg := acquireSudog()
sg.g = gp sg.g = gp
@ -485,6 +463,7 @@ loop:
// otherwise they stack up on quiet channels // otherwise they stack up on quiet channels
// record the successful case, if any. // record the successful case, if any.
// We singly-linked up the SudoGs in lock order. // We singly-linked up the SudoGs in lock order.
casi = -1
cas = nil cas = nil
sglist = gp.waiting sglist = gp.waiting
// Clear all elem before unlinking from gp.waiting. // Clear all elem before unlinking from gp.waiting.
@ -497,11 +476,15 @@ loop:
for _, casei := range lockorder { for _, casei := range lockorder {
k = &scases[casei] k = &scases[casei]
if k.kind == caseNil {
continue
}
if sglist.releasetime > 0 { if sglist.releasetime > 0 {
k.releasetime = sglist.releasetime k.releasetime = sglist.releasetime
} }
if sg == sglist { if sg == sglist {
// sg has already been dequeued by the G that woke us up. // sg has already been dequeued by the G that woke us up.
casi = int(casei)
cas = k cas = k
} else { } else {
c = k.c c = k.c
@ -650,7 +633,7 @@ retc:
if cas.releasetime > 0 { if cas.releasetime > 0 {
blockevent(cas.releasetime-t0, 2) blockevent(cas.releasetime-t0, 2)
} }
return cas.pc, cas.so return casi
sclose: sclose:
// send on closed channel // send on closed channel
@ -694,22 +677,15 @@ func reflect_rselect(cases []runtimeSelect) (chosen int, recvOK bool) {
rc := &cases[i] rc := &cases[i]
switch rc.dir { switch rc.dir {
case selectDefault: case selectDefault:
selectdefaultImpl(sel, uintptr(i), 0) selectdefault(sel)
case selectSend: case selectSend:
if rc.ch == nil { selectsend(sel, rc.ch, rc.val)
break
}
selectsendImpl(sel, rc.ch, uintptr(i), rc.val, 0)
case selectRecv: case selectRecv:
if rc.ch == nil { selectrecv(sel, rc.ch, rc.val, r)
break
}
selectrecvImpl(sel, rc.ch, uintptr(i), rc.val, r, 0)
} }
} }
pc, _ := selectgoImpl(sel) chosen = selectgo(sel)
chosen = int(pc)
recvOK = *r recvOK = *r
return return
} }

View file

@ -192,9 +192,6 @@ func cgocallback_gofunc(fv uintptr, frame uintptr, framesize, ctxt uintptr)
// data dependency ordering. // data dependency ordering.
func publicationBarrier() func publicationBarrier()
//go:noescape
func setcallerpc(argp unsafe.Pointer, pc uintptr)
// getcallerpc returns the program counter (PC) of its caller's caller. // getcallerpc returns the program counter (PC) of its caller's caller.
// getcallersp returns the stack pointer (SP) of its caller's caller. // getcallersp returns the stack pointer (SP) of its caller's caller.
// For both, the argp must be a pointer to the caller's first function argument. // For both, the argp must be a pointer to the caller's first function argument.

View file

@ -589,14 +589,14 @@ func f38(b bool) {
// we care that the println lines have no live variables // we care that the println lines have no live variables
// and therefore no output. // and therefore no output.
if b { if b {
select { // ERROR "live at call to newselect: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to selectgo: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" select { // ERROR "live at call to newselect:( .autotmp_[0-9]+)+$" "live at call to selectgo:( .autotmp_[0-9]+)+$"
case <-fc38(): // ERROR "live at call to selectrecv: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" case <-fc38(): // ERROR "live at call to selectrecv:( .autotmp_[0-9]+)+$"
printnl() printnl()
case fc38() <- *fi38(1): // ERROR "live at call to fc38: .autotmp_[0-9]+$" "live at call to fi38: .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to selectsend: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" case fc38() <- *fi38(1): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" "live at call to selectsend:( .autotmp_[0-9]+)+$"
printnl() printnl()
case *fi38(2) = <-fc38(): // ERROR "live at call to fc38: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to fi38: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to selectrecv: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" case *fi38(2) = <-fc38(): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" "live at call to selectrecv:( .autotmp_[0-9]+)+$"
printnl() printnl()
case *fi38(3), *fb38() = <-fc38(): // ERROR "live at call to fb38: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to fc38: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to fi38: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to selectrecv2: .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+ .autotmp_[0-9]+$" case *fi38(3), *fb38() = <-fc38(): // ERROR "live at call to fb38:( .autotmp_[0-9]+)+$" "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" "live at call to selectrecv:( .autotmp_[0-9]+)+$"
printnl() printnl()
} }
printnl() printnl()