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  }