diff --git a/compiler/compiler.go b/compiler/compiler.go index 45e6c8a54b..7fd34f0b99 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1981,9 +1981,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) case (strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscall")) && name != "syscall.SyscallN": - if b.GOOS != "darwin" { - return b.createSyscall(instr) - } + return b.createSyscall(instr) case name == "syscall.syscalln": if b.GOOS == "windows" { return b.createSyscalln(instr) diff --git a/compiler/syscall.go b/compiler/syscall.go index 946acdecb0..7ac36c6cbb 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -269,6 +269,24 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { retval = b.CreateInsertValue(retval, zero, 1, "") retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil + case "darwin": + r1, r2, errFlag, err := b.createDarwinRawSyscall(call) + if err != nil { + return llvm.Value{}, err + } + // Darwin returns (r1, r2, err) where err is the raw errno on + // failure (carry flag set) and r1=-1 in that case. On success, + // err=0 and r1/r2 carry the syscall return values. + zero := llvm.ConstInt(b.uintptrType, 0, false) + minusOne := llvm.ConstInt(b.uintptrType, ^uint64(0), false) // -1 as uintptr + finalR1 := b.CreateSelect(errFlag, minusOne, r1, "") + finalR2 := b.CreateSelect(errFlag, zero, r2, "") + finalErr := b.CreateSelect(errFlag, r1, zero, "syscallError") + retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)) + retval = b.CreateInsertValue(retval, finalR1, 0, "") + retval = b.CreateInsertValue(retval, finalR2, 1, "") + retval = b.CreateInsertValue(retval, finalErr, 2, "") + return retval, nil case "windows": // On Windows, syscall.Syscall* is basically just a function pointer // call. This is complicated in gc because of stack switching and the @@ -546,3 +564,104 @@ func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value { // abi.FuncPCABI0 returns). return b.CreatePtrToInt(llvmFn, b.uintptrType, "") } + +// createDarwinRawSyscall emits a raw kernel syscall for darwin and returns +// (r1, r2, errFlag). errFlag is an i1 derived from the carry flag. It is +// called only from createSyscall's darwin branch. +// +// References (upstream Go): +// +// src/syscall/asm_darwin_amd64.s +// src/syscall/asm_darwin_arm64.s +func (b *builder) createDarwinRawSyscall(call *ssa.CallCommon) (r1, r2, errFlag llvm.Value, err error) { + num := b.getValue(call.Args[0], getPos(call)) + switch b.GOARCH { + case "amd64": + // AMD64 darwin syscall ABI: + // - syscall number in RAX, ORed with 0x2000000 (BSD class) + // - args in RDI, RSI, RDX, R10, R8, R9 + // - SYSCALL instruction + // - primary return in RAX, secondary in RDX + // - carry flag set on error + args := []llvm.Value{ + b.CreateAdd(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), ""), + } + argTypes := []llvm.Type{b.uintptrType} + constraints := "={rax},={rdx},={@ccc},0" + for i, arg := range call.Args[1:] { + constraints += "," + [...]string{ + "{rdi}", + "{rsi}", + "{rdx}", + "{r10}", + "{r8}", + "{r9}", + }[i] + llvmValue := b.getValue(arg, getPos(call)) + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + constraints += ",~{rcx},~{r11}" + // LLVM's x86 backend requires the flag-output (={@ccc}) to be at least + // i32; passing i1 directly triggers "Glue output operand is of invalid + // type" during instruction selection. Receive i32 and truncate. + retType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.ctx.Int32Type()}, false) + fnType := llvm.FunctionType(retType, argTypes, false) + target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false) + result := b.CreateCall(fnType, target, args, "") + r1 = b.CreateExtractValue(result, 0, "syscall.r1") + r2 = b.CreateExtractValue(result, 1, "syscall.r2") + errFlagWide := b.CreateExtractValue(result, 2, "syscall.errFlagWide") + errFlag = b.CreateTrunc(errFlagWide, b.ctx.Int1Type(), "syscall.errFlag") + return r1, r2, errFlag, nil + + case "arm64": + // ARM64 darwin syscall ABI: + // - syscall number in X16 + // - args in X0..X5 + // - SVC #0x80 + // - primary return in X0, secondary in X1 + // - carry flag set on error (BCS) + var args []llvm.Value + var argTypes []llvm.Type + constraints := "={x0},={x1},={@cccs}" + for i, arg := range call.Args[1:] { + constraints += "," + [...]string{ + "0", // tie to first output (X0) + "{x1}", + "{x2}", + "{x3}", + "{x4}", + "{x5}", + }[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) + constraints += ",{x16}" // syscall number (also implicitly clobbered) + // Mark X0-X7 clobbered if not used as inputs. The kernel may + // clobber any of these per the AArch64 caller-saved convention. + // Unlike linux/arm64 (which uses x8 for the syscall number and + // has x16/x17 as scratch), darwin's ABI uses x16 directly, so + // no extra scratch-register clobbers are needed. + for i := len(call.Args) - 1; i < 8; i++ { + constraints += ",~{x" + strconv.Itoa(i) + "}" + } + // AArch64's flag-output constraint requires an i32 result, not i1. + // Truncate to i1 after extraction. + retType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.ctx.Int32Type()}, false) + fnType := llvm.FunctionType(retType, argTypes, false) + target := llvm.InlineAsm(fnType, "svc #0x80", constraints, true, false, 0, false) + result := b.CreateCall(fnType, target, args, "") + r1 = b.CreateExtractValue(result, 0, "syscall.r1") + r2 = b.CreateExtractValue(result, 1, "syscall.r2") + errFlagWide := b.CreateExtractValue(result, 2, "syscall.errFlagWide") + errFlag = b.CreateTrunc(errFlagWide, b.ctx.Int1Type(), "syscall.errFlag") + return r1, r2, errFlag, nil + + default: + return llvm.Value{}, llvm.Value{}, llvm.Value{}, b.makeError(call.Pos(), "system calls are not supported on darwin/"+b.GOARCH) + } +} diff --git a/main_test.go b/main_test.go index 6ac0d0f596..788c1cde82 100644 --- a/main_test.go +++ b/main_test.go @@ -348,6 +348,12 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) }) } + if options.GOOS == "darwin" { + t.Run("syscall_darwin.go", func(t *testing.T) { + t.Parallel() + runTest("syscall_darwin.go", options, t, nil, nil) + }) + } if isWebAssembly { t.Run("alias.go-scheduler-none", func(t *testing.T) { t.Parallel() diff --git a/testdata/syscall_darwin.go b/testdata/syscall_darwin.go new file mode 100644 index 0000000000..06e11a54fa --- /dev/null +++ b/testdata/syscall_darwin.go @@ -0,0 +1,47 @@ +//go:build darwin + +package main + +import ( + "fmt" + "os" + "syscall" +) + +func main() { + // Happy path: SYS_GETPID returns the current pid; no errno. + r1, _, errno := syscall.Syscall(syscall.SYS_GETPID, 0, 0, 0) + if errno != 0 { + fmt.Println("getpid errno:", errno) + return + } + if int(r1) != os.Getpid() { + fmt.Println("getpid mismatch:", r1, os.Getpid()) + return + } + fmt.Println("getpid ok") + + // Error path: close(99999) should return EBADF. + _, _, errno = syscall.Syscall(syscall.SYS_CLOSE, 99999, 0, 0) + if errno == syscall.EBADF { + fmt.Println("close ebadf ok") + } else { + fmt.Println("close errno unexpected:", errno) + } + + // Syscall6 path: anonymous mmap exercises all 6 argument + // registers (RDI/RSI/RDX/R10/R8/R9 on amd64; X0..X5 on arm64). + // A swap of any register slot would make the kernel reject the + // call, so successful return is meaningful coverage. + const ( + PROT_READ_WRITE = 0x3 // PROT_READ | PROT_WRITE + MAP_PRIVATE = 0x0002 + MAP_ANON = 0x1000 + ) + addr, _, errno := syscall.Syscall6(syscall.SYS_MMAP, 0, 4096, PROT_READ_WRITE, MAP_PRIVATE|MAP_ANON, ^uintptr(0), 0) + if errno != 0 || addr == 0 { + fmt.Println("mmap6 failed:", errno, "addr=", addr) + return + } + fmt.Println("mmap6 ok") +} diff --git a/testdata/syscall_darwin.txt b/testdata/syscall_darwin.txt new file mode 100644 index 0000000000..bc34881680 --- /dev/null +++ b/testdata/syscall_darwin.txt @@ -0,0 +1,3 @@ +getpid ok +close ebadf ok +mmap6 ok