aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/internal/task/task_asyncify.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal/task/task_asyncify.go')
-rw-r--r--src/internal/task/task_asyncify.go127
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
+}