//go: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 t.gcData.swap() currentTask = t if !t.state.launched { t.state.launch() t.state.launched = true } else { t.state.rewind() } currentTask = prevTask t.gcData.swap() 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 }