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

     1  //go:build tinygo
     2  
     3  // Only generate .debug_frame, don't generate .eh_frame.
     4  .cfi_sections .debug_frame
     5  
     6  .section .text.tinygo_startTask
     7  .global  tinygo_startTask
     8  .type    tinygo_startTask, %function
     9  tinygo_startTask:
    10      .cfi_startproc
    11      // Small assembly stub for starting a goroutine. This is already run on the
    12      // new stack, with the callee-saved registers already loaded.
    13      // Most importantly, r4 contains the pc of the to-be-started function and r5
    14      // contains the only argument it is given. Multiple arguments are packed
    15      // into one by storing them in a new allocation.
    16  
    17      // Indicate to the unwinder that there is nothing to unwind, this is the
    18      // root frame. It avoids the following (bogus) error message in GDB:
    19      //     Backtrace stopped: previous frame identical to this frame (corrupt stack?)
    20      .cfi_undefined lr
    21  
    22      // Set the first argument of the goroutine start wrapper, which contains all
    23      // the arguments.
    24      mov   r0, r5
    25  
    26      // Branch to the "goroutine start" function. By using blx instead of bx,
    27      // we'll return here instead of tail calling.
    28      blx   r4
    29  
    30      // After return, exit this goroutine. This is a tail call.
    31      bl    	tinygo_pause
    32      .cfi_endproc
    33  .size tinygo_startTask, .-tinygo_startTask
    34  
    35  .section .text.tinygo_switchToScheduler
    36  .global  tinygo_switchToScheduler
    37  .type    tinygo_switchToScheduler, %function
    38  tinygo_switchToScheduler:
    39      .cfi_startproc
    40      // r0 = sp *uintptr
    41  
    42      // Currently on the task stack (SP=PSP). We need to store the position on
    43      // the stack where the in-use registers will be stored.
    44      mov r1, sp
    45      subs r1, #36
    46      str r1, [r0]
    47  
    48      b tinygo_swapTask
    49      .cfi_endproc
    50  .size tinygo_switchToScheduler, .-tinygo_switchToScheduler
    51  
    52  .section .text.tinygo_switchToTask
    53  .global  tinygo_switchToTask
    54  .type    tinygo_switchToTask, %function
    55  tinygo_switchToTask:
    56      .cfi_startproc
    57      // r0 = sp uintptr
    58  
    59      // Currently on the scheduler stack (SP=MSP). We'll have to update the PSP,
    60      // and then we can invoke swapTask.
    61      msr PSP, r0
    62  
    63      b.n tinygo_swapTask
    64      .cfi_endproc
    65  .size tinygo_switchToTask, .-tinygo_switchToTask
    66  
    67  .section .text.tinygo_swapTask
    68  .global  tinygo_swapTask
    69  .type    tinygo_swapTask, %function
    70  tinygo_swapTask:
    71      .cfi_startproc
    72      // This function stores the current register state to the stack, switches to
    73      // the other stack (MSP/PSP), and loads the register state from the other
    74      // stack. Apart from saving and restoring all relevant callee-saved
    75      // registers, it also ends with branching to the last program counter (saved
    76      // as the lr register, to follow the ARM calling convention).
    77  
    78      // On pre-Thumb2 CPUs (Cortex-M0 in particular), registers r8-r15 cannot be
    79      // used directly. Only very few operations work on them, such as mov. That's
    80      // why the higher register values are first stored in the temporary register
    81      // r3 when loading/storing them.
    82      // It is possible to reduce the swapTask by two instructions (~2 cycles) on
    83      // Cortex-M0 by reordering the layout of the pushed registers from {r4-r11,
    84      // lr} to {r8-r11, r4-r8, lr}. However, that also requires a change on the
    85      // Go side (depending on thumb1/thumb2!) and so is not really worth the
    86      // complexity.
    87  
    88      // Store state to old task. It saves the lr instead of the pc, because that
    89      // will be the pc after returning back to the old task (in a different
    90      // invocation of swapTask).
    91      #if defined(__thumb2__)
    92      push {r4-r11, lr}
    93      .cfi_def_cfa_offset 9*4
    94      #else
    95      mov r0, r8
    96      mov r1, r9
    97      mov r2, r10
    98      mov r3, r11
    99      push {r0-r3, lr}
   100      .cfi_def_cfa_offset 5*4
   101      push {r4-r7}
   102      .cfi_def_cfa_offset 9*4
   103      #endif
   104  
   105      // Switch the stack. This could either switch from PSP to MSP, or from MSP
   106      // to PSP. By using an XOR (eor), it will just switch to the other stack.
   107      mrs  r0, CONTROL // load CONTROL register
   108      movs r3, #2
   109      eors r0, r0, r3  // flip the SPSEL (active stack pointer) bit
   110      msr  CONTROL, r0 // store CONTROL register
   111      isb              // required to flush the pipeline
   112  
   113      // Load state from new task and branch to the previous position in the
   114      // program.
   115      #if defined(__thumb2__)
   116      pop {r4-r11, pc}
   117      #else
   118      pop {r4-r7}
   119      .cfi_def_cfa_offset 5*4
   120      pop {r0-r3}
   121      .cfi_def_cfa_offset 1*4
   122      mov r8, r0
   123      mov r9, r1
   124      mov r10, r2
   125      mov r11, r3
   126      pop {pc}
   127      #endif
   128      .cfi_endproc
   129  .size tinygo_swapTask, .-tinygo_swapTask