mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-14 07:53:40 +00:00
refactor coroutine lowering and tasks
This commit is contained in:
@@ -2,110 +2,6 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const stackSize = 1024
|
||||
|
||||
// Stack canary, to detect a stack overflow. The number is a random number
|
||||
// generated by random.org. The bit fiddling dance is necessary because
|
||||
// otherwise Go wouldn't allow the cast to a smaller integer size.
|
||||
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
|
||||
|
||||
var (
|
||||
currentTask *task // currently running goroutine, or nil
|
||||
)
|
||||
|
||||
// This type points to the bottom of the goroutine stack and contains some state
|
||||
// that must be kept with the task. The last field is a canary, which is
|
||||
// necessary to make sure that no stack overflow occured when switching tasks.
|
||||
type task struct {
|
||||
// The order of fields in this structs must be kept in sync with assembly!
|
||||
calleeSavedRegs
|
||||
pc uintptr
|
||||
sp uintptr
|
||||
taskState
|
||||
canaryPtr *uintptr // used to detect stack overflows
|
||||
}
|
||||
|
||||
// getCoroutine returns the currently executing goroutine. It is used as an
|
||||
// intrinsic when compiling channel operations, but is not necessary with the
|
||||
// task-based scheduler.
|
||||
//go:inline
|
||||
func getCoroutine() *task {
|
||||
return currentTask
|
||||
}
|
||||
|
||||
// state is a small helper that returns the task state, and is provided for
|
||||
// compatibility with the coroutine implementation.
|
||||
//go:inline
|
||||
func (t *task) state() *taskState {
|
||||
return &t.taskState
|
||||
}
|
||||
|
||||
// resume is a small helper that resumes this task until this task switches back
|
||||
// to the scheduler.
|
||||
func (t *task) resume() {
|
||||
currentTask = t
|
||||
switchToTask(t)
|
||||
currentTask = nil
|
||||
}
|
||||
|
||||
// switchToScheduler saves the current state on the stack, saves the current
|
||||
// stack pointer in the task, and switches to the scheduler. It must only be
|
||||
// called when actually running on this task.
|
||||
// When it returns, the scheduler has switched back to this task (for example,
|
||||
// after a blocking operation completed).
|
||||
//export tinygo_switchToScheduler
|
||||
func switchToScheduler(t *task)
|
||||
|
||||
// switchToTask switches from the scheduler to the task. It must only be called
|
||||
// from the scheduler.
|
||||
// When this function returns, the task just yielded control back to the
|
||||
// scheduler.
|
||||
//export tinygo_switchToTask
|
||||
func switchToTask(t *task)
|
||||
|
||||
// startTask is a small wrapper function that sets up the first (and only)
|
||||
// argument to the new goroutine and makes sure it is exited when the goroutine
|
||||
// finishes.
|
||||
//go:extern tinygo_startTask
|
||||
var startTask [0]uint8
|
||||
|
||||
// startGoroutine starts a new goroutine with the given function pointer and
|
||||
// argument. It creates a new goroutine stack, prepares it for execution, and
|
||||
// adds it to the runqueue.
|
||||
func startGoroutine(fn, args uintptr) {
|
||||
stack := alloc(stackSize)
|
||||
t := (*task)(unsafe.Pointer(uintptr(stack) + stackSize - unsafe.Sizeof(task{})))
|
||||
|
||||
// Set up the stack canary, a random number that should be checked when
|
||||
// switching from the task back to the scheduler. The stack canary pointer
|
||||
// points to the first word of the stack. If it has changed between now and
|
||||
// the next stack switch, there was a stack overflow.
|
||||
t.canaryPtr = (*uintptr)(unsafe.Pointer(stack))
|
||||
*t.canaryPtr = stackCanary
|
||||
|
||||
// Store the initial sp/pc for the startTask function (implemented in
|
||||
// assembly).
|
||||
t.sp = uintptr(stack) + stackSize - unsafe.Sizeof(task{})
|
||||
t.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
t.prepareStartTask(fn, args)
|
||||
scheduleLogTask(" start goroutine:", t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
// yield suspends execution of the current goroutine
|
||||
// any wakeups must be configured before calling yield
|
||||
//export runtime.yield
|
||||
func yield() {
|
||||
// Check whether the canary (the lowest address of the stack) is still
|
||||
// valid. If it is not, a stack overflow has occured.
|
||||
if *currentTask.canaryPtr != stackCanary {
|
||||
runtimePanic("goroutine stack overflow")
|
||||
}
|
||||
switchToScheduler(currentTask)
|
||||
}
|
||||
|
||||
// getSystemStackPointer returns the current stack pointer of the system stack.
|
||||
// This is not necessarily the same as the current stack pointer.
|
||||
//export tinygo_getSystemStackPointer
|
||||
|
||||
Reference in New Issue
Block a user