
This is implemented as follows: * The parent coroutine allocates space for the return value in its frame and stores a pointer to this frame in the parent coroutine handle. * The child coroutine obtains the alloca from its parent using the parent coroutine handle. It then stores the result value there. * The parent value reads the data from the alloca on resumption.
258 строки
7 КиБ
Go
258 строки
7 КиБ
Go
package runtime
|
|
|
|
// This file implements the Go scheduler using coroutines.
|
|
// A goroutine contains a whole stack. A coroutine is just a single function.
|
|
// How do we use coroutines for goroutines, then?
|
|
// * Every function that contains a blocking call (like sleep) is marked
|
|
// blocking, and all it's parents (callers) are marked blocking as well
|
|
// transitively until the root (main.main or a go statement).
|
|
// * A blocking function that calls a non-blocking function is called as
|
|
// usual.
|
|
// * A blocking function that calls a blocking function passes its own
|
|
// coroutine handle as a parameter to the subroutine. When the subroutine
|
|
// returns, it will re-insert the parent into the scheduler.
|
|
// Note that a goroutine is generally called a 'task' for brevity and because
|
|
// that's the more common term among RTOSes. But a goroutine and a task are
|
|
// basically the same thing. Although, the code often uses the word 'task' to
|
|
// refer to both a coroutine and a goroutine, as most of the scheduler doesn't
|
|
// care about the difference.
|
|
//
|
|
// For more background on coroutines in LLVM:
|
|
// https://llvm.org/docs/Coroutines.html
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
|
|
const schedulerDebug = false
|
|
|
|
// A coroutine instance, wrapped here to provide some type safety. The value
|
|
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
|
|
type coroutine uint8
|
|
|
|
//go:export llvm.coro.resume
|
|
func (t *coroutine) resume()
|
|
|
|
//go:export llvm.coro.destroy
|
|
func (t *coroutine) destroy()
|
|
|
|
//go:export llvm.coro.done
|
|
func (t *coroutine) done() bool
|
|
|
|
//go:export llvm.coro.promise
|
|
func (t *coroutine) _promise(alignment int32, from bool) unsafe.Pointer
|
|
|
|
// Get the promise belonging to a task.
|
|
func (t *coroutine) promise() *taskState {
|
|
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
|
|
}
|
|
|
|
func makeGoroutine(*uint8) *uint8
|
|
|
|
// 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
|
|
}
|
|
|
|
// Queues used by the scheduler.
|
|
//
|
|
// TODO: runqueueFront can be removed by making the run queue a circular linked
|
|
// list. The runqueueBack will simply refer to the front in the 'next' pointer.
|
|
var (
|
|
runqueueFront *coroutine
|
|
runqueueBack *coroutine
|
|
sleepQueue *coroutine
|
|
sleepQueueBaseTime timeUnit
|
|
)
|
|
|
|
// Simple logging, for debugging.
|
|
func scheduleLog(msg string) {
|
|
if schedulerDebug {
|
|
println(msg)
|
|
}
|
|
}
|
|
|
|
// Simple logging with a task pointer, for debugging.
|
|
func scheduleLogTask(msg string, t *coroutine) {
|
|
if schedulerDebug {
|
|
println(msg, t)
|
|
}
|
|
}
|
|
|
|
// Set the task to sleep for a given time.
|
|
//
|
|
// This is a compiler intrinsic.
|
|
func sleepTask(caller *coroutine, duration int64) {
|
|
if schedulerDebug {
|
|
println(" set sleep:", caller, uint(duration/tickMicros))
|
|
}
|
|
promise := caller.promise()
|
|
promise.data = uint(duration / tickMicros) // TODO: longer durations
|
|
addSleepTask(caller)
|
|
}
|
|
|
|
// Add a non-queued task to the run queue.
|
|
//
|
|
// This is a compiler intrinsic, and is called from a callee to reactivate the
|
|
// caller.
|
|
func activateTask(task *coroutine) {
|
|
if task == nil {
|
|
return
|
|
}
|
|
scheduleLogTask(" set runnable:", task)
|
|
runqueuePushBack(task)
|
|
}
|
|
|
|
func setTaskData(task *coroutine, value unsafe.Pointer) {
|
|
task.promise().data = uint(uintptr(value))
|
|
}
|
|
|
|
func getTaskData(task *coroutine) unsafe.Pointer {
|
|
return unsafe.Pointer(uintptr(task.promise().data))
|
|
}
|
|
|
|
// Add this task to the end of the run queue. May also destroy the task if it's
|
|
// done.
|
|
func runqueuePushBack(t *coroutine) {
|
|
if t.done() {
|
|
scheduleLogTask(" destroy task:", t)
|
|
t.destroy()
|
|
return
|
|
}
|
|
if schedulerDebug {
|
|
if t.promise().next != nil {
|
|
panic("runtime: runqueuePushBack: expected next task to be nil")
|
|
}
|
|
}
|
|
if runqueueBack == nil { // empty runqueue
|
|
scheduleLogTask(" add to runqueue front:", t)
|
|
runqueueBack = t
|
|
runqueueFront = t
|
|
} else {
|
|
scheduleLogTask(" add to runqueue back:", t)
|
|
lastTaskPromise := runqueueBack.promise()
|
|
lastTaskPromise.next = t
|
|
runqueueBack = t
|
|
}
|
|
}
|
|
|
|
// Get a task from the front of the run queue. Returns nil if there is none.
|
|
func runqueuePopFront() *coroutine {
|
|
t := runqueueFront
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
if schedulerDebug {
|
|
println(" runqueuePopFront:", t)
|
|
}
|
|
promise := t.promise()
|
|
runqueueFront = promise.next
|
|
if runqueueFront == nil {
|
|
// Runqueue is empty now.
|
|
runqueueBack = nil
|
|
}
|
|
promise.next = nil
|
|
return t
|
|
}
|
|
|
|
// Add this task to the sleep queue, assuming its state is set to sleeping.
|
|
func addSleepTask(t *coroutine) {
|
|
if schedulerDebug {
|
|
if t.promise().next != nil {
|
|
panic("runtime: addSleepTask: expected next task to be nil")
|
|
}
|
|
}
|
|
now := ticks()
|
|
if sleepQueue == nil {
|
|
scheduleLog(" -> sleep new queue")
|
|
// Create new linked list for the sleep queue.
|
|
sleepQueue = t
|
|
sleepQueueBaseTime = now
|
|
return
|
|
}
|
|
|
|
// Make sure promise.data is relative to the queue time base.
|
|
promise := t.promise()
|
|
|
|
// Insert at front of sleep queue.
|
|
if promise.data < sleepQueue.promise().data {
|
|
scheduleLog(" -> sleep at start")
|
|
sleepQueue.promise().data -= promise.data
|
|
promise.next = sleepQueue
|
|
sleepQueue = t
|
|
return
|
|
}
|
|
|
|
// Add to sleep queue (in the middle or at the end).
|
|
queueIndex := sleepQueue
|
|
for {
|
|
promise.data -= queueIndex.promise().data
|
|
if queueIndex.promise().next == nil || queueIndex.promise().data > promise.data {
|
|
if queueIndex.promise().next == nil {
|
|
scheduleLog(" -> sleep at end")
|
|
promise.next = nil
|
|
} else {
|
|
scheduleLog(" -> sleep in middle")
|
|
promise.next = queueIndex.promise().next
|
|
promise.next.promise().data -= promise.data
|
|
}
|
|
queueIndex.promise().next = t
|
|
break
|
|
}
|
|
queueIndex = queueIndex.promise().next
|
|
}
|
|
}
|
|
|
|
// Run the scheduler until all tasks have finished.
|
|
func scheduler() {
|
|
// Main scheduler loop.
|
|
for {
|
|
scheduleLog("\n schedule")
|
|
now := ticks()
|
|
|
|
// Add tasks that are done sleeping to the end of the runqueue so they
|
|
// will be executed soon.
|
|
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.promise().data) {
|
|
t := sleepQueue
|
|
scheduleLogTask(" awake:", t)
|
|
promise := t.promise()
|
|
sleepQueueBaseTime += timeUnit(promise.data)
|
|
sleepQueue = promise.next
|
|
promise.next = nil
|
|
runqueuePushBack(t)
|
|
}
|
|
|
|
t := runqueuePopFront()
|
|
if t == nil {
|
|
if sleepQueue == nil {
|
|
// No more tasks to execute.
|
|
// It would be nice if we could detect deadlocks here, because
|
|
// there might still be functions waiting on each other in a
|
|
// deadlock.
|
|
scheduleLog(" no tasks left!")
|
|
return
|
|
}
|
|
timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
|
|
if schedulerDebug {
|
|
println(" sleeping...", sleepQueue, uint(timeLeft))
|
|
}
|
|
sleepTicks(timeUnit(timeLeft))
|
|
if asyncScheduler {
|
|
// The sleepTicks function above only sets a timeout at which
|
|
// point the scheduler will be called again. It does not really
|
|
// sleep.
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Run the given task.
|
|
scheduleLog(" <- runqueuePopFront")
|
|
scheduleLogTask(" run:", t)
|
|
t.resume()
|
|
}
|
|
}
|