compiler,runtime: implement stack-based scheduler

This scheduler is intended to live along the (stackless) coroutine based
scheduler which is needed for WebAssembly and unsupported platforms. The
stack based scheduler is somewhat simpler in implementation as it does
not require full program transform passes and supports things like
function pointers and interface methods out of the box with no changes.

Code size is reduced in most cases, even in the case where no scheduler
scheduler is used at all. I'm not exactly sure why but these changes
likely allowed some further optimizations somewhere. Even RAM is
slightly reduced, perhaps some global was elminated in the process as
well.
This commit is contained in:
Ayke van Laethem
2019-08-13 12:35:46 +02:00
committed by Ron Evans
parent 61f711ef26
commit 542135c357
17 changed files with 702 additions and 224 deletions
+48 -37
View File
@@ -30,7 +30,7 @@ import (
type channel struct {
elementSize uint16 // the size of one value in this channel
state chanState
blocked *coroutine
blocked *task
}
type chanState uint8
@@ -50,40 +50,44 @@ type chanSelectState struct {
value unsafe.Pointer
}
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.
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
func chanSend(sender *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateEmpty:
sender.promise().ptr = value
scheduleLogChan(" send: chan is empty ", ch, sender)
sender.state().ptr = value
ch.state = chanStateSend
ch.blocked = sender
chanYield()
case chanStateRecv:
scheduleLogChan(" send: chan in recv mode", ch, sender)
receiver := ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize))
receiverPromise.data = 1 // commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, value, uintptr(ch.elementSize))
receiverState.data = 1 // commaOk = true
ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
activateTask(sender)
reactivateParent(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateClosed:
runtimePanic("send on closed channel")
case chanStateSend:
sender.promise().ptr = value
sender.promise().next = ch.blocked
scheduleLogChan(" send: chan in send mode", ch, sender)
sender.state().ptr = value
sender.state().next = ch.blocked
ch.blocked = sender
chanYield()
}
}
@@ -91,36 +95,43 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
// 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.
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) {
func chanRecv(receiver *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateSend:
scheduleLogChan(" recv: chan in send mode", ch, receiver)
sender := ch.blocked
senderPromise := sender.promise()
memcpy(value, senderPromise.ptr, uintptr(ch.elementSize))
receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(receiver)
senderState := sender.state()
memcpy(value, senderState.ptr, uintptr(ch.elementSize))
receiver.state().data = 1 // commaOk = true
ch.blocked = senderState.next
senderState.next = nil
reactivateParent(receiver)
activateTask(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateEmpty:
receiver.promise().ptr = value
scheduleLogChan(" recv: chan is empty ", ch, receiver)
receiver.state().ptr = value
ch.state = chanStateRecv
ch.blocked = receiver
chanYield()
case chanStateClosed:
scheduleLogChan(" recv: chan is closed ", ch, receiver)
memzero(value, uintptr(ch.elementSize))
receiver.promise().data = 0 // commaOk = false
activateTask(receiver)
receiver.state().data = 0 // commaOk = false
reactivateParent(receiver)
case chanStateRecv:
receiver.promise().ptr = value
receiver.promise().next = ch.blocked
scheduleLogChan(" recv: chan in recv mode", ch, receiver)
receiver.state().ptr = value
receiver.state().next = ch.blocked
ch.blocked = receiver
chanYield()
}
}
@@ -143,9 +154,9 @@ func chanClose(ch *channel) {
runtimePanic("close channel during send")
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(receiverPromise.ptr, uintptr(ch.elementSize))
receiverPromise.data = 0 // commaOk = false
receiverState := ch.blocked.state()
memzero(receiverState.ptr, uintptr(ch.elementSize))
receiverState.data = 0 // commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
ch.blocked = nil
@@ -174,10 +185,10 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
case chanStateSend:
// We can receive immediately.
sender := state.ch.blocked
senderPromise := sender.promise()
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderPromise.next
senderPromise.next = nil
senderState := sender.state()
memcpy(recvbuf, senderState.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderState.next
senderState.next = nil
activateTask(sender)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
@@ -193,11 +204,11 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
switch state.ch.state {
case chanStateRecv:
receiver := state.ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
receiverPromise.data = 1 // commaOk = true
state.ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, state.value, uintptr(state.ch.elementSize))
receiverState.data = 1 // commaOk = true
state.ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty