mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 11:37:46 +00:00
212 lines
5.5 KiB
Go
212 lines
5.5 KiB
Go
package runtime
|
|
|
|
// This file implements the TinyGo scheduler. This scheduler is a very simple
|
|
// cooperative round robin scheduler, with a runqueue that contains a linked
|
|
// list of goroutines (tasks) that should be run next, in order of when they
|
|
// were added to the queue (first-in, first-out). It also contains a sleep queue
|
|
// with sleeping goroutines in order of when they should be re-activated.
|
|
//
|
|
// The scheduler is used both for the coroutine based scheduler and for the task
|
|
// based scheduler (see compiler/goroutine-lowering.go for a description). In
|
|
// both cases, the 'task' type is used to represent one goroutine. In the case
|
|
// of the task based scheduler, it literally is the goroutine itself: a pointer
|
|
// to the bottom of the stack where some important fields are kept. In the case
|
|
// of the coroutine-based scheduler, it is the coroutine pointer (a *i8 in
|
|
// LLVM).
|
|
|
|
import "unsafe"
|
|
|
|
const schedulerDebug = false
|
|
|
|
// State of a task. Internally represented as:
|
|
//
|
|
// {i8* next, i8* ptr, i32/i64 data}
|
|
type taskState struct {
|
|
next *task
|
|
ptr unsafe.Pointer
|
|
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 *task
|
|
runqueueBack *task
|
|
sleepQueue *task
|
|
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 *task) {
|
|
if schedulerDebug {
|
|
println("---", msg, t)
|
|
}
|
|
}
|
|
|
|
// Simple logging with a channel and task pointer.
|
|
func scheduleLogChan(msg string, ch *channel, t *task) {
|
|
if schedulerDebug {
|
|
println("---", msg, ch, t)
|
|
}
|
|
}
|
|
|
|
// Set the task to sleep for a given time.
|
|
//
|
|
// This is a compiler intrinsic.
|
|
func sleepTask(caller *task, duration int64) {
|
|
if schedulerDebug {
|
|
println(" set sleep:", caller, uint(duration/tickMicros))
|
|
}
|
|
state := caller.state()
|
|
state.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(t *task) {
|
|
if t == nil {
|
|
return
|
|
}
|
|
scheduleLogTask(" set runnable:", t)
|
|
runqueuePushBack(t)
|
|
}
|
|
|
|
// getTaskStateData is a helper function to get the current .data field of the
|
|
// goroutine state.
|
|
func getTaskStateData(t *task) uint {
|
|
return t.state().data
|
|
}
|
|
|
|
// Add this task to the end of the run queue. May also destroy the task if it's
|
|
// done.
|
|
func runqueuePushBack(t *task) {
|
|
if schedulerDebug {
|
|
if t.state().next != nil {
|
|
panic("runtime: runqueuePushBack: expected next task to be nil")
|
|
}
|
|
}
|
|
if runqueueBack == nil { // empty runqueue
|
|
runqueueBack = t
|
|
runqueueFront = t
|
|
} else {
|
|
lastTaskState := runqueueBack.state()
|
|
lastTaskState.next = t
|
|
runqueueBack = t
|
|
}
|
|
}
|
|
|
|
// Get a task from the front of the run queue. Returns nil if there is none.
|
|
func runqueuePopFront() *task {
|
|
t := runqueueFront
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
state := t.state()
|
|
runqueueFront = state.next
|
|
if runqueueFront == nil {
|
|
// Runqueue is empty now.
|
|
runqueueBack = nil
|
|
}
|
|
state.next = nil
|
|
return t
|
|
}
|
|
|
|
// Add this task to the sleep queue, assuming its state is set to sleeping.
|
|
func addSleepTask(t *task) {
|
|
if schedulerDebug {
|
|
if t.state().next != nil {
|
|
panic("runtime: addSleepTask: expected next task to be nil")
|
|
}
|
|
}
|
|
now := ticks()
|
|
if sleepQueue == nil {
|
|
scheduleLog(" -> sleep new queue")
|
|
|
|
// set new base time
|
|
sleepQueueBaseTime = now
|
|
}
|
|
|
|
// Add to sleep queue.
|
|
q := &sleepQueue
|
|
for ; *q != nil; q = &((*q).state()).next {
|
|
if t.state().data < (*q).state().data {
|
|
// this will finish earlier than the next - insert here
|
|
break
|
|
} else {
|
|
// this will finish later - adjust delay
|
|
t.state().data -= (*q).state().data
|
|
}
|
|
}
|
|
if *q != nil {
|
|
// cut delay time between this sleep task and the next
|
|
(*q).state().data -= t.state().data
|
|
}
|
|
t.state().next = *q
|
|
*q = t
|
|
}
|
|
|
|
// Run the scheduler until all tasks have finished.
|
|
func scheduler() {
|
|
// Main scheduler loop.
|
|
for {
|
|
scheduleLog("")
|
|
scheduleLog(" 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.state().data) {
|
|
t := sleepQueue
|
|
scheduleLogTask(" awake:", t)
|
|
state := t.state()
|
|
sleepQueueBaseTime += timeUnit(state.data)
|
|
sleepQueue = state.next
|
|
state.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.state().data) - (now - sleepQueueBaseTime)
|
|
if schedulerDebug {
|
|
println(" sleeping...", sleepQueue, uint(timeLeft))
|
|
for t := sleepQueue; t != nil; t = t.state().next {
|
|
println(" task sleeping:", t, timeUnit(t.state().data))
|
|
}
|
|
}
|
|
sleepTicks(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.
|
|
scheduleLogTask(" run:", t)
|
|
t.resume()
|
|
}
|
|
}
|