Skip to content

Commit 105fe9b

Browse files
aykevldeadprogram
authored andcommitted
darwin: replace custom syscall package with Go native syscall package
This required a few compiler and runtime tricks to work, but I ran a bunch of tests and it seems fine. (CI will of course do more exhaustive testing). The main benefit here is that we don't need to maintain the darwin version of the syscall package, and reduce extra risks for bugs (because we reuse the well-tested syscall package). For example, Go 1.23 needed a bunch of new constants in the syscall package. That would have been avoided if we had used the native syscall package on MacOS.
1 parent 753f4b3 commit 105fe9b

19 files changed

+229
-531
lines changed

compileopts/target.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
389389
"-platform_version", "macos", platformVersion, platformVersion,
390390
)
391391
spec.ExtraFiles = append(spec.ExtraFiles,
392+
"src/runtime/os_darwin.c",
392393
"src/runtime/runtime_unix.c")
393394
case "linux":
394395
spec.Linker = "ld.lld"

compiler/compiler.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
18471847
case strings.HasPrefix(name, "(device/riscv.CSR)."):
18481848
return b.emitCSROperation(instr)
18491849
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"):
1850-
return b.createSyscall(instr)
1850+
if b.GOOS != "darwin" {
1851+
return b.createSyscall(instr)
1852+
}
18511853
case strings.HasPrefix(name, "syscall.rawSyscallNoError") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscallNoError"):
18521854
return b.createRawSyscallNoError(instr)
18531855
case name == "runtime.supportsRecover":
@@ -1865,6 +1867,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
18651867
return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil
18661868
case name == "runtime/interrupt.New":
18671869
return b.createInterruptGlobal(instr)
1870+
case name == "internal/abi.FuncPCABI0":
1871+
retval := b.createDarwinFuncPCABI0Call(instr)
1872+
if !retval.IsNil() {
1873+
return retval, nil
1874+
}
18681875
}
18691876

18701877
calleeType, callee = b.getFunction(fn)

compiler/syscall.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package compiler
55

66
import (
77
"strconv"
8+
"strings"
89

910
"golang.org/x/tools/go/ssa"
1011
"tinygo.org/x/go-llvm"
@@ -329,3 +330,64 @@ func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, err
329330
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
330331
return retval, nil
331332
}
333+
334+
// Lower a call to internal/abi.FuncPCABI0 on MacOS.
335+
// This function is called like this:
336+
//
337+
// syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
338+
//
339+
// So we'll want to return a function pointer (as uintptr) that points to the
340+
// libc function. Specifically, we _don't_ want to point to the trampoline
341+
// function (which is implemented in Go assembly which we can't read), but
342+
// rather to the actually intended function. For this we're going to assume that
343+
// all the functions follow a specific pattern: libc_<functionname>_trampoline.
344+
//
345+
// The return value is the function pointer as an uintptr, or a nil value if
346+
// this isn't possible (and a regular call should be made as fallback).
347+
func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value {
348+
if b.GOOS != "darwin" {
349+
// This has only been tested on MacOS (and only seems to be used there).
350+
return llvm.Value{}
351+
}
352+
353+
// Check that it uses a function call like syscall.libc_*_trampoline
354+
itf := instr.Args[0].(*ssa.MakeInterface)
355+
calledFn := itf.X.(*ssa.Function)
356+
if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" {
357+
return llvm.Value{}
358+
}
359+
if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") {
360+
361+
return llvm.Value{}
362+
}
363+
364+
// Extract the libc function name.
365+
name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_")
366+
if name == "open" {
367+
// Special case: open() is a variadic function and can't be called like
368+
// a regular function. Therefore, we need to use a wrapper implemented
369+
// in C.
370+
name = "syscall_libc_open"
371+
}
372+
if b.GOARCH == "amd64" {
373+
if name == "fdopendir" || name == "readdir_r" {
374+
// Hack to support amd64, which needs the $INODE64 suffix.
375+
// This is also done in upstream Go:
376+
// https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467
377+
name += "$INODE64"
378+
}
379+
}
380+
381+
// Obtain the C function.
382+
// Use a simple function (no parameters or return value) because all we need
383+
// is the address of the function.
384+
llvmFn := b.mod.NamedFunction(name)
385+
if llvmFn.IsNil() {
386+
llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false)
387+
llvmFn = llvm.AddFunction(b.mod, name, llvmFnType)
388+
}
389+
390+
// Cast the function pointer to a uintptr (because that's what
391+
// abi.FuncPCABI0 returns).
392+
return b.CreatePtrToInt(llvmFn, b.uintptrType, "")
393+
}

lib/macos-minimal-sdk

loader/goroot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool)
218218
// with the TinyGo version. This is the case on some targets.
219219
func needsSyscallPackage(buildTags []string) bool {
220220
for _, tag := range buildTags {
221-
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" {
221+
if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" {
222222
return true
223223
}
224224
}

src/internal/abi/funcpc.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ package abi
44
// (in particular internal/syscall/unix on MacOS). They do not currently have an
55
// implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it
66
// uses a C-like calling convention).
7+
// Calls to FuncPCABI0 however are treated specially by the compiler when
8+
// compiling for MacOS.
79

8-
func FuncPCABI0(f interface{}) uintptr {
9-
panic("unimplemented: internal/abi.FuncPCABI0")
10-
}
10+
func FuncPCABI0(f interface{}) uintptr
1111

12-
func FuncPCABIInternal(f interface{}) uintptr {
13-
panic("unimplemented: internal/abi.FuncPCABIInternal")
14-
}
12+
func FuncPCABIInternal(f interface{}) uintptr

src/os/dir_darwin.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) {
135135
}
136136
var dir uintptr
137137
for {
138-
dir, err = syscall.Fdopendir(fd2)
138+
dir, err = fdopendir(fd2)
139139
if err != syscall.EINTR {
140140
break
141141
}
@@ -149,6 +149,9 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) {
149149

150150
// Implemented in syscall/syscall_libc_darwin_*.go.
151151

152+
//go:linkname fdopendir syscall.fdopendir
153+
func fdopendir(fd int) (dir uintptr, err error)
154+
152155
//go:linkname closedir syscall.closedir
153156
func closedir(dir uintptr) (err error)
154157

src/os/file_darwin.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package os
2+
3+
import "syscall"
4+
5+
func pipe(p []int) error {
6+
return syscall.Pipe(p)
7+
}

src/os/file_notdarwin.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2
2+
3+
package os
4+
5+
import "syscall"
6+
7+
func pipe(p []int) error {
8+
return syscall.Pipe2(p, syscall.O_CLOEXEC)
9+
}

src/os/file_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func Truncate(name string, size int64) error {
7070

7171
func Pipe() (r *File, w *File, err error) {
7272
var p [2]int
73-
err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC))
73+
err = handleSyscallError(pipe(p[:]))
7474
if err != nil {
7575
return
7676
}

0 commit comments

Comments
 (0)