github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/internal/task/task_stack_esp32.go (about)

     1  //go:build scheduler.tasks && esp32
     2  
     3  package task
     4  
     5  // The windowed ABI (used on the ESP32) is as follows:
     6  //   a0:    return address (link register)
     7  //   a1:    stack pointer (must be 16-byte aligned)
     8  //   a2-a7: incoming arguments
     9  //   a7:    stack frame pointer (optional, normally unused in TinyGo)
    10  // Sources:
    11  //   http://cholla.mmto.org/esp8266/xtensa.html
    12  //   https://0x04.net/~mwk/doc/xtensa.pdf
    13  
    14  import (
    15  	"unsafe"
    16  )
    17  
    18  var systemStack uintptr
    19  
    20  // calleeSavedRegs is the list of registers that must be saved and restored when
    21  // switching between tasks. Also see task_stack_esp8266.S that relies on the
    22  // exact layout of this struct.
    23  type calleeSavedRegs struct {
    24  	// Registers in the register window of tinygo_startTask.
    25  	a0 uintptr
    26  	a1 uintptr
    27  	a2 uintptr
    28  	a3 uintptr
    29  
    30  	// Locals that can be used by tinygo_swapTask.
    31  	// The first field is the a0 loaded in tinygo_swapTask, the rest is unused.
    32  	locals [4]uintptr
    33  }
    34  
    35  // archInit runs architecture-specific setup for the goroutine startup.
    36  func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
    37  	// Store the stack pointer for the tinygo_swapTask function (implemented in
    38  	// assembly). It needs to point to the locals field instead of a0 so that
    39  	// the retw.n at the end of tinygo_swapTask will return into
    40  	// tinygo_startTask with a0-a3 loaded (using the register window mechanism).
    41  	s.sp = uintptr(unsafe.Pointer(&r.locals[0]))
    42  
    43  	// Start the goroutine at tinygo_startTask (defined in
    44  	// src/internal/task/task_stack_esp32.S). The topmost two bits are not part
    45  	// of the address but instead store the register window of the caller.
    46  	// In this case there is no caller, instead we set up the return address as
    47  	// if tinygo_startTask called tinygo_swapTask with a call4 instruction.
    48  	r.locals[0] = uintptr(unsafe.Pointer(&startTask))&^(3<<30) | (1 << 30)
    49  
    50  	// Set up the stack pointer inside tinygo_startTask.
    51  	// Unlike most calling conventions, the windowed ABI actually saves the
    52  	// stack pointer on the stack to make register windowing work.
    53  	r.a1 = uintptr(unsafe.Pointer(r)) + 32
    54  
    55  	// Store the function pointer and the (only) parameter on the stack in a
    56  	// location that will be reloaded into registers when doing the
    57  	// pseudo-return to tinygo_startTask using the register window mechanism.
    58  	r.a3 = fn
    59  	r.a2 = uintptr(args)
    60  }
    61  
    62  func (s *state) resume() {
    63  	swapTask(s.sp, &systemStack)
    64  }
    65  
    66  func (s *state) pause() {
    67  	newStack := systemStack
    68  	systemStack = 0
    69  	swapTask(newStack, &s.sp)
    70  }
    71  
    72  // SystemStack returns the system stack pointer when called from a task stack.
    73  // When called from the system stack, it returns 0.
    74  func SystemStack() uintptr {
    75  	return systemStack
    76  }