Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
119 changes: 119 additions & 0 deletions compiler/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
6 changes: 6 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
47 changes: 47 additions & 0 deletions testdata/syscall_darwin.go
Original file line number Diff line number Diff line change
@@ -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")
}
3 changes: 3 additions & 0 deletions testdata/syscall_darwin.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
getpid ok
close ebadf ok
mmap6 ok