diff options
author | Ayke van Laethem <[email protected]> | 2024-06-24 20:22:16 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2024-07-22 16:21:26 +0200 |
commit | 725518f007663d6742f1e92ebc2d6d88418ccf61 (patch) | |
tree | 01489f64270419ddb28520cd040b24babddab475 | |
parent | 04d1261f8a48b18a4404c52f858c35c0668316cd (diff) | |
download | tinygo-725518f007663d6742f1e92ebc2d6d88418ccf61.tar.gz tinygo-725518f007663d6742f1e92ebc2d6d88418ccf61.zip |
all: add linux/mipsle support
This adds linux/mipsle (little endian Mips) support to TinyGo.
It also adds experimental linux/mips (big-endian) support. It doesn't
quite work yet, some parts of the standard library (like the reflect
package) currently seem to assume a little-endian system.
-rw-r--r-- | .github/workflows/build-macos.yml | 2 | ||||
-rw-r--r-- | .github/workflows/linux.yml | 6 | ||||
-rw-r--r-- | .github/workflows/windows.yml | 2 | ||||
-rw-r--r-- | GNUmakefile | 4 | ||||
-rw-r--r-- | builder/builder_test.go | 2 | ||||
-rw-r--r-- | builder/library.go | 3 | ||||
-rw-r--r-- | compileopts/config.go | 5 | ||||
-rw-r--r-- | compileopts/target.go | 20 | ||||
-rw-r--r-- | compiler/defer.go | 12 | ||||
-rw-r--r-- | compiler/llvm.go | 3 | ||||
-rw-r--r-- | compiler/syscall.go | 94 | ||||
-rw-r--r-- | main_test.go | 7 | ||||
-rw-r--r-- | src/internal/task/task_stack_mipsx.S | 66 | ||||
-rw-r--r-- | src/internal/task/task_stack_mipsx.go | 61 | ||||
-rw-r--r-- | src/runtime/arch_386.go | 2 | ||||
-rw-r--r-- | src/runtime/arch_amd64.go | 2 | ||||
-rw-r--r-- | src/runtime/arch_arm.go | 2 | ||||
-rw-r--r-- | src/runtime/arch_arm64.go | 2 | ||||
-rw-r--r-- | src/runtime/arch_mips.go | 21 | ||||
-rw-r--r-- | src/runtime/arch_mipsle.go | 21 | ||||
-rw-r--r-- | src/runtime/asm_mipsx.S | 40 | ||||
-rw-r--r-- | src/runtime/os_linux.go | 2 | ||||
-rw-r--r-- | src/runtime/runtime_unix.go | 3 |
23 files changed, 371 insertions, 11 deletions
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7acd2e5b7..17fe3852a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-${{ matrix.os }}-v2 + key: llvm-build-18-${{ matrix.os }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8bb92e083..218019224 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-alpine-v1 + key: llvm-build-18-linux-alpine-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -223,7 +223,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-asserts-v1 + key: llvm-build-18-linux-asserts-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -336,7 +336,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-${{ matrix.goarch }}-v1 + key: llvm-build-18-linux-${{ matrix.goarch }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 2b26a9b1c..0d144304d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -66,7 +66,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-windows-v1 + key: llvm-build-18-windows-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/GNUmakefile b/GNUmakefile index f2f5e2f53..f06f28a18 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -245,7 +245,7 @@ llvm-source: $(LLVM_PROJECTDIR)/llvm # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: - mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) + mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) # Build LLVM. $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja @@ -835,6 +835,7 @@ endif $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo + GOOS=linux GOARCH=mips $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo @@ -882,6 +883,7 @@ endif @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl diff --git a/builder/builder_test.go b/builder/builder_test.go index c7b863873..3fc166c5c 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -57,6 +57,8 @@ 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: "linux", GOARCH: "mipsle"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, diff --git a/builder/library.go b/builder/library.go index e0ac31a74..83fa3db94 100644 --- a/builder/library.go +++ b/builder/library.go @@ -182,6 +182,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ if strings.HasPrefix(target, "riscv64-") { args = append(args, "-march=rv64gc") } + if strings.HasPrefix(target, "mips") { + args = append(args, "-fno-pic") + } var once sync.Once diff --git a/compileopts/config.go b/compileopts/config.go index ab6de4e44..893fbf001 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -212,7 +212,10 @@ func (c *Config) RP2040BootPatch() bool { func MuslArchitecture(triple string) string { arch := strings.Split(triple, "-")[0] if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { - arch = "arm" + return "arm" + } + if arch == "mipsel" { + return "mips" } return arch } diff --git a/compileopts/target.go b/compileopts/target.go index 646029e44..fdb29e210 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -193,6 +193,10 @@ func LoadTarget(options *Options) (*TargetSpec, error) { default: return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) } + case "mips": + llvmarch = "mips" + case "mipsle": + llvmarch = "mipsel" case "wasm": llvmarch = "wasm32" default: @@ -327,6 +331,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { } else { // linux spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" } + case "mips", "mipsle": + spec.CPU = "mips32r2" + spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" + spec.CFlags = append(spec.CFlags, "-fno-pic") case "wasm": spec.CPU = "generic" spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" @@ -419,8 +427,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // operating systems so we need separate assembly files. suffix = "_windows" } - spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S") - spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S") + asmGoarch := goarch + if goarch == "mips" || goarch == "mipsle" { + asmGoarch = "mipsx" + } + spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S") + spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S") } if goarch != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. @@ -438,6 +450,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Emulator = "qemu-arm {}" case "arm64": spec.Emulator = "qemu-aarch64 {}" + case "mips": + spec.Emulator = "qemu-mips {}" + case "mipsle": + spec.Emulator = "qemu-mipsel {}" } } } diff --git a/compiler/defer.go b/compiler/defer.go index e1ff2f58e..df8686957 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -187,6 +187,18 @@ std z+5, r29 ldi r24, 0 1:` constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}" + case "mips": + // $4 flag (zero or non-zero) + // $5 defer frame + asmString = ` +.set noat +move $$4, $$zero +jal 1f +1: +addiu $$ra, 8 +sw $$ra, 4($$5) +.set at` + constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31},~{memory}" case "riscv32": asmString = ` la a2, 1f diff --git a/compiler/llvm.go b/compiler/llvm.go index 5d4eaaa64..9e3a95d4f 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -429,6 +429,9 @@ func (c *compilerContext) archFamily() string { if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { return "arm" } + if arch == "mipsel" { + return "mips" + } return arch } diff --git a/compiler/syscall.go b/compiler/syscall.go index bf3955df2..d9d21c3cf 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -12,7 +12,8 @@ import ( // createRawSyscall creates a system call with the provided system call number // and returns the result as a single integer (the system call result). The -// result is not further interpreted. +// result is not further interpreted (with the exception of MIPS to use the same +// return value everywhere). func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { num := b.getValue(call.Args[0], getPos(call)) switch { @@ -137,6 +138,97 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux": + // Implement the system call convention for Linux. + // Source: syscall(2) man page and musl: + // https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h + // Also useful: + // https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall + // The syscall number goes in r2, the result also in r2. + // Register r7 is both an input paramter and an output parameter: if it + // is non-zero, the system call failed and r2 is the error code. + // The code below implements the O32 syscall ABI, not the N32 ABI. It + // could implement both at the same time if needed (like what appears to + // be done in musl) by forcing arg5-arg7 into the right registers but + // letting the compiler decide the registers should result in _slightly_ + // faster and smaller code. + args := []llvm.Value{num} + argTypes := []llvm.Type{b.uintptrType} + constraints := "={$2},={$7},0" + syscallParams := call.Args[1:] + if len(syscallParams) > 7 { + // There is one syscall that uses 7 parameters: sync_file_range. + // But only 7, not more. Go however only has Syscall6 and Syscall9. + // Therefore, we can ignore the remaining parameters. + syscallParams = syscallParams[:7] + } + for i, arg := range syscallParams { + constraints += "," + [...]string{ + "{$4}", // arg1 + "{$5}", // arg2 + "{$6}", // arg3 + "1", // arg4, error return + "r", // arg5 on the stack + "r", // arg6 on the stack + "r", // arg7 on the stack + }[i] + llvmValue := b.getValue(arg, getPos(call)) + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + // Create assembly code. + // Parameters beyond the first 4 are passed on the stack instead of in + // registers in the O32 syscall ABI. + // We need ".set noat" because LLVM might pick register $1 ($at) as the + // register for a parameter and apparently this is not allowed on MIPS + // unless you use this specific pragma. + asm := "syscall" + switch len(syscallParams) { + case 5: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 6: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 7: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "sw $9, 24($$sp)\n" + // arg7 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + } + constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}" + returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false) + fnType := llvm.FunctionType(returnType, argTypes, false) + target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false) + call := b.CreateCall(fnType, target, args, "") + resultCode := b.CreateExtractValue(call, 0, "") // r2 + errorFlag := b.CreateExtractValue(call, 1, "") // r7 + // Pseudocode to return the result with the same convention as other + // archs: + // return (errorFlag != 0) ? -resultCode : resultCode; + // At least on QEMU with the O32 ABI, the error code is always positive. + zero := llvm.ConstInt(b.uintptrType, 0, false) + isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "") + negativeResult := b.CreateSub(zero, resultCode, "") + result := b.CreateSelect(isError, negativeResult, resultCode, "") + return result, nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } diff --git a/main_test.go b/main_test.go index c294b3a8a..057e008e8 100644 --- a/main_test.go +++ b/main_test.go @@ -37,6 +37,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "MIPSLinux": "linux/mipsle", "WASIp1": "wasip1/wasm", } @@ -211,6 +212,12 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if options.GOOS == "linux" && (options.GOARCH == "mips" || options.GOARCH == "mipsle") { + if name == "atomic.go" { + // 64-bit atomic operations aren't currently supported on MIPS. + continue + } + } if options.Target == "simavr" { // Not all tests are currently supported on AVR. // Skip the ones that aren't. diff --git a/src/internal/task/task_stack_mipsx.S b/src/internal/task/task_stack_mipsx.S new file mode 100644 index 000000000..903a847c7 --- /dev/null +++ b/src/internal/task/task_stack_mipsx.S @@ -0,0 +1,66 @@ +.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 + + // After return, exit this goroutine. This is a tail call. + j tinygo_pause + 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 diff --git a/src/internal/task/task_stack_mipsx.go b/src/internal/task/task_stack_mipsx.go new file mode 100644 index 000000000..2a7fb5cbf --- /dev/null +++ b/src/internal/task/task_stack_mipsx.go @@ -0,0 +1,61 @@ +//go:build scheduler.tasks && (mips || mipsle) + +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_mipsle.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_386.go b/src/runtime/arch_386.go index c4dbdf166..4e9cce72b 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_amd64.go b/src/runtime/arch_amd64.go index 996476013..3bb03e3c7 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned // memory, thus the result must be 16-byte aligned. diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 33a7513b0..e28e85410 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -11,6 +11,8 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index ac28ab7c3..4e798e36b 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_mips.go b/src/runtime/arch_mips.go new file mode 100644 index 000000000..bfaf890ae --- /dev/null +++ b/src/runtime/arch_mips.go @@ -0,0 +1,21 @@ +package runtime + +const GOARCH = "mips" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const linux_MAP_ANONYMOUS = 0x800 + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/arch_mipsle.go b/src/runtime/arch_mipsle.go new file mode 100644 index 000000000..b6bf7d516 --- /dev/null +++ b/src/runtime/arch_mipsle.go @@ -0,0 +1,21 @@ +package runtime + +const GOARCH = "mipsle" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const linux_MAP_ANONYMOUS = 0x800 + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/asm_mipsx.S b/src/runtime/asm_mipsx.S new file mode 100644 index 000000000..e38064364 --- /dev/null +++ b/src/runtime/asm_mipsx.S @@ -0,0 +1,40 @@ +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +.type tinygo_scanCurrentStack, %function +tinygo_scanCurrentStack: + // Push callee-saved registers onto the stack. + 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) + + // Scan the stack. + jal tinygo_scanstack + move $a0, $sp // in the branch delay slot + + // Restore return address. + lw $ra, 36($sp) + + // Restore stack state. + addiu $sp, $sp, 40 + + // Return to the caller. + jalr $ra + nop + +.section .text.tinygo_longjmp +.global tinygo_longjmp +tinygo_longjmp: + // Note: the code we jump to assumes a0 is non-zero, which is already the + // case because that's the defer frame pointer. + lw $sp, 0($a0) // jumpSP + lw $a1, 4($a0) // jumpPC + jr $a1 + nop diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 7218ea653..403f00246 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 = linux_MAP_ANONYMOUS // different on alpha, hppa, mips, xtensa ) // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 50eb11dea..8c5a42ff7 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -229,6 +229,9 @@ func preinit() { // heap size. // This can happen on 32-bit systems. heapMaxSize /= 2 + if heapMaxSize < 4096 { + runtimePanic("cannot allocate heap memory") + } continue } heapStart = uintptr(addr) |