task_stack.mx raw

   1  //go:build scheduler.tasks || scheduler.cores
   2  
   3  package task
   4  
   5  import (
   6  	"unsafe"
   7  )
   8  
   9  //go:linkname runtimePanic runtime.runtimePanic
  10  func runtimePanic(str string)
  11  
  12  // Stack canary, to detect a stack overflow. The number is a random number
  13  // generated by random.org. The bit fiddling dance is necessary because
  14  // otherwise Go wouldn't allow the cast to a smaller integer size.
  15  const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
  16  
  17  // state is a structure which holds a reference to the state of the task.
  18  // When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp.
  19  type state struct {
  20  	// sp is the stack pointer of the saved state.
  21  	// When the task is inactive, the saved registers are stored at the top of the stack.
  22  	// Note: this should ideally be a unsafe.Pointer for the precise GC. The GC
  23  	// will find the stack through canaryPtr though so it's not currently a
  24  	// problem to store this value as uintptr.
  25  	sp uintptr
  26  
  27  	// canaryPtr points to the top word of the stack (the lowest address).
  28  	// This is used to detect stack overflows.
  29  	// When initializing the goroutine, the stackCanary constant is stored there.
  30  	// If the stack overflowed, the word will likely no longer equal stackCanary.
  31  	canaryPtr *uintptr
  32  }
  33  
  34  //export moxie_task_exit
  35  func taskExit() {
  36  	// TODO: explicitly free the stack after switching back to the scheduler.
  37  	Pause()
  38  }
  39  
  40  // initialize the state and prepare to call the specified function with the specified argument bundle.
  41  func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
  42  	// Create a stack.
  43  	stack := runtime_alloc(stackSize, nil)
  44  
  45  	// Set up the stack canary, a random number that should be checked when
  46  	// switching from the task back to the scheduler. The stack canary pointer
  47  	// points to the first word of the stack. If it has changed between now and
  48  	// the next stack switch, there was a stack overflow.
  49  	s.canaryPtr = (*uintptr)(stack)
  50  	*s.canaryPtr = stackCanary
  51  
  52  	// Get a pointer to the top of the stack, where the initial register values
  53  	// are stored. They will be popped off the stack on the first stack switch
  54  	// to the goroutine, and will start running moxie_startTask (this setup
  55  	// happens in archInit).
  56  	r := (*calleeSavedRegs)(unsafe.Add(stack, stackSize-unsafe.Sizeof(calleeSavedRegs{})))
  57  
  58  	// Invoke architecture-specific initialization.
  59  	s.archInit(r, fn, args)
  60  }
  61  
  62  //export moxie_swapTask
  63  func swapTask(oldStack uintptr, newStack *uintptr)
  64  
  65  // startTask is a small wrapper function that sets up the first (and only)
  66  // argument to the new goroutine and makes sure it is exited when the goroutine
  67  // finishes.
  68  //
  69  //go:extern moxie_startTask
  70  var startTask [0]uint8
  71  
  72  // start creates and starts a new goroutine with the given function and arguments.
  73  // The new goroutine is scheduled to run later.
  74  func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
  75  	t := &Task{}
  76  	t.state.initialize(fn, args, stackSize)
  77  	scheduleTask(t)
  78  }
  79  
  80  // OnSystemStack returns whether the caller is running on the system stack.
  81  func OnSystemStack() bool {
  82  	// If there is not an active goroutine, then this must be running on the system stack.
  83  	return Current() == nil
  84  }
  85