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  }