mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 19:17:47 +00:00
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.
This commit is contained in:
+146
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,11 @@ func makeGoroutine(*uint8) *uint8
|
||||
|
||||
// State/promise of a task. Internally represented as:
|
||||
//
|
||||
// {i8* next, i32/i64 data}
|
||||
// {i8* next, i1 commaOk, i32/i64 data}
|
||||
type taskState struct {
|
||||
next *coroutine
|
||||
data uint
|
||||
next *coroutine
|
||||
commaOk bool // 'comma-ok' flag for channel receive operation
|
||||
data uint
|
||||
}
|
||||
|
||||
// Queues used by the scheduler.
|
||||
|
||||
Reference in New Issue
Block a user