diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 214f2a62..a703d10a 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -45,6 +45,11 @@ func Pause() { currentTask.state.pause() } +//export tinygo_pause +func pause() { + Pause() +} + // Resume the task until it pauses or completes. // This may only be called from the scheduler. func (t *Task) Resume() { @@ -58,10 +63,32 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // Create a stack. stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) + // Set up the stack canary, a random number that should be checked when + // switching from the task back to the scheduler. The stack canary pointer + // points to the first word of the stack. If it has changed between now and + // the next stack switch, there was a stack overflow. + s.canaryPtr = &stack[0] + *s.canaryPtr = stackCanary + + // Get a pointer to the top of the stack, where the initial register values + // are stored. They will be popped off the stack on the first stack switch + // to the goroutine, and will start running tinygo_startTask (this setup + // happens in archInit). + r := (*calleeSavedRegs)(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))])) + // Invoke architecture-specific initialization. - s.archInit(stack, fn, args) + s.archInit(r, fn, args) } +//export tinygo_swapTask +func swapTask(oldStack uintptr, newStack *uintptr) + +// startTask is a small wrapper function that sets up the first (and only) +// argument to the new goroutine and makes sure it is exited when the goroutine +// finishes. +//go:extern tinygo_startTask +var startTask [0]uint8 + //go:linkname runqueuePushBack runtime.runqueuePushBack func runqueuePushBack(*Task) diff --git a/src/internal/task/task_stack_avr.S b/src/internal/task/task_stack_avr.S new file mode 100644 index 00000000..20f98990 --- /dev/null +++ b/src/internal/task/task_stack_avr.S @@ -0,0 +1,99 @@ +.section .bss.tinygo_systemStack +.global tinygo_systemStack +.type tinygo_systemStack, %object +tinygo_systemStack: + .short 0 + +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, r2r3 contain the pc of the to-be-started function and + // r4r5 contain the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + movw r24, r4 + + // Branch to the "goroutine start" function. Note that the Z register is + // call-clobbered, so does not need to be restored after use. + movw Z, r2 + icall + + // After return, exit this goroutine. This is a tail call. +#if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 + // Small memory devices (≤8kB flash) that do not have the long call + // instruction availble will need to use rcall instead. + // Note that they will probably not be able to run more than the main + // goroutine anyway, but this file is compiled for all AVRs so it needs to + // compile at least. + rcall tinygo_pause +#else + // Other devices can (and must) use the regular call instruction. + call tinygo_pause +#endif + +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // r24:r25 = newStack uintptr + // r22:r23 = oldStack *uintptr + + // Save all call-saved registers: + // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers + push r29 // Y + push r28 // Y + push r17 + push r16 + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push r7 + push r6 + push r5 + push r4 + push r3 + push r2 + + // Save the current stack pointer in oldStack. + in r2, 0x3d; SPL + in r3, 0x3e; SPH + movw Y, r22 + std Y+0, r2 + std Y+1, r3 + + // Switch to the new stack pointer. + out 0x3d, r24; SPL + out 0x3e, r25; SPH + + // Load saved register from the new stack. + pop r2 + pop r3 + pop r4 + pop r5 + pop r6 + pop r7 + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + pop r16 + pop r17 + pop r28 // Y + pop r29 // Y + + // Return into the new task, as if tinygo_swapTask was a regular call. + ret diff --git a/src/internal/task/task_stack_avr.go b/src/internal/task/task_stack_avr.go index 08c0b63e..655280ac 100644 --- a/src/internal/task/task_stack_avr.go +++ b/src/internal/task/task_stack_avr.go @@ -4,9 +4,12 @@ package task import "unsafe" +//go:extern tinygo_systemStack +var systemStack uintptr + // calleeSavedRegs is the list of registers that must be saved and restored when -// switching between tasks. Also see scheduler_avr.S that relies on the -// exact layout of this struct. +// switching between tasks. Also see task_stack_avr.S that relies on the exact +// layout of this struct. // // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers type calleeSavedRegs struct { @@ -23,34 +26,15 @@ type calleeSavedRegs struct { pc uintptr } -// registers gets a pointer to the registers stored at the top of the stack. -func (s *state) registers() *calleeSavedRegs { - return (*calleeSavedRegs)(unsafe.Pointer(s.sp + 1)) -} - -// startTask is a small wrapper function that sets up the first (and only) -// argument to the new goroutine and makes sure it is exited when the goroutine -// finishes. -//go:extern tinygo_startTask -var startTask [0]uint8 - // archInit runs architecture-specific setup for the goroutine startup. // Note: adding //go:noinline to work around an AVR backend bug. //go:noinline -func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) { - // Set up the stack canary, a random number that should be checked when - // switching from the task back to the scheduler. The stack canary pointer - // points to the first word of the stack. If it has changed between now and - // the next stack switch, there was a stack overflow. - s.canaryPtr = &stack[0] - *s.canaryPtr = stackCanary - +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). - s.sp = uintptr(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))])) - 1 + s.sp = uintptr(unsafe.Pointer(r)) - 1 // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. - r := s.registers() // Start the function at tinygo_startTask. startTask := uintptr(unsafe.Pointer(&startTask)) @@ -67,20 +51,17 @@ func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) { } func (s *state) resume() { - switchToTask(s.sp) + swapTask(s.sp, &systemStack) } -//export tinygo_switchToTask -func switchToTask(uintptr) - -//export tinygo_switchToScheduler -func switchToScheduler(*uintptr) - func (s *state) pause() { - switchToScheduler(&s.sp) + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) } -//export tinygo_pause -func pause() { - Pause() +// SystemStack returns the system stack pointer when called from a task stack. +// When called from the system stack, it returns 0. +func SystemStack() uintptr { + return systemStack } diff --git a/src/runtime/scheduler_cortexm.S b/src/internal/task/task_stack_cortexm.S similarity index 92% rename from src/runtime/scheduler_cortexm.S rename to src/internal/task/task_stack_cortexm.S index fddddfde..afb23eb2 100644 --- a/src/runtime/scheduler_cortexm.S +++ b/src/internal/task/task_stack_cortexm.S @@ -30,17 +30,6 @@ tinygo_startTask: .cfi_endproc .size tinygo_startTask, .-tinygo_startTask -.section .text.tinygo_getSystemStackPointer -.global tinygo_getSystemStackPointer -.type tinygo_getSystemStackPointer, %function -tinygo_getSystemStackPointer: - .cfi_startproc - // The system stack pointer is always stored in the MSP register. - mrs r0, MSP - bx lr - .cfi_endproc -.size tinygo_getSystemStackPointer, .-tinygo_getSystemStackPointer - .section .text.tinygo_switchToScheduler .global tinygo_switchToScheduler .type tinygo_switchToScheduler, %function diff --git a/src/internal/task/task_stack_cortexm.go b/src/internal/task/task_stack_cortexm.go index 0417b922..eb3fe182 100644 --- a/src/internal/task/task_stack_cortexm.go +++ b/src/internal/task/task_stack_cortexm.go @@ -2,10 +2,13 @@ package task -import "unsafe" +import ( + "device/arm" + "unsafe" +) // calleeSavedRegs is the list of registers that must be saved and restored when -// switching between tasks. Also see scheduler_cortexm.S that relies on the +// switching between tasks. Also see task_stack_cortexm.S that relies on the // exact layout of this struct. type calleeSavedRegs struct { r4 uintptr @@ -20,34 +23,15 @@ type calleeSavedRegs struct { pc uintptr } -// registers gets a pointer to the registers stored at the top of the stack. -func (s *state) registers() *calleeSavedRegs { - return (*calleeSavedRegs)(unsafe.Pointer(s.sp)) -} - -// startTask is a small wrapper function that sets up the first (and only) -// argument to the new goroutine and makes sure it is exited when the goroutine -// finishes. -//go:extern tinygo_startTask -var startTask [0]uint8 - // archInit runs architecture-specific setup for the goroutine startup. -func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) { - // Set up the stack canary, a random number that should be checked when - // switching from the task back to the scheduler. The stack canary pointer - // points to the first word of the stack. If it has changed between now and - // the next stack switch, there was a stack overflow. - s.canaryPtr = &stack[0] - *s.canaryPtr = stackCanary - +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). - s.sp = uintptr(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))])) + s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. - r := s.registers() - // Start the function at tinygo_startTask (defined in src/runtime/scheduler_cortexm.S). + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_cortexm.S). // This assembly code calls a function (passed in r4) with a single argument (passed in r5). // After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) @@ -75,7 +59,8 @@ func (s *state) pause() { switchToScheduler(&s.sp) } -//export tinygo_pause -func pause() { - Pause() +// SystemStack returns the system stack pointer. On Cortex-M, it is always +// available. +func SystemStack() uintptr { + return arm.AsmFull("mrs {}, MSP", nil) } diff --git a/src/runtime/scheduler_avr.S b/src/runtime/scheduler_avr.S deleted file mode 100644 index 83daf2e2..00000000 --- a/src/runtime/scheduler_avr.S +++ /dev/null @@ -1,189 +0,0 @@ -.section .bss.tinygo_systemStack -.global tinygo_systemStack -.type tinygo_systemStack, %object -tinygo_systemStack: - .short 0 - -.section .text.tinygo_startTask -.global tinygo_startTask -.type tinygo_startTask, %function -tinygo_startTask: - // Small assembly stub for starting a goroutine. This is already run on the - // new stack, with the callee-saved registers already loaded. - // Most importantly, r2r3 contain the pc of the to-be-started function and - // r4r5 contain the only argument it is given. Multiple arguments are packed - // into one by storing them in a new allocation. - - // Set the first argument of the goroutine start wrapper, which contains all - // the arguments. - movw r24, r4 - - // Branch to the "goroutine start" function. Note that the Z register is - // call-clobbered, so does not need to be restored after use. - movw Z, r2 - icall - - // After return, exit this goroutine. This is a tail call. -#if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 - // Small memory devices (≤8kB flash) that do not have the long call - // instruction availble will need to use rcall instead. - // Note that they will probably not be able to run more than the main - // goroutine anyway, but this file is compiled for all AVRs so it needs to - // compile at least. - rcall tinygo_pause -#else - // Other devices can (and must) use the regular call instruction. - call tinygo_pause -#endif - -// Get the system stack pointer, independent of whether we're currently on the -// system stack or a task stack. -.global tinygo_getSystemStackPointer -.type tinygo_getSystemStackPointer, %function -tinygo_getSystemStackPointer: - // Load system stack pointer. - lds r24, tinygo_systemStack - lds r25, tinygo_systemStack+1 - - // Compare against 0. - cp r24, r1 - cpc r25, r1 - - // Branch (and then return) if tinygo_systemStack has a non-zero value. - brne 1f - - // tinygo_systemStack is zero, so return the current stack pointer. - in r24, 0x3d; SPL - in r25, 0x3e; SPH - -1: - ret - -.global tinygo_switchToTask -.type tinygo_switchToTask, %function -tinygo_switchToTask: - // The sp parameter is the only parameter, so it will take up r24:r25. - // r24:r25 = sp uintptr - - // Save all call-saved registers: - // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers - push r29 // Y - push r28 // Y - push r17 - push r16 - push r15 - push r14 - push r13 - push r12 - push r11 - push r10 - push r9 - push r8 - push r7 - push r6 - push r5 - push r4 - push r3 - push r2 - - // Save the system stack pointer in a global. - in r2, 0x3d; SPL - in r3, 0x3e; SPH - sts tinygo_systemStack+0, r2 - sts tinygo_systemStack+1, r3 - - // Switch to the task stack pointer. - out 0x3d, r24; SPL - out 0x3e, r25; SPH - - // Load saved register from the task stack. - pop r2 - pop r3 - pop r4 - pop r5 - pop r6 - pop r7 - pop r8 - pop r9 - pop r10 - pop r11 - pop r12 - pop r13 - pop r14 - pop r15 - pop r16 - pop r17 - pop r28 // Y - pop r29 // Y - - // Return into the new task, as if tinygo_switchToScheduler was a regular - // call. - ret - -.global tinygo_switchToScheduler -.type tinygo_switchToScheduler, %function -tinygo_switchToScheduler: - // The sp parameter is the only parameter, so it will take up r24:r25. - // r24:r25 = sp *uintptr - - // Save all call-saved registers on the task stack: - // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers - push r29 // Y - push r28 // Y - push r17 - push r16 - push r15 - push r14 - push r13 - push r12 - push r11 - push r10 - push r9 - push r8 - push r7 - push r6 - push r5 - push r4 - push r3 - push r2 - - // Save the task stack. - in r2, 0x3d; SPL - in r3, 0x3e; SPH - movw Y, r24 - std Y+0, r2 - std Y+1, r3 - - // Switch to the system stack. - lds r2, tinygo_systemStack - lds r3, tinygo_systemStack+1 - out 0x3d, r2; SPL - out 0x3e, r3; SPH - - // Clear tinygo_systemStack to make sure tinygo_getSystemStackPointer knows - // which pointer to return. - sts tinygo_systemStack+0, r1 - sts tinygo_systemStack+1, r1 - - // Load saved register from the system stack. - pop r2 - pop r3 - pop r4 - pop r5 - pop r6 - pop r7 - pop r8 - pop r9 - pop r10 - pop r11 - pop r12 - pop r13 - pop r14 - pop r15 - pop r16 - pop r17 - pop r28 // Y - pop r29 // Y - - // Return into the scheduler, as if tinygo_switchToTask was a regular call. - ret diff --git a/src/runtime/scheduler_tasks.go b/src/runtime/scheduler_tasks.go index 9be63d5d..03009b34 100644 --- a/src/runtime/scheduler_tasks.go +++ b/src/runtime/scheduler_tasks.go @@ -2,7 +2,16 @@ package runtime +import "internal/task" + // getSystemStackPointer returns the current stack pointer of the system stack. // This is not necessarily the same as the current stack pointer. -//export tinygo_getSystemStackPointer -func getSystemStackPointer() uintptr +func getSystemStackPointer() uintptr { + // TODO: this always returns the correct stack on Cortex-M, so don't bother + // comparing against 0. + sp := task.SystemStack() + if sp == 0 { + sp = getCurrentStackPointer() + } + return sp +} diff --git a/targets/avr.json b/targets/avr.json index 75f3e175..fdae4b8b 100644 --- a/targets/avr.json +++ b/targets/avr.json @@ -13,7 +13,7 @@ "-Wl,--gc-sections" ], "extra-files": [ - "src/runtime/gc_avr.S", - "src/runtime/scheduler_avr.S" + "src/internal/task/task_stack_avr.S", + "src/runtime/gc_avr.S" ] } diff --git a/targets/cortex-m.json b/targets/cortex-m.json index 15be7d2d..6f031546 100644 --- a/targets/cortex-m.json +++ b/targets/cortex-m.json @@ -25,8 +25,8 @@ ], "extra-files": [ "src/device/arm/cortexm.s", - "src/runtime/gc_arm.S", - "src/runtime/scheduler_cortexm.S" + "src/internal/task/task_stack_cortexm.S", + "src/runtime/gc_arm.S" ], "gdb": "gdb-multiarch" }