github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/internal/task/task_asyncify.go (about) 1 //go:build scheduler.asyncify 2 3 package task 4 5 import ( 6 "unsafe" 7 ) 8 9 // Stack canary, to detect a stack overflow. The number is a random number 10 // generated by random.org. The bit fiddling dance is necessary because 11 // otherwise Go wouldn't allow the cast to a smaller integer size. 12 const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0))) 13 14 //go:linkname runtimePanic runtime.runtimePanic 15 func runtimePanic(str string) 16 17 // state is a structure which holds a reference to the state of the task. 18 // When the task is suspended, the stack pointers are saved here. 19 type state struct { 20 // entry is the entry function of the task. 21 // This is needed every time the function is invoked so that asyncify knows what to rewind. 22 entry uintptr 23 24 // args are a pointer to a struct holding the arguments of the function. 25 args unsafe.Pointer 26 27 // stackState is the state of the stack while unwound. 28 stackState 29 30 launched bool 31 } 32 33 // stackState is the saved state of a stack while unwound. 34 // The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two. 35 type stackState struct { 36 // asyncify is the stack pointer of the asyncify stack. 37 // This starts from the bottom and grows upwards. 38 asyncifysp uintptr 39 40 // asyncify is stack pointer of the C stack. 41 // This starts from the top and grows downwards. 42 csp uintptr 43 } 44 45 // start creates and starts a new goroutine with the given function and arguments. 46 // The new goroutine is immediately started. 47 func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { 48 t := &Task{} 49 t.state.initialize(fn, args, stackSize) 50 runqueuePushBack(t) 51 } 52 53 //export tinygo_launch 54 func (*state) launch() 55 56 //go:linkname align runtime.align 57 func align(p uintptr) uintptr 58 59 // initialize the state and prepare to call the specified function with the specified argument bundle. 60 func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { 61 // Save the entry call. 62 s.entry = fn 63 s.args = args 64 65 // Create a stack. 66 stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) 67 68 // Calculate stack base addresses. 69 s.asyncifysp = uintptr(unsafe.Pointer(&stack[0])) 70 s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0)) 71 stack[0] = stackCanary 72 } 73 74 //go:linkname runqueuePushBack runtime.runqueuePushBack 75 func runqueuePushBack(*Task) 76 77 // currentTask is the current running task, or nil if currently in the scheduler. 78 var currentTask *Task 79 80 // Current returns the current active task. 81 func Current() *Task { 82 return currentTask 83 } 84 85 // Pause suspends the current task and returns to the scheduler. 86 // This function may only be called when running on a goroutine stack, not when running on the system stack. 87 func Pause() { 88 // This is mildly unsafe but this is also the only place we can do this. 89 if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary { 90 runtimePanic("stack overflow") 91 } 92 93 currentTask.state.unwind() 94 95 *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary 96 } 97 98 //export tinygo_unwind 99 func (*stackState) unwind() 100 101 // Resume the task until it pauses or completes. 102 // This may only be called from the scheduler. 103 func (t *Task) Resume() { 104 // The current task must be saved and restored because this can nest on WASM with JS. 105 prevTask := currentTask 106 t.gcData.swap() 107 currentTask = t 108 if !t.state.launched { 109 t.state.launch() 110 t.state.launched = true 111 } else { 112 t.state.rewind() 113 } 114 currentTask = prevTask 115 t.gcData.swap() 116 if t.state.asyncifysp > t.state.csp { 117 runtimePanic("stack overflow") 118 } 119 } 120 121 //export tinygo_rewind 122 func (*state) rewind() 123 124 // OnSystemStack returns whether the caller is running on the system stack. 125 func OnSystemStack() bool { 126 // If there is not an active goroutine, then this must be running on the system stack. 127 return Current() == nil 128 }