github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/internal/task/task_stack.go (about) 1 //go:build scheduler.tasks 2 3 package task 4 5 import ( 6 "runtime/interrupt" 7 "unsafe" 8 ) 9 10 //go:linkname runtimePanic runtime.runtimePanic 11 func runtimePanic(str string) 12 13 // Stack canary, to detect a stack overflow. The number is a random number 14 // generated by random.org. The bit fiddling dance is necessary because 15 // otherwise Go wouldn't allow the cast to a smaller integer size. 16 const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0))) 17 18 // state is a structure which holds a reference to the state of the task. 19 // When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp. 20 type state struct { 21 // sp is the stack pointer of the saved state. 22 // When the task is inactive, the saved registers are stored at the top of the stack. 23 // Note: this should ideally be a unsafe.Pointer for the precise GC. The GC 24 // will find the stack through canaryPtr though so it's not currently a 25 // problem to store this value as uintptr. 26 sp uintptr 27 28 // canaryPtr points to the top word of the stack (the lowest address). 29 // This is used to detect stack overflows. 30 // When initializing the goroutine, the stackCanary constant is stored there. 31 // If the stack overflowed, the word will likely no longer equal stackCanary. 32 canaryPtr *uintptr 33 } 34 35 // currentTask is the current running task, or nil if currently in the scheduler. 36 var currentTask *Task 37 38 // Current returns the current active task. 39 func Current() *Task { 40 return currentTask 41 } 42 43 // Pause suspends the current task and returns to the scheduler. 44 // This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt. 45 func Pause() { 46 // Check whether the canary (the lowest address of the stack) is still 47 // valid. If it is not, a stack overflow has occured. 48 if *currentTask.state.canaryPtr != stackCanary { 49 runtimePanic("goroutine stack overflow") 50 } 51 if interrupt.In() { 52 runtimePanic("blocked inside interrupt") 53 } 54 currentTask.state.pause() 55 } 56 57 //export tinygo_pause 58 func pause() { 59 Pause() 60 } 61 62 // Resume the task until it pauses or completes. 63 // This may only be called from the scheduler. 64 func (t *Task) Resume() { 65 currentTask = t 66 t.gcData.swap() 67 t.state.resume() 68 t.gcData.swap() 69 currentTask = nil 70 } 71 72 // initialize the state and prepare to call the specified function with the specified argument bundle. 73 func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { 74 // Create a stack. 75 stack := runtime_alloc(stackSize, nil) 76 77 // Set up the stack canary, a random number that should be checked when 78 // switching from the task back to the scheduler. The stack canary pointer 79 // points to the first word of the stack. If it has changed between now and 80 // the next stack switch, there was a stack overflow. 81 s.canaryPtr = (*uintptr)(stack) 82 *s.canaryPtr = stackCanary 83 84 // Get a pointer to the top of the stack, where the initial register values 85 // are stored. They will be popped off the stack on the first stack switch 86 // to the goroutine, and will start running tinygo_startTask (this setup 87 // happens in archInit). 88 r := (*calleeSavedRegs)(unsafe.Add(stack, stackSize-unsafe.Sizeof(calleeSavedRegs{}))) 89 90 // Invoke architecture-specific initialization. 91 s.archInit(r, fn, args) 92 } 93 94 //export tinygo_swapTask 95 func swapTask(oldStack uintptr, newStack *uintptr) 96 97 // startTask is a small wrapper function that sets up the first (and only) 98 // argument to the new goroutine and makes sure it is exited when the goroutine 99 // finishes. 100 // 101 //go:extern tinygo_startTask 102 var startTask [0]uint8 103 104 //go:linkname runqueuePushBack runtime.runqueuePushBack 105 func runqueuePushBack(*Task) 106 107 //go:linkname runtime_alloc runtime.alloc 108 func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer 109 110 // start creates and starts a new goroutine with the given function and arguments. 111 // The new goroutine is scheduled to run later. 112 func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { 113 t := &Task{} 114 t.state.initialize(fn, args, stackSize) 115 runqueuePushBack(t) 116 } 117 118 // OnSystemStack returns whether the caller is running on the system stack. 119 func OnSystemStack() bool { 120 // If there is not an active goroutine, then this must be running on the system stack. 121 return Current() == nil 122 }