diff options
author | Ayke van Laethem <[email protected]> | 2019-01-13 17:05:00 +0100 |
---|---|---|
committer | Ayke van Laethem <[email protected]> | 2019-01-21 22:09:37 +0100 |
commit | 2e4dd09bbccf5dd8d3908b98887bbd2320346a85 (patch) | |
tree | 00eb010c13b1da80021dbeb3f94bdaf517c4cf9f /src/runtime/chan.go | |
parent | 602c2647490fc2352ff283da59a6b537c8e8cacc (diff) | |
download | tinygo-2e4dd09bbccf5dd8d3908b98887bbd2320346a85.tar.gz tinygo-2e4dd09bbccf5dd8d3908b98887bbd2320346a85.zip |
compiler: add support for channel operations
Support for channels is not complete. The following pieces are missing:
* Channels with values bigger than int. An int in TinyGo can always
contain at least a pointer, so pointers are okay to send.
* Buffered channels.
* The select statement.
Diffstat (limited to 'src/runtime/chan.go')
-rw-r--r-- | src/runtime/chan.go | 147 |
1 files changed, 146 insertions, 1 deletions
diff --git a/src/runtime/chan.go b/src/runtime/chan.go index abd0b1178..940861fbb 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -2,7 +2,152 @@ package runtime // This file implements the 'chan' type and send/receive/select operations. -// dummy +// A channel can be in one of the following states: +// empty: +// No goroutine is waiting on a send or receive operation. The 'blocked' +// member is nil. +// recv: +// A goroutine tries to receive from the channel. This goroutine is stored +// in the 'blocked' member. +// send: +// The reverse of send. A goroutine tries to send to the channel. This +// goroutine is stored in the 'blocked' member. +// closed: +// The channel is closed. Sends will panic, receives will get a zero value +// plus optionally the indication that the channel is zero (with the +// commao-ok value in the coroutine). +// +// A send/recv transmission is completed by copying from the data element of the +// sending coroutine to the data element of the receiving coroutine, and setting +// the 'comma-ok' value to true. +// A receive operation on a closed channel is completed by zeroing the data +// element of the receiving coroutine and setting the 'comma-ok' value to false. + +import ( + "unsafe" +) type channel struct { + state uint8 + blocked *coroutine +} + +const ( + chanStateEmpty = iota + chanStateRecv + chanStateSend + chanStateClosed +) + +func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) +func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) + +// chanSend sends a single value over the channel. If this operation can +// complete immediately (there is a goroutine waiting for a value), it sends the +// value and re-activates both goroutines. If not, it sets itself as waiting on +// a value. +// +// The unsafe.Pointer value is used during lowering. During IR generation, it +// points to the to-be-received value. During coroutine lowering, this value is +// replaced with a read from the coroutine promise. +func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) { + if ch == nil { + // A nil channel blocks forever. Do not scheduler this goroutine again. + return + } + switch ch.state { + case chanStateEmpty: + ch.state = chanStateSend + ch.blocked = sender + case chanStateRecv: + receiver := ch.blocked + receiverPromise := receiver.promise() + senderPromise := sender.promise() + memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) + receiverPromise.commaOk = true + ch.blocked = receiverPromise.next + receiverPromise.next = nil + activateTask(receiver) + activateTask(sender) + if ch.blocked == nil { + ch.state = chanStateEmpty + } + case chanStateClosed: + runtimePanic("send on closed channel") + case chanStateSend: + sender.promise().next = ch.blocked + ch.blocked = sender + } +} + +// chanRecv receives a single value over a channel. If there is an available +// sender, it receives the value immediately and re-activates both coroutines. +// If not, it sets itself as available for receiving. If the channel is closed, +// it immediately activates itself with a zero value as the result. +// +// The two unnamed values exist to help during lowering. The unsafe.Pointer +// points to the value, and the *bool points to the comma-ok value. Both are +// replaced by reads from the coroutine promise. +func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) { + if ch == nil { + // A nil channel blocks forever. Do not scheduler this goroutine again. + return + } + switch ch.state { + case chanStateSend: + sender := ch.blocked + receiverPromise := receiver.promise() + senderPromise := sender.promise() + memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) + receiverPromise.commaOk = true + ch.blocked = senderPromise.next + senderPromise.next = nil + activateTask(receiver) + activateTask(sender) + if ch.blocked == nil { + ch.state = chanStateEmpty + } + case chanStateEmpty: + ch.state = chanStateRecv + ch.blocked = receiver + case chanStateClosed: + receiverPromise := receiver.promise() + memzero(unsafe.Pointer(&receiverPromise.data), size) + receiverPromise.commaOk = false + activateTask(receiver) + case chanStateRecv: + receiver.promise().next = ch.blocked + ch.blocked = receiver + } +} + +// chanClose closes the given channel. If this channel has a receiver or is +// empty, it closes the channel. Else, it panics. +func chanClose(ch *channel, size uintptr) { + if ch == nil { + // Not allowed by the language spec. + runtimePanic("close of nil channel") + } + switch ch.state { + case chanStateClosed: + // Not allowed by the language spec. + runtimePanic("close of closed channel") + case chanStateSend: + // This panic should ideally on the sending side, not in this goroutine. + // But when a goroutine tries to send while the channel is being closed, + // that is clearly invalid: the send should have been completed already + // before the close. + runtimePanic("close channel during send") + case chanStateRecv: + // The receiver must be re-activated with a zero value. + receiverPromise := ch.blocked.promise() + memzero(unsafe.Pointer(&receiverPromise.data), size) + receiverPromise.commaOk = false + activateTask(ch.blocked) + ch.state = chanStateClosed + ch.blocked = nil + case chanStateEmpty: + // Easy case. No available sender or receiver. + ch.state = chanStateClosed + } } |