From add014f21b1663b42899fe20f04f851e47688898 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 8 Mar 2020 23:10:21 +0100 Subject: [PATCH] avr: add support for tasks scheduler This adds support for the `-scheduler=tasks` flag for AVR. On most AVR chips you wouldn't want to run a real scheduler but it may be useful in some cases, especially on devices with more RAM. It is disabled by default. --- Makefile | 2 + src/internal/task/task_stack_avr.go | 88 +++++++++++++ src/runtime/scheduler_avr.S | 189 ++++++++++++++++++++++++++++ targets/avr.json | 3 + 4 files changed, 282 insertions(+) create mode 100644 src/internal/task/task_stack_avr.go create mode 100644 src/runtime/scheduler_avr.S diff --git a/Makefile b/Makefile index d76558ff..6a18278c 100644 --- a/Makefile +++ b/Makefile @@ -292,6 +292,8 @@ ifneq ($(AVR), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 diff --git a/src/internal/task/task_stack_avr.go b/src/internal/task/task_stack_avr.go new file mode 100644 index 00000000..cbd5a988 --- /dev/null +++ b/src/internal/task/task_stack_avr.go @@ -0,0 +1,88 @@ +// +build scheduler.tasks,avr + +package task + +import "unsafe" + +const stackSize = 256 + +// 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. +// +// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers +type calleeSavedRegs struct { + r2r3 uintptr + r4r5 uintptr + r6r7 uintptr + r8r9 uintptr + r10r11 uintptr + r12r13 uintptr + r14r15 uintptr + r16r17 uintptr + r28r29 uintptr // Y register + + 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 + + // 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 + + // 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)) + r.pc = startTask/2>>8 | startTask/2<<8 + + // Pass the function to call in r2:r3. + // This function is a compiler-generated wrapper which loads arguments out + // of a struct pointer. See createGoroutineStartWrapper (defined in + // compiler/goroutine.go) for more information. + r.r2r3 = fn + + // Pass the pointer to the arguments struct in r4:45. + r.r4r5 = uintptr(args) +} + +func (s *state) resume() { + switchToTask(s.sp) +} + +//export tinygo_switchToTask +func switchToTask(uintptr) + +//export tinygo_switchToScheduler +func switchToScheduler(*uintptr) + +func (s *state) pause() { + switchToScheduler(&s.sp) +} + +//export tinygo_pause +func pause() { + Pause() +} diff --git a/src/runtime/scheduler_avr.S b/src/runtime/scheduler_avr.S new file mode 100644 index 00000000..83daf2e2 --- /dev/null +++ b/src/runtime/scheduler_avr.S @@ -0,0 +1,189 @@ +.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/targets/avr.json b/targets/avr.json index d3f97a5b..932717e2 100644 --- a/targets/avr.json +++ b/targets/avr.json @@ -9,5 +9,8 @@ "ldflags": [ "-T", "targets/avr.ld", "-Wl,--gc-sections" + ], + "extra-files": [ + "src/runtime/scheduler_avr.S" ] }