From 913a33f8e9e2e275e90f96413a64cfd4f3da2ce4 Mon Sep 17 00:00:00 2001 From: leongross Date: Wed, 7 Aug 2024 15:49:05 +0200 Subject: [PATCH] add initial support for riscv syscalls, add build targets (wip) Signed-off-by: leongross --- GNUmakefile | 2 ++ builder/builder_test.go | 2 ++ compileopts/config.go | 5 ++++ compileopts/target.go | 7 ++++++ compiler/syscall.go | 53 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index d560ae82e3..36abb64963 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -881,6 +881,8 @@ endif @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/riscv32 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/riscv64 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 3fc166c5c8..ac4f58602b 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -59,6 +59,8 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "arm64"}, {GOOS: "linux", GOARCH: "mips"}, {GOOS: "linux", GOARCH: "mipsle"}, + {GOOS: "linux", GOARCH: "riscv"}, + {GOOS: "linux", GOARCH: "riscv64"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, diff --git a/compileopts/config.go b/compileopts/config.go index a5ab7cd8f9..3112b3837b 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -220,6 +220,11 @@ func CanonicalArchName(triple string) string { if arch == "mipsel" { return "mips" } + + if arch == "riscv32" { + return "riscv" + } + return arch } diff --git a/compileopts/target.go b/compileopts/target.go index fdb29e2109..0b4e6b5044 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -199,6 +199,8 @@ func LoadTarget(options *Options) (*TargetSpec, error) { llvmarch = "mipsel" case "wasm": llvmarch = "wasm32" + case "riscv": + llvmarch = "riscv64" default: llvmarch = options.GOARCH } @@ -344,6 +346,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "-mnontrapping-fptoint", "-msign-ext", ) + case "riscv64": + // TODO: are these the features we really need? What does SiFive use? what does qemu support? + spec.CPU = "generic-rv64" + spec.Features = "+a,+c,+d,+f,+m,+q,+rvc,+zbb,+zbp" + spec.CFlags = append(spec.CFlags, "-march=rv64gc") } if goos == "darwin" { spec.Linker = "ld.lld" diff --git a/compiler/syscall.go b/compiler/syscall.go index d9d21c3cf5..80e66a76be 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -145,7 +145,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { // 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 + // Register r7 is both an input parameter 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 @@ -156,6 +156,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { 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. @@ -229,6 +230,56 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { result := b.CreateSelect(isError, negativeResult, resultCode, "") return result, nil + case (b.GOARCH == "riscv" || b.GOARCH == "riscv64") && b.GOOS == "linux": + // https://stackoverflow.com/questions/59800430/risc-v-ecall-syscall-calling-convention-on-pk-linux + // https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/syscall.2?h=man-pages-5.04#n200 + // https://pdos.csail.mit.edu/6.S081/2021/slides/6s081-lec-syscall.pdf + // https://git.musl-libc.org/cgit/musl/tree/arch/riscv64/syscall_arch.h + // https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf + args := []llvm.Value{num} + argTypes := []llvm.Type{} + constraints := "={a7}" // syscall number + for i, arg := range call.Args[1:] { + constraints += "," + [...]string{ + "{a0}", + "{a1}", + "{a2}", + "{a3}", + "{a4}", + "{a5}", + }[i] + llvmValue := b.getValue(arg, getPos(call)) + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + args = append(args, num) + argTypes = append(argTypes, b.uintptrType) + + // constrain registers used for syscall/ecall + for i := len(call.Args) - 1; i < 4; i++ { + constraints += ",~{a" + strconv.Itoa(i) + "}" + } + + // constrain caller responsible registers + // See Table 18.2: RISC-V calling convention register usage + // TODO: does llvm take their stack usage into account? + // TODO: are only the registers needed saved here or all? can llvm determine this itself? + + // temporary integer registers + for i := 0; i < 8; i++ { + constraints += ",~{t" + strconv.Itoa(i) + "}" + } + + // temporary floating-point registers + for i := 0; i < 12; i++ { + constraints += ",~{ft" + strconv.Itoa(i) + "}" + } + + // generate function + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) + target := llvm.InlineAsm(fnType, "ecall", constraints, true, false, 0, false) + return b.CreateCall(fnType, target, args, ""), nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) }