diff --git a/compileopts/target.go b/compileopts/target.go index ad72252c3..81de4ed07 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -239,14 +239,16 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // No target spec available. Use the default one, useful on most systems // with a regular OS. spec := TargetSpec{ - Triple: triple, - GOOS: goos, - GOARCH: goarch, - BuildTags: []string{goos, goarch}, - Linker: "cc", - CFlags: []string{"--target=" + triple}, - GDB: []string{"gdb"}, - PortReset: "false", + Triple: triple, + GOOS: goos, + GOARCH: goarch, + BuildTags: []string{goos, goarch}, + Scheduler: "tasks", + Linker: "cc", + DefaultStackSize: 1024 * 64, // 64kB + CFlags: []string{"--target=" + triple}, + GDB: []string{"gdb"}, + PortReset: "false", } if goos == "darwin" { spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") @@ -256,6 +258,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { } if goarch != "wasm" { spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S") + spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S") } if goarch != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 55090a9b1..20a1ad6d8 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -34,6 +34,9 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p } else { // The stack size is fixed at compile time. By emitting it here as a // constant, it can be optimized. + if b.DefaultStackSize == 0 { + b.addError(pos, "default stack size for goroutines is not set") + } stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) } case "coroutines": diff --git a/src/internal/task/task_stack_386.S b/src/internal/task/task_stack_386.S new file mode 100644 index 000000000..c82213e98 --- /dev/null +++ b/src/internal/task/task_stack_386.S @@ -0,0 +1,58 @@ +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + .cfi_startproc + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, EBX contain the pc of the to-be-started function and + // ESI contain the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Indicate to the unwinder that there is nothing to unwind, this is the + // root frame. It avoids bogus extra frames in GDB. + .cfi_undefined eip + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + pushl %esi + + // Branch to the "goroutine start" function. + calll *%ebx + + // Rebalance the stack (to undo the above push). + addl $4, %esp + + // After return, exit this goroutine. This is a tail call. + jmp tinygo_pause + .cfi_endproc + +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + movl 4(%esp), %eax // newStack uintptr + movl 8(%esp), %ecx // oldStack *uintptr + // More information on the calling convention: + // https://wiki.osdev.org/System_V_ABI#i386 + + // Save all callee-saved registers: + pushl %ebp + pushl %edi + pushl %esi + pushl %ebx + + // Save the current stack pointer in oldStack. + movl %esp, (%ecx) + + // Switch to the new stack pointer. + movl %eax, %esp + + // Load saved register from the new stack. + popl %ebx + popl %esi + popl %edi + popl %ebp + + // Return into the new task, as if tinygo_swapTask was a regular call. + ret diff --git a/src/internal/task/task_stack_386.go b/src/internal/task/task_stack_386.go new file mode 100644 index 000000000..3f1cd3ee0 --- /dev/null +++ b/src/internal/task/task_stack_386.go @@ -0,0 +1,59 @@ +// +build scheduler.tasks,386 + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_386.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + ebx uintptr + esi uintptr + edi uintptr + ebp uintptr + + pc uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +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(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in + // src/internal/task/task_stack_386.S). This assembly code calls a function + // (passed in EBX) with a single argument (passed in ESI). After the + // function returns, it calls Pause(). + r.pc = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in EBX. + // 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.ebx = fn + + // Pass the pointer to the arguments struct in ESI. + r.esi = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// 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/internal/task/task_stack_amd64.S b/src/internal/task/task_stack_amd64.S new file mode 100644 index 000000000..44b5f065b --- /dev/null +++ b/src/internal/task/task_stack_amd64.S @@ -0,0 +1,74 @@ +#ifdef __MACH__ // Darwin +.global _tinygo_startTask +_tinygo_startTask: +#else // Linux etc +.section .text.tinygo_startTask +.global tinygo_startTask +tinygo_startTask: +#endif + .cfi_startproc + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, r12 contain the pc of the to-be-started function and + // r13 contain the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Indicate to the unwinder that there is nothing to unwind, this is the + // root frame. It avoids bogus extra frames in GDB like here: + // #10 0x00000000004277b6 in () at [...] + // #11 0x00000000004278f3 in tinygo_startTask () at [...] + // #12 0x0000000000002030 in ?? () + // #13 0x0000000000000071 in ?? () + .cfi_undefined rip + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + movq %r13, %rdi + + // Branch to the "goroutine start" function. + callq *%r12 + + // After return, exit this goroutine. This is a tail call. + #ifdef __MACH__ + jmp _tinygo_pause + #else + jmp tinygo_pause + #endif + .cfi_endproc + +#ifdef __MACH__ // Darwin +.global _tinygo_swapTask +_tinygo_swapTask: +#else // Linux etc +.global tinygo_swapTask +.section .text.tinygo_swapTask +tinygo_swapTask: +#endif + // This function gets the following parameters: + // %rdi = newStack uintptr + // %rsi = oldStack *uintptr + + // Save all callee-saved registers: + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + pushq %rbp + pushq %rbx + + // Save the current stack pointer in oldStack. + movq %rsp, (%rsi) + + // Switch to the new stack pointer. + movq %rdi, %rsp + + // Load saved register from the new stack. + popq %rbx + popq %rbp + popq %r12 + popq %r13 + popq %r14 + popq %r15 + + // Return into the new task, as if tinygo_swapTask was a regular call. + ret diff --git a/src/internal/task/task_stack_amd64.go b/src/internal/task/task_stack_amd64.go new file mode 100644 index 000000000..7f1d132ed --- /dev/null +++ b/src/internal/task/task_stack_amd64.go @@ -0,0 +1,61 @@ +// +build scheduler.tasks,amd64 + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_amd64.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + rbx uintptr + rbp uintptr + r12 uintptr + r13 uintptr + r14 uintptr + r15 uintptr + + pc uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +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(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in + // src/internal/task/task_stack_amd64.S). This assembly code calls a + // function (passed in r12) with a single argument (passed in r13). After + // the function returns, it calls Pause(). + r.pc = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in r12. + // 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.r12 = fn + + // Pass the pointer to the arguments struct in r13. + r.r13 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// 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/internal/task/task_stack_arm.S b/src/internal/task/task_stack_arm.S new file mode 100644 index 000000000..3a1e3f0ae --- /dev/null +++ b/src/internal/task/task_stack_arm.S @@ -0,0 +1,51 @@ +// Only generate .debug_frame, don't generate .eh_frame. +.cfi_sections .debug_frame + +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + .cfi_startproc + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, r4 contains the pc of the to-be-started function and r5 + // contains the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Indicate to the unwinder that there is nothing to unwind, this is the + // root frame. It avoids the following (bogus) error message in GDB: + // Backtrace stopped: previous frame identical to this frame (corrupt stack?) + .cfi_undefined lr + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + mov r0, r5 + + // Branch to the "goroutine start" function. By using blx instead of bx, + // we'll return here instead of tail calling. + blx r4 + + // After return, exit this goroutine. This is a tail call. + bl tinygo_pause + .cfi_endproc +.size tinygo_startTask, .-tinygo_startTask + +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // r0 = newStack uintptr + // r1 = oldStack *uintptr + + // Save all callee-saved registers: + push {r4-r11, lr} + + // Save the current stack pointer in oldStack. + str sp, [r1] + + // Switch to the new stack pointer. + mov sp, r0 + + // Load state from new task and branch to the previous position in the + // program. + pop {r4-r11, pc} diff --git a/src/internal/task/task_stack_arm.go b/src/internal/task/task_stack_arm.go new file mode 100644 index 000000000..acd63ece8 --- /dev/null +++ b/src/internal/task/task_stack_arm.go @@ -0,0 +1,61 @@ +// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_arm.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + r4 uintptr + r5 uintptr + r6 uintptr + r7 uintptr + r8 uintptr + r9 uintptr + r10 uintptr + r11 uintptr + + pc uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +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(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm.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)) + + // Pass the function to call in r4. + // 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.r4 = fn + + // Pass the pointer to the arguments struct in r5. + r.r5 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// 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/internal/task/task_stack_arm64.S b/src/internal/task/task_stack_arm64.S new file mode 100644 index 000000000..29042860f --- /dev/null +++ b/src/internal/task/task_stack_arm64.S @@ -0,0 +1,59 @@ +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + .cfi_startproc + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, x19 contains the pc of the to-be-started function and + // x20 contains the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Indicate to the unwinder that there is nothing to unwind, this is the + // root frame. It avoids the following (bogus) error message in GDB: + // Backtrace stopped: previous frame identical to this frame (corrupt stack?) + .cfi_undefined lr + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + mov x0, x20 + + // Branch to the "goroutine start" function. By using blx instead of bx, + // we'll return here instead of tail calling. + blr x19 + + // After return, exit this goroutine. This is a tail call. + b tinygo_pause + .cfi_endproc +.size tinygo_startTask, .-tinygo_startTask + +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // x0 = newStack uintptr + // x1 = oldStack *uintptr + + // Save all callee-saved registers: + stp x19, x20, [sp, #-96]! + stp x21, x22, [sp, #16] + stp x23, x24, [sp, #32] + stp x25, x26, [sp, #48] + stp x27, x28, [sp, #64] + stp x29, x30, [sp, #80] + + // Save the current stack pointer in oldStack. + mov x8, sp + str x8, [x1] + + // Switch to the new stack pointer. + mov sp, x0 + + // Restore stack state and return. + ldp x29, x30, [sp, #80] + ldp x27, x28, [sp, #64] + ldp x25, x26, [sp, #48] + ldp x23, x24, [sp, #32] + ldp x21, x22, [sp, #16] + ldp x19, x20, [sp], #96 + ret diff --git a/src/internal/task/task_stack_arm64.go b/src/internal/task/task_stack_arm64.go new file mode 100644 index 000000000..d65c30c49 --- /dev/null +++ b/src/internal/task/task_stack_arm64.go @@ -0,0 +1,64 @@ +// +build scheduler.tasks,arm64 + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_arm64.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + x19 uintptr + x20 uintptr + x21 uintptr + x22 uintptr + x23 uintptr + x24 uintptr + x25 uintptr + x26 uintptr + x27 uintptr + x28 uintptr + x29 uintptr + + pc uintptr // aka x30 aka LR +} + +// archInit runs architecture-specific setup for the goroutine startup. +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(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64.S). + // This assembly code calls a function (passed in x19) with a single argument + // (passed in x20). After the function returns, it calls Pause(). + r.pc = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in x19. + // 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.x19 = fn + + // Pass the pointer to the arguments struct in x20. + r.x20 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// 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/internal/task/task_stack_cortexm.go b/src/internal/task/task_stack_cortexm.go index eb3fe1827..1d9969796 100644 --- a/src/internal/task/task_stack_cortexm.go +++ b/src/internal/task/task_stack_cortexm.go @@ -2,6 +2,12 @@ package task +// Note that this is almost the same as task_stack_arm.go, but it uses the MSP +// register to store the system stack pointer instead of a global variable. The +// big advantage of this is that interrupts always execute with MSP (and not +// PSP, which is used for goroutines) so that goroutines do not need extra stack +// space for interrupts. + import ( "device/arm" "unsafe" diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json index 87d6696e7..6e3c44c7f 100644 --- a/targets/nintendoswitch.json +++ b/targets/nintendoswitch.json @@ -1,6 +1,7 @@ { "llvm-target": "aarch64", "build-tags": ["nintendoswitch", "arm64"], + "scheduler": "tasks", "goos": "linux", "goarch": "arm64", "linker": "ld.lld", @@ -9,6 +10,7 @@ "gc": "conservative", "relocation-model": "pic", "cpu": "cortex-a57", + "default-stack-size": 2048, "cflags": [ "-target", "aarch64-unknown-none", "-mcpu=cortex-a57", @@ -26,6 +28,7 @@ "linkerscript": "targets/nintendoswitch.ld", "extra-files": [ "targets/nintendoswitch.s", + "src/internal/task/task_stack_arm64.S", "src/runtime/gc_arm64.S", "src/runtime/runtime_nintendoswitch.s" ]