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

     1  //go:build tinygo
     2  
     3  .section .text.tinygo_startTask,"ax",@progbits
     4  .global  tinygo_startTask
     5  .type    tinygo_startTask, %function
     6  tinygo_startTask:
     7      // Small assembly stub for starting a goroutine. This already runs on the
     8      // new stack, control reaches this function after returning from the initial
     9      // tinygo_swapTask below (the retw.n instruction).
    10      //
    11      // The stack was set up in such a way that it looks as if this function was
    12      // paused using tinygo_swapTask by setting up the parent register window and
    13      // return pointer as a call4 instruction - except such a call never took
    14      // place. Instead, the stack pointer is switched to the new stack after all
    15      // live-but-invisible registers have been flushed to the stack. This means
    16      // that all registers as present in tinygo_swapTask are moved four up (a2 in
    17      // tinygo_swapTask is a6 in this function). We don't use any of those
    18      // registers however. Instead, the retw.n instruction will load them through
    19      // an underflow exception from the stack which means we get a0-a3 as defined
    20      // in task_stack_esp32.go.
    21  
    22      // Branch to the "goroutine start" function. The first (and only) parameter
    23      // is stored in a2, but has to be moved to a6 to make it appear as a2 in the
    24      // goroutine start function (due to changing the register window by four
    25      // with callx4).
    26      mov.n a6, a2
    27      callx4 a3
    28  
    29      // After return, exit this goroutine. This call never returns.
    30      call4  tinygo_pause
    31  
    32  .section .text.tinygo_swapTask,"ax",@progbits
    33  .global tinygo_swapTask
    34  .type tinygo_swapTask, %function
    35  tinygo_swapTask:
    36      // This function gets the following parameters:
    37      // a2 = newStack uintptr
    38      // a3 = oldStack *uintptr
    39  
    40      // Reserve 32 bytes on the stack. It really needs to be 32 bytes, with 16
    41      // extra at the bottom to adhere to the ABI.
    42      entry sp, 32
    43  
    44      // Disable interrupts while flushing registers. This is necessary because
    45      // interrupts might want to use the stack pointer (at a2) which will be some
    46      // arbitrary register while registers are flushed.
    47      rsil a4, 3 // XCHAL_EXCM_LEVEL
    48  
    49      // Flush all unsaved registers to the stack.
    50      // This trick has been borrowed from the Zephyr project:
    51      // https://github.com/zephyrproject-rtos/zephyr/blob/d79b003758/arch/xtensa/include/xtensa-asm2-s.h#L17
    52      and a12, a12, a12
    53      rotw 3
    54      and a12, a12, a12
    55      rotw 3
    56      and a12, a12, a12
    57      rotw 3
    58      and a12, a12, a12
    59      rotw 3
    60      and a12, a12, a12
    61      rotw 4
    62  
    63      // Restore interrupts.
    64      wsr.ps a4
    65  
    66      // At this point, the following is true:
    67      //     WindowStart == 1 << WindowBase
    68      // Therefore, we don't need to do this manually.
    69      // It also means that the stack pointer can now be safely modified.
    70  
    71      // Save a0, which stores the return address and the parent register window
    72      // in the upper two bits.
    73      s32i.n a0, sp, 0
    74  
    75      // Save the current stack pointer in oldStack.
    76      s32i.n  sp, a3, 0
    77  
    78      // Switch to the new stack pointer (newStack).
    79      mov.n   sp, a2
    80  
    81      // Load a0, which is the previous return addres from before the previous
    82      // switch or the constructed return address to tinygo_startTask. This
    83      // register also stores the parent register window.
    84      l32i.n a0, sp, 0
    85  
    86      // Return into the new stack. This instruction will trigger a window
    87      // underflow, reloading the saved registers from the stack.
    88      retw.n