aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/runtime/chan.go
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-01-13 17:05:00 +0100
committerAyke van Laethem <[email protected]>2019-01-21 22:09:37 +0100
commit2e4dd09bbccf5dd8d3908b98887bbd2320346a85 (patch)
tree00eb010c13b1da80021dbeb3f94bdaf517c4cf9f /src/runtime/chan.go
parent602c2647490fc2352ff283da59a6b537c8e8cacc (diff)
downloadtinygo-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.go147
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
+ }
}