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