Skip to content

Commit c310c68

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 5ed9523 commit c310c68

17 files changed

+124
-302
lines changed

src/cmd/compile/internal/gc/builtin.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,10 @@ var runtimeDecls = [...]struct {
100100
{"selectnbrecv", funcTag, 82},
101101
{"selectnbrecv2", funcTag, 84},
102102
{"newselect", funcTag, 85},
103-
{"selectsend", funcTag, 81},
104-
{"selectrecv", funcTag, 72},
105-
{"selectrecv2", funcTag, 86},
106-
{"selectdefault", funcTag, 87},
107-
{"selectgo", funcTag, 56},
103+
{"selectsend", funcTag, 74},
104+
{"selectrecv", funcTag, 86},
105+
{"selectdefault", funcTag, 56},
106+
{"selectgo", funcTag, 87},
108107
{"block", funcTag, 5},
109108
{"makeslice", funcTag, 89},
110109
{"makeslice64", funcTag, 90},
@@ -227,8 +226,8 @@ func runtimeTypes() []*Type {
227226
typs[83] = typPtr(typs[11])
228227
typs[84] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[83]), anonfield(typs[70])}, []*Node{anonfield(typs[11])})
229228
typs[85] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[8])}, nil)
230-
typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[70]), anonfield(typs[3]), anonfield(typs[83])}, []*Node{anonfield(typs[11])})
231-
typs[87] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[11])})
229+
typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[70]), anonfield(typs[3]), anonfield(typs[83])}, nil)
230+
typs[87] = functype(nil, []*Node{anonfield(typs[1])}, []*Node{anonfield(typs[32])})
232231
typs[88] = typSlice(typs[2])
233232
typs[89] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[32])}, []*Node{anonfield(typs[88])})
234233
typs[90] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[88])})

src/cmd/compile/internal/gc/builtin/runtime.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,10 @@ func selectnbrecv(chanType *byte, elem *any, hchan <-chan any) bool
134134
func selectnbrecv2(chanType *byte, elem *any, received *bool, hchan <-chan any) bool
135135

136136
func newselect(sel *byte, selsize int64, size int32)
137-
func selectsend(sel *byte, hchan chan<- any, elem *any) (selected bool)
138-
func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool)
139-
func selectrecv2(sel *byte, hchan <-chan any, elem *any, received *bool) (selected bool)
140-
func selectdefault(sel *byte) (selected bool)
141-
func selectgo(sel *byte)
137+
func selectsend(sel *byte, hchan chan<- any, elem *any)
138+
func selectrecv(sel *byte, hchan <-chan any, elem *any, received *bool)
139+
func selectdefault(sel *byte)
140+
func selectgo(sel *byte) int
142141
func block()
143142

144143
func makeslice(typ *byte, len int, cap int) (ary []any)

src/cmd/compile/internal/gc/plive.go

-109
Original file line numberDiff line numberDiff line change
@@ -306,49 +306,6 @@ func iscall(prog *obj.Prog, name *obj.LSym) bool {
306306
return name == prog.To.Sym
307307
}
308308

309-
// Returns true for instructions that call a runtime function implementing a
310-
// select communication clause.
311-
312-
var selectNames [4]*obj.LSym
313-
314-
func isselectcommcasecall(prog *obj.Prog) bool {
315-
if selectNames[0] == nil {
316-
selectNames[0] = Linksym(Pkglookup("selectsend", Runtimepkg))
317-
selectNames[1] = Linksym(Pkglookup("selectrecv", Runtimepkg))
318-
selectNames[2] = Linksym(Pkglookup("selectrecv2", Runtimepkg))
319-
selectNames[3] = Linksym(Pkglookup("selectdefault", Runtimepkg))
320-
}
321-
322-
for _, name := range selectNames {
323-
if iscall(prog, name) {
324-
return true
325-
}
326-
}
327-
return false
328-
}
329-
330-
// Returns true for call instructions that target runtime·newselect.
331-
332-
var isnewselect_sym *obj.LSym
333-
334-
func isnewselect(prog *obj.Prog) bool {
335-
if isnewselect_sym == nil {
336-
isnewselect_sym = Linksym(Pkglookup("newselect", Runtimepkg))
337-
}
338-
return iscall(prog, isnewselect_sym)
339-
}
340-
341-
// Returns true for call instructions that target runtime·selectgo.
342-
343-
var isselectgocall_sym *obj.LSym
344-
345-
func isselectgocall(prog *obj.Prog) bool {
346-
if isselectgocall_sym == nil {
347-
isselectgocall_sym = Linksym(Pkglookup("selectgo", Runtimepkg))
348-
}
349-
return iscall(prog, isselectgocall_sym)
350-
}
351-
352309
var isdeferreturn_sym *obj.LSym
353310

354311
func isdeferreturn(prog *obj.Prog) bool {
@@ -358,52 +315,6 @@ func isdeferreturn(prog *obj.Prog) bool {
358315
return iscall(prog, isdeferreturn_sym)
359316
}
360317

361-
// Walk backwards from a runtime·selectgo call up to its immediately dominating
362-
// runtime·newselect call. Any successor nodes of communication clause nodes
363-
// are implicit successors of the runtime·selectgo call node. The goal of this
364-
// analysis is to add these missing edges to complete the control flow graph.
365-
func addselectgosucc(selectgo *BasicBlock) {
366-
pred := selectgo
367-
for {
368-
if len(pred.pred) == 0 {
369-
Fatalf("selectgo does not have a newselect")
370-
}
371-
pred = pred.pred[0]
372-
if blockany(pred, isselectcommcasecall) {
373-
// A select comm case block should have exactly one
374-
// successor.
375-
if len(pred.succ) != 1 {
376-
Fatalf("select comm case has too many successors")
377-
}
378-
succ := pred.succ[0]
379-
380-
// Its successor should have exactly two successors.
381-
// The drop through should flow to the selectgo block
382-
// and the branch should lead to the select case
383-
// statements block.
384-
if len(succ.succ) != 2 {
385-
Fatalf("select comm case successor has too many successors")
386-
}
387-
388-
// Add the block as a successor of the selectgo block.
389-
addedge(selectgo, succ)
390-
}
391-
392-
if blockany(pred, isnewselect) {
393-
// Reached the matching newselect.
394-
break
395-
}
396-
}
397-
}
398-
399-
// The entry point for the missing selectgo control flow algorithm. Takes a
400-
// slice of *BasicBlocks containing selectgo calls.
401-
func fixselectgo(selectgo []*BasicBlock) {
402-
for _, bb := range selectgo {
403-
addselectgosucc(bb)
404-
}
405-
}
406-
407318
// Constructs a control flow graph from a sequence of instructions. This
408319
// procedure is complicated by various sources of implicit control flow that are
409320
// not accounted for using the standard cfg construction algorithm. Returns a
@@ -418,10 +329,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
418329
p.Opt = nil
419330
}
420331

421-
// Allocate a slice to remember where we have seen selectgo calls.
422-
// These blocks will be revisited to add successor control flow edges.
423-
var selectgo []*BasicBlock
424-
425332
// Loop through all instructions identifying branch targets
426333
// and fall-throughs and allocate basic blocks.
427334
var cfg []*BasicBlock
@@ -442,12 +349,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
442349
p.Link.Opt = newblock(p.Link)
443350
cfg = append(cfg, p.Link.Opt.(*BasicBlock))
444351
}
445-
} else if isselectcommcasecall(p) || isselectgocall(p) {
446-
// Accommodate implicit selectgo control flow.
447-
if p.Link.Opt == nil {
448-
p.Link.Opt = newblock(p.Link)
449-
cfg = append(cfg, p.Link.Opt.(*BasicBlock))
450-
}
451352
}
452353
}
453354

@@ -468,11 +369,6 @@ func newcfg(firstp *obj.Prog) []*BasicBlock {
468369
// generate any unreachable RET instructions.
469370
break
470371
}
471-
472-
// Collect basic blocks with selectgo calls.
473-
if isselectgocall(p) {
474-
selectgo = append(selectgo, bb)
475-
}
476372
}
477373

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

505-
// Add missing successor edges to the selectgo blocks.
506-
if len(selectgo) != 0 {
507-
fixselectgo(selectgo)
508-
}
509-
510401
// Find a depth-first order and assign a depth-first number to
511402
// all basic blocks.
512403
for _, bb := range cfg {

src/cmd/compile/internal/gc/select.go

+42-35
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func walkselect(sel *Node) {
101101
var n *Node
102102
var var_ *Node
103103
var selv *Node
104+
var chosen *Node
104105
if i == 0 {
105106
sel.Nbody.Set1(mkcall("block", nil, nil))
106107
goto out
@@ -165,6 +166,7 @@ func walkselect(sel *Node) {
165166
}
166167

167168
l = append(l, cas.Nbody.Slice()...)
169+
l = append(l, nod(OBREAK, nil, nil))
168170
sel.Nbody.Set(l)
169171
goto out
170172
}
@@ -220,24 +222,21 @@ func walkselect(sel *Node) {
220222
default:
221223
Fatalf("select %v", n.Op)
222224

223-
// if selectnbsend(c, v) { body } else { default body }
224225
case OSEND:
226+
// if selectnbsend(c, v) { body } else { default body }
225227
ch := n.Left
226-
227228
r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), ch, n.Right)
228229

229-
// if c != nil && selectnbrecv(&v, c) { body } else { default body }
230230
case OSELRECV:
231+
// if c != nil && selectnbrecv(&v, c) { body } else { default body }
231232
r = nod(OIF, nil, nil)
232-
233233
r.Ninit.Set(cas.Ninit.Slice())
234234
ch := n.Right.Left
235235
r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), Types[TBOOL], &r.Ninit, typename(ch.Type), n.Left, ch)
236236

237-
// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
238237
case OSELRECV2:
238+
// if c != nil && selectnbrecv2(&v, c) { body } else { default body }
239239
r = nod(OIF, nil, nil)
240-
241240
r.Ninit.Set(cas.Ninit.Slice())
242241
ch := n.Right.Left
243242
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) {
246245
r.Left = typecheck(r.Left, Erv)
247246
r.Nbody.Set(cas.Nbody.Slice())
248247
r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
249-
sel.Nbody.Set1(r)
248+
sel.Nbody.Set2(r, nod(OBREAK, nil, nil))
250249
goto out
251250
}
252251

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

256255
// generate sel-struct
257256
setlineno(sel)
258-
259257
selv = temp(selecttype(int32(sel.Xoffset)))
260258
r = nod(OAS, selv, nil)
261259
r = typecheck(r, Etop)
@@ -264,52 +262,62 @@ func walkselect(sel *Node) {
264262
r = mkcall("newselect", nil, nil, var_, nodintconst(selv.Type.Width), nodintconst(sel.Xoffset))
265263
r = typecheck(r, Etop)
266264
init = append(init, r)
265+
267266
// register cases
268267
for _, cas := range sel.List.Slice() {
269268
setlineno(cas)
270-
n = cas.Left
271-
r = nod(OIF, nil, nil)
272-
r.Ninit.Set(cas.Ninit.Slice())
269+
270+
init = append(init, cas.Ninit.Slice()...)
273271
cas.Ninit.Set(nil)
274-
if n != nil {
275-
r.Ninit.AppendNodes(&n.Ninit)
276-
n.Ninit.Set(nil)
277-
}
278272

279-
if n == nil {
280-
// selectdefault(sel *byte);
281-
r.Left = mkcall("selectdefault", Types[TBOOL], &r.Ninit, var_)
282-
} else {
273+
var x *Node
274+
if n := cas.Left; n != nil {
275+
init = append(init, n.Ninit.Slice()...)
276+
283277
switch n.Op {
284278
default:
285279
Fatalf("select %v", n.Op)
286-
287-
// selectsend(sel *byte, hchan *chan any, elem *any) (selected bool);
288280
case OSEND:
289-
r.Left = mkcall1(chanfn("selectsend", 2, n.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Left, n.Right)
290-
291-
// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
281+
// selectsend(sel *byte, hchan *chan any, elem *any)
282+
x = mkcall1(chanfn("selectsend", 2, n.Left.Type), nil, nil, var_, n.Left, n.Right)
292283
case OSELRECV:
293-
r.Left = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Right.Left, n.Left)
294-
295-
// selectrecv2(sel *byte, hchan *chan any, elem *any, received *bool) (selected bool);
284+
// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
285+
x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, nodnil())
296286
case OSELRECV2:
297-
r.Left = mkcall1(chanfn("selectrecv2", 2, n.Right.Left.Type), Types[TBOOL], &r.Ninit, var_, n.Right.Left, n.Left, n.List.First())
287+
// selectrecv(sel *byte, hchan *chan any, elem *any, received *bool)
288+
x = mkcall1(chanfn("selectrecv", 2, n.Right.Left.Type), nil, nil, var_, n.Right.Left, n.Left, n.List.First())
298289
}
290+
} else {
291+
// selectdefault(sel *byte)
292+
x = mkcall("selectdefault", nil, nil, var_)
299293
}
300294

301-
// selv is no longer alive after use.
302-
r.Nbody.Append(nod(OVARKILL, selv, nil))
295+
init = append(init, x)
296+
}
297+
298+
// run the select
299+
setlineno(sel)
300+
chosen = temp(Types[TINT])
301+
r = nod(OAS, chosen, mkcall("selectgo", Types[TINT], nil, var_))
302+
r = typecheck(r, Etop)
303+
init = append(init, r)
304+
305+
// selv is no longer alive after selectgo.
306+
init = append(init, nod(OVARKILL, selv, nil))
307+
308+
// dispatch cases
309+
for i, cas := range sel.List.Slice() {
310+
setlineno(cas)
303311

312+
cond := nod(OEQ, chosen, nodintconst(int64(i)))
313+
cond = typecheck(cond, Erv)
314+
315+
r = nod(OIF, cond, nil)
304316
r.Nbody.AppendNodes(&cas.Nbody)
305317
r.Nbody.Append(nod(OBREAK, nil, nil))
306318
init = append(init, r)
307319
}
308320

309-
// run the select
310-
setlineno(sel)
311-
312-
init = append(init, mkcall("selectgo", nil, nil, var_))
313321
sel.Nbody.Set(init)
314322

315323
out:
@@ -328,7 +336,6 @@ func selecttype(size int32) *Type {
328336
scase.List.Append(nod(ODCLFIELD, newname(lookup("chan")), typenod(ptrto(Types[TUINT8]))))
329337
scase.List.Append(nod(ODCLFIELD, newname(lookup("pc")), typenod(Types[TUINTPTR])))
330338
scase.List.Append(nod(ODCLFIELD, newname(lookup("kind")), typenod(Types[TUINT16])))
331-
scase.List.Append(nod(ODCLFIELD, newname(lookup("so")), typenod(Types[TUINT16])))
332339
scase.List.Append(nod(ODCLFIELD, newname(lookup("receivedp")), typenod(ptrto(Types[TUINT8]))))
333340
scase.List.Append(nod(ODCLFIELD, newname(lookup("releasetime")), typenod(Types[TUINT64])))
334341
scase = typecheck(scase, Etype)

src/cmd/compile/internal/gc/ssa.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ func (s *state) stmt(n *Node) {
527527
s.call(n, callNormal)
528528
if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC {
529529
if fn := n.Left.Sym.Name; compiling_runtime && fn == "throw" ||
530-
n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "selectgo" || fn == "block") {
530+
n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block") {
531531
m := s.mem()
532532
b := s.endBlock()
533533
b.Kind = ssa.BlockExit
@@ -921,12 +921,13 @@ func (s *state) stmt(n *Node) {
921921
lab.breakTarget = nil
922922
}
923923

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

src/runtime/asm_386.s

-6
Original file line numberDiff line numberDiff line change
@@ -799,12 +799,6 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$4-8
799799
MOVL AX, ret+4(FP)
800800
RET
801801

802-
TEXT runtime·setcallerpc(SB),NOSPLIT,$4-8
803-
MOVL argp+0(FP),AX // addr of first arg
804-
MOVL pc+4(FP), BX
805-
MOVL BX, -4(AX) // set calling pc
806-
RET
807-
808802
// func cputicks() int64
809803
TEXT runtime·cputicks(SB),NOSPLIT,$0-8
810804
TESTL $0x4000000, runtime·cpuid_edx(SB) // no sse2, no mfence

0 commit comments

Comments
 (0)