diff --git a/.travis.yml b/.travis.yml index a36e493b..40f2b8c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,11 @@ addons: - llvm-7-dev - clang-7 - libclang-7-dev + - gcc-arm-linux-gnueabi - binutils-arm-none-eabi + - libc6-dev-armel-cross - qemu-system-arm + - qemu-user - gcc-avr - avr-libc diff --git a/compiler/syscall.go b/compiler/syscall.go index bfc99757..6ba6535d 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -5,6 +5,7 @@ package compiler import ( "go/constant" + "strconv" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -14,6 +15,7 @@ import ( // OS/arch. func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value) + var syscallResult llvm.Value switch { case c.GOARCH == "amd64" && c.GOOS == "linux": // Sources: @@ -43,25 +45,60 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, constraints += ",~{rcx},~{r11}" fnType := llvm.FunctionType(c.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) - syscallResult := c.builder.CreateCall(target, args, "") - // Return values: r1, r1, err uintptr - // Pseudocode: - // var err uintptr - // if syscallResult < 0 && syscallResult > -4096 { - // err = -syscallResult - // } - // return syscallResult, 0, err - zero := llvm.ConstInt(c.uintptrType, 0, false) - inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "") - inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096 - hasError := c.builder.CreateAnd(inrange1, inrange2, "") - errResult := c.builder.CreateSelect(hasError, c.builder.CreateNot(syscallResult, ""), zero, "syscallError") - retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) - retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") - retval = c.builder.CreateInsertValue(retval, zero, 1, "") - retval = c.builder.CreateInsertValue(retval, errResult, 2, "") - return retval, nil + syscallResult = c.builder.CreateCall(target, args, "") + case c.GOARCH == "arm" && c.GOOS == "linux": + // Implement the EABI system call convention for Linux. + // Source: syscall(2) man page. + args := []llvm.Value{} + argTypes := []llvm.Type{} + // Constraints will look something like: + // ={r0},0,{r1},{r2},{r7},~{r3} + constraints := "={r0}" + for i, arg := range call.Args[1:] { + constraints += "," + [...]string{ + "0", // tie to output + "{r1}", + "{r2}", + "{r3}", + "{r4}", + "{r5}", + "{r6}", + }[i] + llvmValue, err := c.parseExpr(frame, arg) + if err != nil { + return llvm.Value{}, err + } + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + args = append(args, llvm.ConstInt(c.uintptrType, num, false)) + argTypes = append(argTypes, c.uintptrType) + constraints += ",{r7}" // syscall number + for i := len(call.Args) - 1; i < 4; i++ { + // r0-r3 get clobbered after the syscall returns + constraints += ",~{r" + strconv.Itoa(i) + "}" + } + fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) + syscallResult = c.builder.CreateCall(target, args, "") default: return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH) } + // Return values: r0, r1, err uintptr + // Pseudocode: + // var err uintptr + // if syscallResult < 0 && syscallResult > -4096 { + // err = -syscallResult + // } + // return syscallResult, 0, err + zero := llvm.ConstInt(c.uintptrType, 0, false) + inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "") + inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096 + hasError := c.builder.CreateAnd(inrange1, inrange2, "") + errResult := c.builder.CreateSelect(hasError, c.builder.CreateNot(syscallResult, ""), zero, "syscallError") + retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) + retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") + retval = c.builder.CreateInsertValue(retval, zero, 1, "") + retval = c.builder.CreateInsertValue(retval, errResult, 2, "") + return retval, nil } diff --git a/main_test.go b/main_test.go index 25628550..2882c227 100644 --- a/main_test.go +++ b/main_test.go @@ -42,7 +42,7 @@ func TestCompiler(t *testing.T) { } defer os.RemoveAll(tmpdir) - t.Log("running tests on the host...") + t.Log("running tests on host...") for _, path := range matches { t.Run(path, func(t *testing.T) { runTest(path, tmpdir, "", t) @@ -53,7 +53,17 @@ func TestCompiler(t *testing.T) { return } - t.Log("running tests on the qemu target...") + t.Log("running tests for linux/arm...") + for _, path := range matches { + if path == "testdata/cgo/" { + continue // TODO: improve CGo + } + t.Run(path, func(t *testing.T) { + runTest(path, tmpdir, "arm--linux-gnueabi", t) + }) + } + + t.Log("running tests for emulated cortex-m3...") for _, path := range matches { t.Run(path, func(t *testing.T) { runTest(path, tmpdir, "qemu", t) diff --git a/target.go b/target.go index 7f0a640d..141be076 100644 --- a/target.go +++ b/target.go @@ -218,10 +218,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { } if goarch != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. - if goarch == "arm" { + if goarch == "arm" && goos == "linux" { spec.Linker = "arm-linux-gnueabi-gcc" spec.Objcopy = "arm-linux-gnueabi-objcopy" spec.GDB = "arm-linux-gnueabi-gdb" + spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabi"} } if goarch == "arm64" { spec.Linker = "aarch64-linux-gnu-gcc"