1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build (386 || arm) && (freebsd || linux || netbsd || windows)
5
6package purego
7
8import (
9 "runtime"
10 "structs"
11 "unsafe"
12)
13
14const (
15 maxArgs = 32
16)
17
18// syscallArgs is the argument block handed to the syscall15 trampoline. On
19// platforms that go through internal/cgo it is passed to C as a
20// struct syscallArgs, so its layout must match the C ABI.
21//
22// The trampoline reports its results through this block: the return values go
23// to a1 and a2 and, where the platform captures errno, the error code goes to
24// a3. A trampoline that cannot capture errno must clear a3.
25type syscallArgs struct {
26 _ structs.HostLayout
27
28 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
29 a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr
30 f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr
31 arm64_r8 uintptr
32}
33
34func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
35 s := thePool.Get().(*syscallArgs)
36 *s = syscallArgs{
37 fn: fn,
38 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
39 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
40 a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11],
41 a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15],
42 a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19],
43 a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23],
44 a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27],
45 a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31],
46 f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3],
47 f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7],
48 f9: floats[8], f10: floats[9], f11: floats[10], f12: floats[11],
49 f13: floats[12], f14: floats[13], f15: floats[14], f16: floats[15],
50 arm64_r8: r8,
51 }
52 runtime_cgocall(syscallXABI0, unsafe.Pointer(s))
53 return s
54}
55
56// SyscallN takes fn, a C function pointer and a list of arguments as uintptr.
57// There is an internal maximum number of arguments that SyscallN can take. It panics
58// when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on
59// platforms that capture it, otherwise 0.
60//
61// In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]
62// especially point 4.
63//
64// NOTE: SyscallN does not properly call functions that have both integer and float parameters.
65// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
66// for an explanation of why that is.
67//
68// On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the
69// stack.
70//
71// The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes
72// which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect
73// their memory location.
74//
75//go:uintptrescapes
76func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
77 if fn == 0 {
78 panic("purego: fn is nil")
79 }
80 if len(args) > maxArgs {
81 panic("purego: too many arguments to SyscallN")
82 }
83
84 // Add padding so there is no out-of-bounds slicing.
85 var tmp [maxArgs]uintptr
86 n := copy(tmp[:], args)
87 return syscallPadded(fn, &tmp, n)
88}
89
90// syscallPadded calls fn with the first n values of tmp as arguments.
91func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) {
92 // Windows uses syscall.SyscallN in syscall_windows.go.
93 if runtime.GOOS == "windows" {
94 return syscall_syscallN(fn, tmp[:n]...)
95 }
96 var floats [16]uintptr
97 copy(floats[:], tmp[:16])
98 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
99 defer thePool.Put(s)
100 return s.a1, s.a2, s.a3
101}