github.com/aykevl/tinygo@v0.5.0/src/runtime/scheduler.go (about) 1 package runtime 2 3 // This file implements the Go scheduler using coroutines. 4 // A goroutine contains a whole stack. A coroutine is just a single function. 5 // How do we use coroutines for goroutines, then? 6 // * Every function that contains a blocking call (like sleep) is marked 7 // blocking, and all it's parents (callers) are marked blocking as well 8 // transitively until the root (main.main or a go statement). 9 // * A blocking function that calls a non-blocking function is called as 10 // usual. 11 // * A blocking function that calls a blocking function passes its own 12 // coroutine handle as a parameter to the subroutine. When the subroutine 13 // returns, it will re-insert the parent into the scheduler. 14 // Note that a goroutine is generally called a 'task' for brevity and because 15 // that's the more common term among RTOSes. But a goroutine and a task are 16 // basically the same thing. Although, the code often uses the word 'task' to 17 // refer to both a coroutine and a goroutine, as most of the scheduler doesn't 18 // care about the difference. 19 // 20 // For more background on coroutines in LLVM: 21 // https://llvm.org/docs/Coroutines.html 22 23 import ( 24 "unsafe" 25 ) 26 27 const schedulerDebug = false 28 29 // A coroutine instance, wrapped here to provide some type safety. The value 30 // must not be used directly, it is meant to be used as an opaque *i8 in LLVM. 31 type coroutine uint8 32 33 //go:export llvm.coro.resume 34 func (t *coroutine) resume() 35 36 //go:export llvm.coro.destroy 37 func (t *coroutine) destroy() 38 39 //go:export llvm.coro.done 40 func (t *coroutine) done() bool 41 42 //go:export llvm.coro.promise 43 func (t *coroutine) _promise(alignment int32, from bool) unsafe.Pointer 44 45 // Get the promise belonging to a task. 46 func (t *coroutine) promise() *taskState { 47 return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false)) 48 } 49 50 func makeGoroutine(*uint8) *uint8 51 52 // State/promise of a task. Internally represented as: 53 // 54 // {i8* next, i1 commaOk, i32/i64 data} 55 type taskState struct { 56 next *coroutine 57 commaOk bool // 'comma-ok' flag for channel receive operation 58 data uint 59 } 60 61 // Queues used by the scheduler. 62 // 63 // TODO: runqueueFront can be removed by making the run queue a circular linked 64 // list. The runqueueBack will simply refer to the front in the 'next' pointer. 65 var ( 66 runqueueFront *coroutine 67 runqueueBack *coroutine 68 sleepQueue *coroutine 69 sleepQueueBaseTime timeUnit 70 ) 71 72 // Simple logging, for debugging. 73 func scheduleLog(msg string) { 74 if schedulerDebug { 75 println(msg) 76 } 77 } 78 79 // Simple logging with a task pointer, for debugging. 80 func scheduleLogTask(msg string, t *coroutine) { 81 if schedulerDebug { 82 println(msg, t) 83 } 84 } 85 86 // Set the task to sleep for a given time. 87 // 88 // This is a compiler intrinsic. 89 func sleepTask(caller *coroutine, duration int64) { 90 if schedulerDebug { 91 println(" set sleep:", caller, uint(duration/tickMicros)) 92 } 93 promise := caller.promise() 94 promise.data = uint(duration / tickMicros) // TODO: longer durations 95 addSleepTask(caller) 96 } 97 98 // Add a non-queued task to the run queue. 99 // 100 // This is a compiler intrinsic, and is called from a callee to reactivate the 101 // caller. 102 func activateTask(task *coroutine) { 103 if task == nil { 104 return 105 } 106 scheduleLogTask(" set runnable:", task) 107 runqueuePushBack(task) 108 } 109 110 // Add this task to the end of the run queue. May also destroy the task if it's 111 // done. 112 func runqueuePushBack(t *coroutine) { 113 if t.done() { 114 scheduleLogTask(" destroy task:", t) 115 t.destroy() 116 return 117 } 118 if schedulerDebug { 119 if t.promise().next != nil { 120 panic("runtime: runqueuePushBack: expected next task to be nil") 121 } 122 } 123 if runqueueBack == nil { // empty runqueue 124 scheduleLogTask(" add to runqueue front:", t) 125 runqueueBack = t 126 runqueueFront = t 127 } else { 128 scheduleLogTask(" add to runqueue back:", t) 129 lastTaskPromise := runqueueBack.promise() 130 lastTaskPromise.next = t 131 runqueueBack = t 132 } 133 } 134 135 // Get a task from the front of the run queue. Returns nil if there is none. 136 func runqueuePopFront() *coroutine { 137 t := runqueueFront 138 if t == nil { 139 return nil 140 } 141 if schedulerDebug { 142 println(" runqueuePopFront:", t) 143 } 144 promise := t.promise() 145 runqueueFront = promise.next 146 if runqueueFront == nil { 147 // Runqueue is empty now. 148 runqueueBack = nil 149 } 150 promise.next = nil 151 return t 152 } 153 154 // Add this task to the sleep queue, assuming its state is set to sleeping. 155 func addSleepTask(t *coroutine) { 156 if schedulerDebug { 157 if t.promise().next != nil { 158 panic("runtime: addSleepTask: expected next task to be nil") 159 } 160 } 161 now := ticks() 162 if sleepQueue == nil { 163 scheduleLog(" -> sleep new queue") 164 // Create new linked list for the sleep queue. 165 sleepQueue = t 166 sleepQueueBaseTime = now 167 return 168 } 169 170 // Make sure promise.data is relative to the queue time base. 171 promise := t.promise() 172 173 // Insert at front of sleep queue. 174 if promise.data < sleepQueue.promise().data { 175 scheduleLog(" -> sleep at start") 176 sleepQueue.promise().data -= promise.data 177 promise.next = sleepQueue 178 sleepQueue = t 179 return 180 } 181 182 // Add to sleep queue (in the middle or at the end). 183 queueIndex := sleepQueue 184 for { 185 promise.data -= queueIndex.promise().data 186 if queueIndex.promise().next == nil || queueIndex.promise().data > promise.data { 187 if queueIndex.promise().next == nil { 188 scheduleLog(" -> sleep at end") 189 promise.next = nil 190 } else { 191 scheduleLog(" -> sleep in middle") 192 promise.next = queueIndex.promise().next 193 promise.next.promise().data -= promise.data 194 } 195 queueIndex.promise().next = t 196 break 197 } 198 queueIndex = queueIndex.promise().next 199 } 200 } 201 202 // Run the scheduler until all tasks have finished. 203 func scheduler() { 204 // Main scheduler loop. 205 for { 206 scheduleLog("\n schedule") 207 now := ticks() 208 209 // Add tasks that are done sleeping to the end of the runqueue so they 210 // will be executed soon. 211 if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.promise().data) { 212 t := sleepQueue 213 scheduleLogTask(" awake:", t) 214 promise := t.promise() 215 sleepQueueBaseTime += timeUnit(promise.data) 216 sleepQueue = promise.next 217 promise.next = nil 218 runqueuePushBack(t) 219 } 220 221 t := runqueuePopFront() 222 if t == nil { 223 if sleepQueue == nil { 224 // No more tasks to execute. 225 // It would be nice if we could detect deadlocks here, because 226 // there might still be functions waiting on each other in a 227 // deadlock. 228 scheduleLog(" no tasks left!") 229 return 230 } 231 timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime) 232 if schedulerDebug { 233 println(" sleeping...", sleepQueue, uint(timeLeft)) 234 } 235 sleepTicks(timeUnit(timeLeft)) 236 if asyncScheduler { 237 // The sleepTicks function above only sets a timeout at which 238 // point the scheduler will be called again. It does not really 239 // sleep. 240 break 241 } 242 continue 243 } 244 245 // Run the given task. 246 scheduleLog(" <- runqueuePopFront") 247 scheduleLogTask(" run:", t) 248 t.resume() 249 } 250 }