//go:build scheduler.tasks package task import ( "runtime/interrupt" "unsafe" ) //go:linkname runtimePanic runtime.runtimePanic func runtimePanic(str string) // 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))) // state is a structure which holds a reference to the state of the task. // When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp. type state struct { // sp is the stack pointer of the saved state. // When the task is inactive, the saved registers are stored at the top of the stack. // Note: this should ideally be a unsafe.Pointer for the precise GC. The GC // will find the stack through canaryPtr though so it's not currently a // problem to store this value as uintptr. sp uintptr // canaryPtr points to the top word of the stack (the lowest address). // This is used to detect stack overflows. // When initializing the goroutine, the stackCanary constant is stored there. // If the stack overflowed, the word will likely no longer equal stackCanary. canaryPtr *uintptr } // 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 or in an interrupt. func Pause() { // Check whether the canary (the lowest address of the stack) is still // valid. If it is not, a stack overflow has occured. if *currentTask.state.canaryPtr != stackCanary { runtimePanic("goroutine stack overflow") } if interrupt.In() { runtimePanic("blocked inside interrupt") } currentTask.state.pause() } //export tinygo_pause func pause() { Pause() } // Resume the task until it pauses or completes. // This may only be called from the scheduler. func (t *Task) Resume() { currentTask = t t.gcData.swap() t.state.resume() t.gcData.swap() currentTask = nil } // 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) { // Create a stack. stack := runtime_alloc(stackSize, nil) // Set up the stack canary, a random number that should be checked when // switching from the task back to the scheduler. The stack canary pointer // points to the first word of the stack. If it has changed between now and // the next stack switch, there was a stack overflow. s.canaryPtr = (*uintptr)(stack) *s.canaryPtr = stackCanary // Get a pointer to the top of the stack, where the initial register values // are stored. They will be popped off the stack on the first stack switch // to the goroutine, and will start running tinygo_startTask (this setup // happens in archInit). r := (*calleeSavedRegs)(unsafe.Add(stack, stackSize-unsafe.Sizeof(calleeSavedRegs{}))) // Invoke architecture-specific initialization. s.archInit(r, fn, args) } //export tinygo_swapTask func swapTask(oldStack uintptr, newStack *uintptr) // startTask is a small wrapper function that sets up the first (and only) // argument to the new goroutine and makes sure it is exited when the goroutine // finishes. // //go:extern tinygo_startTask var startTask [0]uint8 //go:linkname runqueuePushBack runtime.runqueuePushBack func runqueuePushBack(*Task) //go:linkname runtime_alloc runtime.alloc func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is scheduled to run later. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) runqueuePushBack(t) } // 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 }