mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 20:13:40 +00:00
compiler: allow larger-than-int values to be sent across a channel
Instead of storing the value to send/receive in the coroutine promise, store only a pointer in the promise. This simplifies the code a lot and allows larger value sizes to be sent across a channel. Unfortunately, this new system has a code size impact. For example, compiling testdata/channel.go for the BBC micro:bit, there is an increase in code size from 4776 bytes to 4856 bytes. However, the improved flexibility and simplicity of the code should be worth it. If this becomes an issue, we can always refactor the code at a later time.
This commit is contained in:
committed by
Ron Evans
parent
46d5ea8cf6
commit
9a54ee4241
+14
-23
@@ -39,33 +39,27 @@ const (
|
||||
chanStateClosed
|
||||
)
|
||||
|
||||
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
|
||||
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
|
||||
func deadlockStub()
|
||||
|
||||
// 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) {
|
||||
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
return
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateEmpty:
|
||||
sender.promise().ptr = value
|
||||
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
|
||||
memcpy(receiverPromise.ptr, value, size)
|
||||
receiverPromise.data = 1 // commaOk = true
|
||||
ch.blocked = receiverPromise.next
|
||||
receiverPromise.next = nil
|
||||
activateTask(receiver)
|
||||
@@ -76,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
||||
case chanStateClosed:
|
||||
runtimePanic("send on closed channel")
|
||||
case chanStateSend:
|
||||
sender.promise().ptr = value
|
||||
sender.promise().next = ch.blocked
|
||||
ch.blocked = sender
|
||||
}
|
||||
@@ -85,11 +80,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
||||
// 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) {
|
||||
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
return
|
||||
@@ -97,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
||||
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
|
||||
memcpy(value, senderPromise.ptr, size)
|
||||
receiver.promise().data = 1 // commaOk = true
|
||||
ch.blocked = senderPromise.next
|
||||
senderPromise.next = nil
|
||||
activateTask(receiver)
|
||||
@@ -109,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
case chanStateEmpty:
|
||||
receiver.promise().ptr = value
|
||||
ch.state = chanStateRecv
|
||||
ch.blocked = receiver
|
||||
case chanStateClosed:
|
||||
receiverPromise := receiver.promise()
|
||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
||||
receiverPromise.commaOk = false
|
||||
memzero(value, size)
|
||||
receiver.promise().data = 0 // commaOk = false
|
||||
activateTask(receiver)
|
||||
case chanStateRecv:
|
||||
receiver.promise().ptr = value
|
||||
receiver.promise().next = ch.blocked
|
||||
ch.blocked = receiver
|
||||
}
|
||||
@@ -142,8 +133,8 @@ func chanClose(ch *channel, size uintptr) {
|
||||
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
|
||||
memzero(receiverPromise.ptr, size)
|
||||
receiverPromise.data = 0 // commaOk = false
|
||||
activateTask(ch.blocked)
|
||||
ch.state = chanStateClosed
|
||||
ch.blocked = nil
|
||||
|
||||
@@ -49,13 +49,17 @@ func (t *coroutine) promise() *taskState {
|
||||
|
||||
func makeGoroutine(*uint8) *uint8
|
||||
|
||||
// Compiler stub to get the current goroutine. Calls to this function are
|
||||
// removed in the goroutine lowering pass.
|
||||
func getCoroutine() *coroutine
|
||||
|
||||
// State/promise of a task. Internally represented as:
|
||||
//
|
||||
// {i8* next, i1 commaOk, i32/i64 data}
|
||||
type taskState struct {
|
||||
next *coroutine
|
||||
commaOk bool // 'comma-ok' flag for channel receive operation
|
||||
data uint
|
||||
next *coroutine
|
||||
ptr unsafe.Pointer
|
||||
data uint
|
||||
}
|
||||
|
||||
// Queues used by the scheduler.
|
||||
@@ -107,12 +111,22 @@ func activateTask(task *coroutine) {
|
||||
runqueuePushBack(task)
|
||||
}
|
||||
|
||||
func setTaskData(task *coroutine, value unsafe.Pointer) {
|
||||
task.promise().data = uint(uintptr(value))
|
||||
// getTaskPromisePtr is a helper function to set the current .ptr field of a
|
||||
// coroutine promise.
|
||||
func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) {
|
||||
task.promise().ptr = value
|
||||
}
|
||||
|
||||
func getTaskData(task *coroutine) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(task.promise().data))
|
||||
// getTaskPromisePtr is a helper function to get the current .ptr field from a
|
||||
// coroutine promise.
|
||||
func getTaskPromisePtr(task *coroutine) unsafe.Pointer {
|
||||
return task.promise().ptr
|
||||
}
|
||||
|
||||
// getTaskPromiseData is a helper function to get the current .data field of a
|
||||
// coroutine promise.
|
||||
func getTaskPromiseData(task *coroutine) uint {
|
||||
return task.promise().data
|
||||
}
|
||||
|
||||
// Add this task to the end of the run queue. May also destroy the task if it's
|
||||
|
||||
Reference in New Issue
Block a user