mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-08 13:03:39 +00:00
d843ebfe40
core: major improvements to blocking, including support for buffered channels.
105 lines
3.1 KiB
Go
105 lines
3.1 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
|
|
|
|
// setTaskStatePtr 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
|
|
}
|
|
|
|
// yield suspends execution of the current goroutine
|
|
// any wakeups must be configured before calling yield
|
|
func yield()
|
|
|
|
// getSystemStackPointer returns the current stack pointer of the system stack.
|
|
// This is always the current stack pointer.
|
|
func getSystemStackPointer() uintptr {
|
|
return getCurrentStackPointer()
|
|
}
|
|
|
|
func fakeCoroutine(dst **task) {
|
|
*dst = getCoroutine()
|
|
for {
|
|
yield()
|
|
}
|
|
}
|
|
|
|
func getFakeCoroutine() *task {
|
|
// this isnt defined behavior, but this is what our implementation does
|
|
// this is really a horrible hack
|
|
var t *task
|
|
go fakeCoroutine(&t)
|
|
|
|
// the first line of fakeCoroutine will have completed by now
|
|
return t
|
|
}
|
|
|
|
// noret is a placeholder that can be used to indicate that an async function is not going to directly return here
|
|
func noret()
|
|
|
|
func getParentHandle() *task
|
|
|
|
func llvmCoroRefHolder() {
|
|
noret()
|
|
getParentHandle()
|
|
getCoroutine()
|
|
}
|