mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 10:43:29 +00:00
compiler,runtime: implement stack-based scheduler
This scheduler is intended to live along the (stackless) coroutine based scheduler which is needed for WebAssembly and unsupported platforms. The stack based scheduler is somewhat simpler in implementation as it does not require full program transform passes and supports things like function pointers and interface methods out of the box with no changes. Code size is reduced in most cases, even in the case where no scheduler scheduler is used at all. I'm not exactly sure why but these changes likely allowed some further optimizations somewhere. Even RAM is slightly reduced, perhaps some global was elminated in the process as well.
This commit is contained in:
committed by
Ron Evans
parent
61f711ef26
commit
542135c357
@@ -0,0 +1,95 @@
|
||||
// +build scheduler.coroutines
|
||||
|
||||
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 we use the type 'task' to refer to a coroutine, for compatibility
|
||||
// with the task-based scheduler. A task type here does not represent the whole
|
||||
// task, but just the topmost coroutine. For most of the scheduler, this
|
||||
// difference doesn't matter.
|
||||
//
|
||||
// For more background on coroutines in LLVM:
|
||||
// https://llvm.org/docs/Coroutines.html
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// 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 task uint8
|
||||
|
||||
//go:export llvm.coro.resume
|
||||
func (t *task) resume()
|
||||
|
||||
//go:export llvm.coro.destroy
|
||||
func (t *task) destroy()
|
||||
|
||||
//go:export llvm.coro.done
|
||||
func (t *task) done() bool
|
||||
|
||||
//go:export llvm.coro.promise
|
||||
func (t *task) _promise(alignment int32, from bool) unsafe.Pointer
|
||||
|
||||
// Get the state belonging to a task.
|
||||
func (t *task) state() *taskState {
|
||||
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
|
||||
}
|
||||
|
||||
func makeGoroutine(uintptr) uintptr
|
||||
|
||||
// Compiler stub to get the current goroutine. Calls to this function are
|
||||
// removed in the goroutine lowering pass.
|
||||
func getCoroutine() *task
|
||||
|
||||
// getTaskStatePtr is a helper function to set the current .ptr field of a
|
||||
// coroutine promise.
|
||||
func setTaskStatePtr(t *task, value unsafe.Pointer) {
|
||||
t.state().ptr = value
|
||||
}
|
||||
|
||||
// getTaskStatePtr is a helper function to get the current .ptr field from a
|
||||
// coroutine promise.
|
||||
func getTaskStatePtr(t *task) unsafe.Pointer {
|
||||
if t == nil {
|
||||
blockingPanic()
|
||||
}
|
||||
return t.state().ptr
|
||||
}
|
||||
|
||||
//go:linkname sleep time.Sleep
|
||||
func sleep(d int64) {
|
||||
sleepTicks(timeUnit(d / tickMicros))
|
||||
}
|
||||
|
||||
// deadlock is called when a goroutine cannot proceed any more, but is in theory
|
||||
// not exited (so deferred calls won't run). This can happen for example in code
|
||||
// like this, that blocks forever:
|
||||
//
|
||||
// select{}
|
||||
//
|
||||
// The coroutine version is implemented directly in the compiler but it needs
|
||||
// this definition to work.
|
||||
func deadlock()
|
||||
|
||||
// reactivateParent reactivates the parent goroutine. It is necessary in case of
|
||||
// the coroutine-based scheduler.
|
||||
func reactivateParent(t *task) {
|
||||
activateTask(t)
|
||||
}
|
||||
|
||||
// chanYield exits the current goroutine. Used in the channel implementation, to
|
||||
// suspend the current goroutine until it is reactivated by a channel operation
|
||||
// of a different goroutine. It is a no-op in the coroutine implementation.
|
||||
func chanYield() {
|
||||
// Nothing to do here, simply returning from the channel operation also exits
|
||||
// the goroutine temporarily.
|
||||
}
|
||||
Reference in New Issue
Block a user