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 /compiler | |
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.
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/defer.go | 12 | ||||
-rw-r--r-- | compiler/llvm.go | 3 | ||||
-rw-r--r-- | compiler/syscall.go | 94 |
3 files changed, 108 insertions, 1 deletions
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) } |