diff --git a/builder/builder_test.go b/builder/builder_test.go index f8bdd5134..5029d4d01 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -56,6 +56,7 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm64"}, + {GOOS: "linux", GOARCH: "mips"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, diff --git a/compileopts/target.go b/compileopts/target.go index 30573a863..83911e8a0 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -278,6 +278,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { case "arm64": spec.CPU = "generic" spec.Features = "+neon" + case "mips": + spec.CPU = "mips32r2" + spec.Features = "+mips32r2,-noabicalls" } if goos == "darwin" { spec.Linker = "ld.lld" @@ -350,6 +353,8 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Emulator = "qemu-arm {}" case "arm64": spec.Emulator = "qemu-aarch64 {}" + case "mips": + spec.Emulator = "qemu-mips {}" } } } diff --git a/compiler/defer.go b/compiler/defer.go index 99b314353..a769b1f96 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -31,7 +31,7 @@ func (b *builder) supportsRecover() bool { // proposal of WebAssembly: // https://github.com/WebAssembly/exception-handling return false - case "riscv64", "xtensa": + case "riscv64", "xtensa", "mips": // TODO: add support for these architectures return false default: diff --git a/main_test.go b/main_test.go index 72f3c4bbc..96b336caa 100644 --- a/main_test.go +++ b/main_test.go @@ -34,6 +34,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "MIPSLinux": "linux/mips", } var sema = make(chan struct{}, runtime.NumCPU()) diff --git a/src/internal/task/task_stack_mips.S b/src/internal/task/task_stack_mips.S new file mode 100644 index 000000000..285ca4369 --- /dev/null +++ b/src/internal/task/task_stack_mips.S @@ -0,0 +1,69 @@ +.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, s0 contains the pc of the to-be-started function and s1 + // contains 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. + move $a0, $s1 + + // Branch to the "goroutine start" function. Use jalr to write the return + // address to ra so we'll return here after the goroutine exits. + jalr $s0 + nop + nop + + // After return, exit this goroutine. This is a tail call. + j tinygo_pause + nop + nop + +.section .text.tinygo_swapTask +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // a0 = newStack uintptr + // a1 = oldStack *uintptr + + // Push all callee-saved registers. + addiu $sp, $sp, -40 + sw $ra, 36($sp) + sw $s8, 32($sp) + sw $s7, 28($sp) + sw $s6, 24($sp) + sw $s5, 20($sp) + sw $s4, 16($sp) + sw $s3, 12($sp) + sw $s2, 8($sp) + sw $s1, 4($sp) + sw $s0, ($sp) + + // Save the current stack pointer in oldStack. + sw $sp, 0($a1) + + // Switch to the new stack pointer. + move $sp, $a0 + + // Pop all saved registers from this new stack. + lw $ra, 36($sp) + lw $s8, 32($sp) + lw $s7, 28($sp) + lw $s6, 24($sp) + lw $s5, 20($sp) + lw $s4, 16($sp) + lw $s3, 12($sp) + lw $s2, 8($sp) + lw $s1, 4($sp) + lw $s0, ($sp) + addiu $sp, $sp, 40 + + // Return into the task. + jalr $ra + nop + nop diff --git a/src/internal/task/task_stack_mips.go b/src/internal/task/task_stack_mips.go new file mode 100644 index 000000000..706f316e0 --- /dev/null +++ b/src/internal/task/task_stack_mips.go @@ -0,0 +1,61 @@ +//go:build scheduler.tasks && mips + +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_mips.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + s0 uintptr + s1 uintptr + s2 uintptr + s3 uintptr + s4 uintptr + s5 uintptr + s6 uintptr + s7 uintptr + s8 uintptr + ra 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_mips.S). + // This assembly code calls a function (passed in s0) with a single argument + // (passed in s1). After the function returns, it calls Pause(). + r.ra = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in s0. + // 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.s0 = fn + + // Pass the pointer to the arguments struct in s1. + r.s1 = 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/runtime/arch_mips.go b/src/runtime/arch_mips.go new file mode 100644 index 000000000..d9a41d266 --- /dev/null +++ b/src/runtime/arch_mips.go @@ -0,0 +1,19 @@ +package runtime + +const GOARCH = "mips" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const flag_linux_MAP_ANONYMOUS = 0x800 + +// Align on the maximum alignment for this platform (double). +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/asm_mips.S b/src/runtime/asm_mips.S new file mode 100644 index 000000000..e69de29bb diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 67ae21691..db27848fe 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -14,7 +14,7 @@ const ( flag_PROT_READ = 0x1 flag_PROT_WRITE = 0x2 flag_MAP_PRIVATE = 0x2 - flag_MAP_ANONYMOUS = 0x20 + flag_MAP_ANONYMOUS = flag_linux_MAP_ANONYMOUS ) // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h