Skip to content

Commit 3501131

Browse files
committed
compiler,runtime: implement non-blocking selects
Blocking selects are much more complicated, so let's do non-blocking ones first.
1 parent 8287404 commit 3501131

File tree

5 files changed

+295
-25
lines changed

5 files changed

+295
-25
lines changed

compiler/channel.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,154 @@ func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
8585
ch := c.getValue(frame, param)
8686
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
8787
}
88+
89+
// emitSelect emits all IR necessary for a select statements. That's a
90+
// non-trivial amount of code because select is very complex to implement.
91+
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
92+
if len(expr.States) == 0 {
93+
// Shortcuts for some simple selects.
94+
llvmType := c.getLLVMType(expr.Type())
95+
if expr.Blocking {
96+
// Blocks forever:
97+
// select {}
98+
c.createRuntimeCall("deadlockStub", nil, "")
99+
return llvm.Undef(llvmType)
100+
} else {
101+
// No-op:
102+
// select {
103+
// default:
104+
// }
105+
retval := llvm.Undef(llvmType)
106+
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
107+
return retval // {-1, false}
108+
}
109+
}
110+
111+
// This code create a (stack-allocated) slice containing all the select
112+
// cases and then calls runtime.chanSelect to perform the actual select
113+
// statement.
114+
// Simple selects (blocking and with just one case) are already transformed
115+
// into regular chan operations during SSA construction so we don't have to
116+
// optimize such small selects.
117+
118+
// Go through all the cases. Create the selectStates slice and and
119+
// determine the receive buffer size and alignment.
120+
recvbufSize := uint64(0)
121+
recvbufAlign := 0
122+
hasReceives := false
123+
var selectStates []llvm.Value
124+
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
125+
for _, state := range expr.States {
126+
ch := c.getValue(frame, state.Chan)
127+
selectState := c.getZeroValue(chanSelectStateType)
128+
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
129+
switch state.Dir {
130+
case types.RecvOnly:
131+
// Make sure the receive buffer is big enough and has the correct alignment.
132+
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
133+
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
134+
recvbufSize = size
135+
}
136+
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
137+
recvbufAlign = align
138+
}
139+
hasReceives = true
140+
case types.SendOnly:
141+
// Store this value in an alloca and put a pointer to this alloca
142+
// in the send state.
143+
sendValue := c.getValue(frame, state.Send)
144+
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
145+
c.builder.CreateStore(sendValue, alloca)
146+
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
147+
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
148+
default:
149+
panic("unreachable")
150+
}
151+
selectStates = append(selectStates, selectState)
152+
}
153+
154+
// Create a receive buffer, where the received value will be stored.
155+
recvbuf := llvm.Undef(c.i8ptrType)
156+
if hasReceives {
157+
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
158+
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
159+
recvbufAlloca.SetAlignment(recvbufAlign)
160+
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
161+
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
162+
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
163+
}, "select.recvbuf")
164+
}
165+
166+
// Create the states slice (allocated on the stack).
167+
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
168+
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
169+
for i, state := range selectStates {
170+
// Set each slice element to the appropriate channel.
171+
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
172+
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
173+
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
174+
}, "")
175+
c.builder.CreateStore(state, gep)
176+
}
177+
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
178+
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
179+
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
180+
}, "select.states")
181+
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
182+
183+
// Convert the 'blocking' flag on this select into a LLVM value.
184+
blockingInt := uint64(0)
185+
if expr.Blocking {
186+
blockingInt = 1
187+
}
188+
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
189+
190+
// Do the select in the runtime.
191+
results := c.createRuntimeCall("chanSelect", []llvm.Value{
192+
recvbuf,
193+
statesPtr, statesLen, statesLen, // []chanSelectState
194+
blockingValue,
195+
}, "")
196+
197+
// The result value does not include all the possible received values,
198+
// because we can't load them in advance. Instead, the *ssa.Extract
199+
// instruction will treat a *ssa.Select specially and load it there inline.
200+
// Store the receive alloca in a sidetable until we hit this extract
201+
// instruction.
202+
if frame.selectRecvBuf == nil {
203+
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
204+
}
205+
frame.selectRecvBuf[expr] = recvbuf
206+
207+
return results
208+
}
209+
210+
// getChanSelectResult returns the special values from a *ssa.Extract expression
211+
// when extracting a value from a select statement (*ssa.Select). Because
212+
// *ssa.Select cannot load all values in advance, it does this later in the
213+
// *ssa.Extract expression.
214+
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
215+
if expr.Index == 0 {
216+
// index
217+
value := c.getValue(frame, expr.Tuple)
218+
index := c.builder.CreateExtractValue(value, expr.Index, "")
219+
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
220+
index = c.builder.CreateSExt(index, c.intType, "")
221+
}
222+
return index
223+
} else if expr.Index == 1 {
224+
// comma-ok
225+
value := c.getValue(frame, expr.Tuple)
226+
return c.builder.CreateExtractValue(value, expr.Index, "")
227+
} else {
228+
// Select statements are (index, ok, ...) where ... is a number of
229+
// received values, depending on how many receive statements there
230+
// are. They are all combined into one alloca (because only one
231+
// receive can proceed at a time) so we'll get that alloca, bitcast
232+
// it to the correct type, and dereference it.
233+
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
234+
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
235+
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
236+
return c.builder.CreateLoad(ptr, "")
237+
}
238+
}

compiler/compiler.go

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type Frame struct {
8484
deferFuncs map[*ir.Function]int
8585
deferInvokeFuncs map[string]int
8686
deferClosureFuncs map[*ir.Function]int
87+
selectRecvBuf map[*ssa.Select]llvm.Value
8788
}
8889

8990
type Phi struct {
@@ -1445,9 +1446,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
14451446
x := c.getValue(frame, expr.X)
14461447
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
14471448
case *ssa.Extract:
1449+
if _, ok := expr.Tuple.(*ssa.Select); ok {
1450+
return c.getChanSelectResult(frame, expr), nil
1451+
}
14481452
value := c.getValue(frame, expr.Tuple)
1449-
result := c.builder.CreateExtractValue(value, expr.Index, "")
1450-
return result, nil
1453+
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
14511454
case *ssa.Field:
14521455
value := c.getValue(frame, expr.X)
14531456
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
@@ -1696,25 +1699,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
16961699
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
16971700
return it, nil
16981701
case *ssa.Select:
1699-
if len(expr.States) == 0 {
1700-
// Shortcuts for some simple selects.
1701-
llvmType := c.getLLVMType(expr.Type())
1702-
if expr.Blocking {
1703-
// Blocks forever:
1704-
// select {}
1705-
c.createRuntimeCall("deadlockStub", nil, "")
1706-
return llvm.Undef(llvmType), nil
1707-
} else {
1708-
// No-op:
1709-
// select {
1710-
// default:
1711-
// }
1712-
retval := llvm.Undef(llvmType)
1713-
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
1714-
return retval, nil // {-1, false}
1715-
}
1716-
}
1717-
return llvm.Undef(c.getLLVMType(expr.Type())), c.makeError(expr.Pos(), "unimplemented: "+expr.String())
1702+
return c.emitSelect(frame, expr), nil
17181703
case *ssa.Slice:
17191704
if expr.Max != nil {
17201705
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())

src/runtime/chan.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,27 @@ import (
2929

3030
type channel struct {
3131
elementSize uint16 // the size of one value in this channel
32-
state uint8
32+
state chanState
3333
blocked *coroutine
3434
}
3535

36+
type chanState uint8
37+
3638
const (
37-
chanStateEmpty = iota
39+
chanStateEmpty chanState = iota
3840
chanStateRecv
3941
chanStateSend
4042
chanStateClosed
4143
)
4244

45+
// chanSelectState is a single channel operation (send/recv) in a select
46+
// statement. The value pointer is either nil (for receives) or points to the
47+
// value to send (for sends).
48+
type chanSelectState struct {
49+
ch *channel
50+
value unsafe.Pointer
51+
}
52+
4353
func deadlockStub()
4454

4555
// chanSend sends a single value over the channel. If this operation can
@@ -144,3 +154,65 @@ func chanClose(ch *channel) {
144154
ch.state = chanStateClosed
145155
}
146156
}
157+
158+
// chanSelect is the runtime implementation of the select statement. This is
159+
// perhaps the most complicated statement in the Go spec. It returns the
160+
// selected index and the 'comma-ok' value.
161+
//
162+
// TODO: do this in a round-robin fashin (as specified in the Go spec) instead
163+
// of picking the first one that can proceed.
164+
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool) (uintptr, bool) {
165+
// See whether we can receive from one of the channels.
166+
for i, state := range states {
167+
if state.ch == nil {
168+
// A nil channel blocks forever, so don't consider it here.
169+
continue
170+
}
171+
if state.value == nil {
172+
// A receive operation.
173+
switch state.ch.state {
174+
case chanStateSend:
175+
println(" state send")
176+
// We can receive immediately.
177+
sender := state.ch.blocked
178+
senderPromise := sender.promise()
179+
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
180+
state.ch.blocked = senderPromise.next
181+
senderPromise.next = nil
182+
activateTask(sender)
183+
if state.ch.blocked == nil {
184+
state.ch.state = chanStateEmpty
185+
}
186+
return uintptr(i), true // commaOk = true
187+
case chanStateClosed:
188+
println(" state closed")
189+
// Receive the zero value.
190+
memzero(recvbuf, uintptr(state.ch.elementSize))
191+
return uintptr(i), false // commaOk = false
192+
}
193+
} else {
194+
// A send operation: state.value is not nil.
195+
switch state.ch.state {
196+
case chanStateRecv:
197+
receiver := state.ch.blocked
198+
receiverPromise := receiver.promise()
199+
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
200+
receiverPromise.data = 1 // commaOk = true
201+
state.ch.blocked = receiverPromise.next
202+
receiverPromise.next = nil
203+
activateTask(receiver)
204+
if state.ch.blocked == nil {
205+
state.ch.state = chanStateEmpty
206+
}
207+
return uintptr(i), false
208+
case chanStateClosed:
209+
runtimePanic("send on closed channel")
210+
}
211+
}
212+
}
213+
214+
if !blocking {
215+
return ^uintptr(0), false
216+
}
217+
panic("unimplemented: blocking select")
218+
}

testdata/channel.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,63 @@ func main() {
4848
}
4949
println("sum(100):", sum)
5050

51-
// Test select
51+
// Test simple selects.
5252
go selectDeadlock()
5353
go selectNoOp()
5454

55+
// Test select with a single send operation (transformed into chan send).
56+
ch = make(chan int)
57+
go fastreceiver(ch)
58+
select {
59+
case ch <- 5:
60+
println("select one sent")
61+
}
62+
close(ch)
63+
64+
// Test select with a single recv operation (transformed into chan recv).
65+
select {
66+
case n := <-ch:
67+
println("select one n:", n)
68+
}
69+
70+
// Test select recv with channel that has one entry.
71+
ch = make(chan int)
72+
go func(ch chan int) {
73+
ch <- 55
74+
}(ch)
75+
time.Sleep(time.Millisecond)
76+
select {
77+
case make(chan int) <- 3:
78+
println("unreachable")
79+
case n := <-ch:
80+
println("select n from chan:", n)
81+
case n := <-make(chan int):
82+
println("unreachable:", n)
83+
}
84+
85+
// Test select recv with closed channel.
86+
close(ch)
87+
select {
88+
case make(chan int) <- 3:
89+
println("unreachable")
90+
case n := <-ch:
91+
println("select n from closed chan:", n)
92+
case n := <-make(chan int):
93+
println("unreachable:", n)
94+
}
95+
96+
// Test select send.
97+
ch = make(chan int)
98+
go fastreceiver(ch)
99+
time.Sleep(time.Millisecond)
100+
select {
101+
case ch <- 235:
102+
println("select send")
103+
case n := <-make(chan int):
104+
println("unreachable:", n)
105+
}
106+
close(ch)
107+
55108
// Allow goroutines to exit.
56109
time.Sleep(time.Microsecond)
57110
}
@@ -68,7 +121,7 @@ func sender(ch chan int) {
68121
}
69122

70123
func sendComplex(ch chan complex128) {
71-
ch <- 7+10.5i
124+
ch <- 7 + 10.5i
72125
}
73126

74127
func fastsender(ch chan int) {

testdata/channel.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ sum(100): 4950
2323
deadlocking
2424
select no-op
2525
after no-op
26+
select one sent
27+
sum: 5
28+
select one n: 0
29+
state send
30+
select n from chan: 55
31+
state closed
32+
select n from closed chan: 0
33+
select send
34+
sum: 235

0 commit comments

Comments
 (0)