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  }