Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions compiler/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,108 @@ func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "")
}

// createNonBlockingSelect emits IR for a non-blocking select with one channel case.
func (b *builder) createNonBlockingSelect(expr *ssa.Select) llvm.Value {
state := expr.States[0]
ch := b.getValue(state.Chan, state.Pos)

resultType := b.ctx.StructType([]llvm.Type{
b.ctx.Int32Type(),
b.ctx.Int1Type(),
}, false)

var selected llvm.Value
selectOk := llvm.ConstInt(b.ctx.Int1Type(), 1, false)

switch state.Dir {
case types.SendOnly:
sendValue := b.getValue(state.Send, state.Pos)
valueType := b.getLLVMType(state.Send.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0

valuePtr := llvm.ConstNull(b.dataPtrType)
var valueAlloca, valueAllocaSize llvm.Value

if !isZeroSize {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(
valueType,
"select.send.value",
)
b.CreateStore(sendValue, valueAlloca)
valuePtr = valueAlloca
}

selected = b.createRuntimeCall(
"chanTrySend",
[]llvm.Value{ch, valuePtr},
"select.sent",
)

if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}

case types.RecvOnly:
valueType := b.getLLVMType(
state.Chan.Type().Underlying().(*types.Chan).Elem(),
)
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0

// getChanSelectResult loads the received value later.
recvbuf := llvm.Undef(b.dataPtrType)
runtimeRecvbuf := llvm.ConstNull(b.dataPtrType)

if !isZeroSize {
recvbuf, _ = b.createTemporaryAlloca(
valueType,
"select.recvbuf",
)
runtimeRecvbuf = recvbuf
}
results := b.createRuntimeCall(
"chanTryRecv",
[]llvm.Value{ch, runtimeRecvbuf},
"select.recv",
)

selected = b.CreateExtractValue(results, 0, "select.received")
recvOk := b.CreateExtractValue(results, 1, "select.recv.ok")
selectOk = b.CreateSelect(
selected,
recvOk,
llvm.ConstInt(b.ctx.Int1Type(), 1, false),
"select.ok",
)

if b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
b.selectRecvBuf[expr] = recvbuf

default:
panic("unreachable")
}

selectedIndex := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
defaultIndex := llvm.ConstInt(
b.ctx.Int32Type(),
math.MaxUint32,
false,
)

selectIndex := b.CreateSelect(
selected,
selectedIndex,
defaultIndex,
"select.index",
)

result := llvm.Undef(resultType)
result = b.CreateInsertValue(result, selectIndex, 0, "")
result = b.CreateInsertValue(result, selectOk, 1, "")
return result
}

// createSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
Expand All @@ -102,6 +204,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
}

if !expr.Blocking && len(expr.States) == 1 {
return b.createNonBlockingSelect(expr)
}

const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
Expand Down
45 changes: 45 additions & 0 deletions compiler/testdata/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,48 @@ func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
default:
}
}

func selectNonBlockingSend(ch chan int, value int) bool {
select {
case ch <- value:
return true
default:
return false
}
}

func selectNonBlockingRecv(ch chan int) (int, bool, bool) {
select {
case value, ok := <-ch:
return value, ok, true
default:
return 0, false, false
}
}

func selectNonBlockingZeroSend(ch chan struct{}) bool {
select {
case ch <- struct{}{}:
return true
default:
return false
}
}

func selectNonBlockingZeroRecv(ch chan struct{}) (bool, bool) {
select {
case _, ok := <-ch:
return ok, true
default:
return false, false
}
}

func selectBlocking(ch1, ch2 chan int) (int, bool) {
select {
case value, ok := <-ch1:
return value, ok
case value, ok := <-ch2:
return value, ok
}
}
Loading
Loading