mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 20:13:40 +00:00
6917faabf5
This fix is needed because with the new task-based scheduler, the current stack pointer may not be on the system stack.
102 lines
3.4 KiB
Go
102 lines
3.4 KiB
Go
// +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.
|
|
}
|
|
|
|
// getSystemStackPointer returns the current stack pointer of the system stack.
|
|
// This is always the current stack pointer.
|
|
func getSystemStackPointer() uintptr {
|
|
return getCurrentStackPointer()
|
|
}
|