runtime/scheduler: make debugging easier + rename some functions

This shouldn't affect functionality but makes debugging a whole lot
easier. A scheduler is difficult so make it easy to debug.
Этот коммит содержится в:
Ayke van Laethem 2018-09-02 19:30:13 +02:00
родитель 8ba3fef7d7
коммит 9519f989bc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED
2 изменённых файлов: 58 добавлений и 19 удалений

Просмотреть файл

@ -1086,7 +1086,7 @@ func (c *Compiler) parseFunc(frame *Frame) error {
mem := c.builder.CreateCall(c.coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.builder.CreateCall(c.freeFunc, []llvm.Value{mem}, "")
// re-insert parent coroutine
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{frame.fn.llvmFn.FirstParam()}, "")
c.builder.CreateCall(c.mod.NamedFunction("runtime.yieldToScheduler"), []llvm.Value{frame.fn.llvmFn.FirstParam()}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
@ -1182,7 +1182,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
if err != nil {
return err
}
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{handle}, "")
c.builder.CreateCall(c.mod.NamedFunction("runtime.yieldToScheduler"), []llvm.Value{handle}, "")
return nil
case *ssa.If:
cond, err := c.parseExpr(frame, instr.Cond)
@ -1483,7 +1483,7 @@ func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, con
// (with the TASK_STATE_CALL state). When the subroutine is finished, it
// will reactivate the parent (this frame) in it's destroy function.
c.builder.CreateCall(c.mod.NamedFunction("runtime.scheduleTask"), []llvm.Value{result}, "")
c.builder.CreateCall(c.mod.NamedFunction("runtime.yieldToScheduler"), []llvm.Value{result}, "")
// Set task state to TASK_STATE_CALL.
c.builder.CreateCall(c.mod.NamedFunction("runtime.waitForAsyncCall"), []llvm.Value{frame.taskHandle}, "")

Просмотреть файл

@ -25,6 +25,8 @@ 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
@ -75,18 +77,25 @@ var (
// Simple logging, for debugging.
func scheduleLog(msg string) {
//println(msg)
if schedulerDebug {
println(msg)
}
}
// Simple logging with a task pointer, for debugging.
func scheduleLogTask(msg string, t *coroutine) {
//println(msg, t)
if schedulerDebug {
println(msg, t)
}
}
// Set the task state to sleep for a given time.
//
// This is a compiler intrinsic.
func sleepTask(caller *coroutine, duration Duration) {
if schedulerDebug {
println(" set state sleep:", caller, uint32(duration))
}
promise := caller.promise()
promise.state = TASK_STATE_SLEEP
promise.data = uint32(duration) // TODO: longer durations
@ -97,6 +106,7 @@ func sleepTask(caller *coroutine, duration Duration) {
//
// This is a compiler intrinsic.
func waitForAsyncCall(caller *coroutine) {
scheduleLogTask(" set state call:", caller)
promise := caller.promise()
promise.state = TASK_STATE_CALL
}
@ -104,51 +114,70 @@ func waitForAsyncCall(caller *coroutine) {
// Add a task to the runnable or sleep queue, depending on the state.
//
// This is a compiler intrinsic.
func scheduleTask(t *coroutine) {
func yieldToScheduler(t *coroutine) {
if t == nil {
return
}
scheduleLogTask(" schedule task:", t)
// See what we should do with this task: try to execute it directly
// again or let it sleep for a bit.
promise := t.promise()
if promise.state == TASK_STATE_CALL {
scheduleLogTask(" set waiting for call:", t)
return // calling an async task, the subroutine will re-active the parent
} else if promise.state == TASK_STATE_SLEEP && promise.data != 0 {
scheduleLogTask(" set sleeping:", t)
addSleepTask(t)
} else {
pushTask(t)
scheduleLogTask(" set runnable:", t)
runqueuePushBack(t)
}
}
// Add this task to the end of the run queue. May also destroy the task if it's
// done.
func pushTask(t *coroutine) {
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 t.promise().state != TASK_STATE_RUNNABLE {
panic("runtime: runqueuePushBack: expected task state to be runnable")
}
}
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. May return nil if there is none.
func popTask() *coroutine {
// 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
}
scheduleLogTask(" popTask:", t)
if schedulerDebug {
println(" runqueuePopFront:", t)
// Sanity checking.
if t.promise().state != TASK_STATE_RUNNABLE {
panic("runtime: runqueuePopFront: task not runnable")
}
}
promise := t.promise()
runqueueFront = promise.next
if runqueueFront == nil {
// Runqueue is empty now.
runqueueBack = nil
}
promise.next = nil
@ -157,6 +186,14 @@ func popTask() *coroutine {
// 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")
}
if t.promise().state != TASK_STATE_SLEEP {
panic("runtime: addSleepTask: task not sleeping")
}
}
now := monotime()
if sleepQueue == nil {
scheduleLog(" -> sleep new queue")
@ -202,7 +239,7 @@ func addSleepTask(t *coroutine) {
// It takes an initial task (main.main) to bootstrap.
func scheduler(main *coroutine) {
// Initial task.
scheduleTask(main)
yieldToScheduler(main)
// Main scheduler loop.
for {
@ -212,18 +249,17 @@ func scheduler(main *coroutine) {
// Add tasks that are done sleeping to the end of the runqueue so they
// will be executed soon.
if sleepQueue != nil && now-sleepQueueBaseTime >= uint64(sleepQueue.promise().data) {
scheduleLog(" run <- sleep")
t := sleepQueue
scheduleLogTask(" awake:", t)
promise := t.promise()
sleepQueueBaseTime += uint64(promise.data)
sleepQueue = promise.next
promise.state = TASK_STATE_RUNNABLE
promise.next = nil
pushTask(t)
runqueuePushBack(t)
}
scheduleLog(" <- popTask")
t := popTask()
t := runqueuePopFront()
if t == nil {
if sleepQueue == nil {
// No more tasks to execute.
@ -233,17 +269,20 @@ func scheduler(main *coroutine) {
scheduleLog(" no tasks left!")
return
}
scheduleLog(" sleeping...")
timeLeft := uint64(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint32(timeLeft))
}
sleep(Duration(timeLeft))
continue
}
// Run the given task.
scheduleLog(" <- runqueuePopFront")
scheduleLogTask(" run:", t)
t.resume()
// Add the just resumed task to the run queue or the sleep queue.
scheduleTask(t)
yieldToScheduler(t)
}
}