diff options
Diffstat (limited to 'src/internal/task/task_asyncify.go')
-rw-r--r-- | src/internal/task/task_asyncify.go | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go new file mode 100644 index 000000000..d67f0e1ca --- /dev/null +++ b/src/internal/task/task_asyncify.go @@ -0,0 +1,127 @@ +//go:build scheduler.asyncify +// +build scheduler.asyncify + +package task + +import ( + "unsafe" +) + +// 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))) + +//go:linkname runtimePanic runtime.runtimePanic +func runtimePanic(str string) + +// state is a structure which holds a reference to the state of the task. +// When the task is suspended, the stack pointers are saved here. +type state struct { + // entry is the entry function of the task. + // This is needed every time the function is invoked so that asyncify knows what to rewind. + entry uintptr + + // args are a pointer to a struct holding the arguments of the function. + args unsafe.Pointer + + // stackState is the state of the stack while unwound. + stackState + + launched bool +} + +// stackState is the saved state of a stack while unwound. +// The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two. +type stackState struct { + // asyncify is the stack pointer of the asyncify stack. + // This starts from the bottom and grows upwards. + asyncifysp uintptr + + // asyncify is stack pointer of the C stack. + // This starts from the top and grows downwards. + csp uintptr +} + +// start creates and starts a new goroutine with the given function and arguments. +// The new goroutine is immediately started. +func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { + t := &Task{} + t.state.initialize(fn, args, stackSize) + runqueuePushBack(t) +} + +//export tinygo_launch +func (*state) launch() + +//go:linkname align runtime.align +func align(p uintptr) uintptr + +// initialize the state and prepare to call the specified function with the specified argument bundle. +func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { + // Save the entry call. + s.entry = fn + s.args = args + + // Create a stack. + stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) + + // Calculate stack base addresses. + s.asyncifysp = uintptr(unsafe.Pointer(&stack[0])) + s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0)) + stack[0] = stackCanary +} + +//go:linkname runqueuePushBack runtime.runqueuePushBack +func runqueuePushBack(*Task) + +// currentTask is the current running task, or nil if currently in the scheduler. +var currentTask *Task + +// Current returns the current active task. +func Current() *Task { + return currentTask +} + +// Pause suspends the current task and returns to the scheduler. +// This function may only be called when running on a goroutine stack, not when running on the system stack. +func Pause() { + // This is mildly unsafe but this is also the only place we can do this. + if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary { + runtimePanic("stack overflow") + } + + currentTask.state.unwind() + + *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary +} + +//export tinygo_unwind +func (*stackState) unwind() + +// Resume the task until it pauses or completes. +// This may only be called from the scheduler. +func (t *Task) Resume() { + // The current task must be saved and restored because this can nest on WASM with JS. + prevTask := currentTask + currentTask = t + if !t.state.launched { + t.state.launch() + t.state.launched = true + } else { + t.state.rewind() + } + currentTask = prevTask + if t.state.asyncifysp > t.state.csp { + runtimePanic("stack overflow") + } +} + +//export tinygo_rewind +func (*state) rewind() + +// OnSystemStack returns whether the caller is running on the system stack. +func OnSystemStack() bool { + // If there is not an active goroutine, then this must be running on the system stack. + return Current() == nil +} |