1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build darwin || freebsd || linux || windows
5
6package purego
7
8import (
9 "math"
10 "reflect"
11 "runtime"
12 "unsafe"
13
14 "github.com/ebitengine/purego/internal/strings"
15)
16
17// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
18// It panics if it can't find the name symbol.
19func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
20 sym, err := loadSymbol(handle, name)
21 if err != nil {
22 panic(err)
23 }
24 RegisterFunc(fptr, sym)
25}
26
27// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
28// fptr will be set to a function that when called will call the C function given by cfn with the
29// parameters passed in the correct registers and stack.
30//
31// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
32//
33// These conversions describe how a Go type in the fptr will be used to call
34// the C function. It is important to note that there is no way to verify that fptr
35// matches the C function. This also holds true for struct types where the padding
36// needs to be ensured to match that of C; RegisterFunc does not verify this.
37//
38// # Type Conversions (Go <=> C)
39//
40// string <=> char*
41// bool <=> _Bool
42// uintptr <=> uintptr_t
43// uint <=> uint32_t or uint64_t
44// uint8 <=> uint8_t
45// uint16 <=> uint16_t
46// uint32 <=> uint32_t
47// uint64 <=> uint64_t
48// int <=> int32_t or int64_t
49// int8 <=> int8_t
50// int16 <=> int16_t
51// int32 <=> int32_t
52// int64 <=> int64_t
53// float32 <=> float
54// float64 <=> double
55// struct <=> struct (WIP - darwin only)
56// func <=> C function
57// unsafe.Pointer, *T <=> void*
58// []T => void*
59//
60// There is a special case when the last argument of fptr is a variadic interface (or []interface}
61// it will be expanded into a call to the C function as if it had the arguments in that slice.
62// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
63// This is not the same as C variadic.
64//
65// # Memory
66//
67// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
68// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
69// hold onto a reference to Go memory. This is the same as the [Cgo rules].
70//
71// However, there are some special cases. When passing a string as an argument if the string does not end in a null
72// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
73// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
74// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
75// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
76// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
77// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
78// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
79// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
80// to point to C memory (because it's a buffer for example) then use a pointer to byte and then convert that to a slice
81// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
82// of the pointer
83//
84// # Structs
85//
86// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
87// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
88// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
89//
90// # Example
91//
92// All functions below call this C function:
93//
94// char *foo(char *str);
95//
96// // Let purego convert types
97// var foo func(s string) string
98// goString := foo("copied")
99// // Go will garbage collect this string
100//
101// // Manually, handle allocations
102// var foo2 func(b string) *byte
103// mustFree := foo2("not copied\x00")
104// defer free(mustFree)
105//
106// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
107func RegisterFunc(fptr interface{}, cfn uintptr) {
108 fn := reflect.ValueOf(fptr).Elem()
109 ty := fn.Type()
110 if ty.Kind() != reflect.Func {
111 panic("purego: fptr must be a function pointer")
112 }
113 if ty.NumOut() > 1 {
114 panic("purego: function can only return zero or one values")
115 }
116 if cfn == 0 {
117 panic("purego: cfn is nil")
118 }
119 {
120 // this code checks how many registers and stack this function will use
121 // to avoid crashing with too many arguments
122 var ints int
123 var floats int
124 var stack int
125 for i := 0; i < ty.NumIn(); i++ {
126 arg := ty.In(i)
127 switch arg.Kind() {
128 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
129 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
130 reflect.Func, reflect.Bool:
131 if ints < numOfIntegerRegisters() {
132 ints++
133 } else {
134 stack++
135 }
136 case reflect.Float32, reflect.Float64:
137 if floats < numOfFloats {
138 floats++
139 } else {
140 stack++
141 }
142 case reflect.Struct:
143 if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
144 panic("purego: struct arguments are only supported on darwin amd64 & arm64")
145 }
146 if arg.Size() == 0 {
147 continue
148 }
149 addInt := func(u uintptr) {
150 ints++
151 }
152 addFloat := func(u uintptr) {
153 floats++
154 }
155 addStack := func(u uintptr) {
156 stack++
157 }
158 _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
159 default:
160 panic("purego: unsupported kind " + arg.Kind().String())
161 }
162 }
163 sizeOfStack := maxArgs - numOfIntegerRegisters()
164 if stack > sizeOfStack {
165 panic("purego: too many arguments")
166 }
167 }
168 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
169 if len(args) > 0 {
170 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
171 // subtract one from args bc the last argument in args is []interface{}
172 // which we are currently expanding
173 tmp := make([]reflect.Value, len(args)-1+len(variadic))
174 n := copy(tmp, args[:len(args)-1])
175 for i, v := range variadic {
176 tmp[n+i] = reflect.ValueOf(v)
177 }
178 args = tmp
179 }
180 }
181 var sysargs [maxArgs]uintptr
182 stack := sysargs[numOfIntegerRegisters():]
183 var floats [numOfFloats]uintptr
184 var numInts int
185 var numFloats int
186 var numStack int
187 var addStack, addInt, addFloat func(x uintptr)
188 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
189 // Windows arm64 uses the same calling convention as macOS and Linux
190 addStack = func(x uintptr) {
191 stack[numStack] = x
192 numStack++
193 }
194 addInt = func(x uintptr) {
195 if numInts >= numOfIntegerRegisters() {
196 addStack(x)
197 } else {
198 sysargs[numInts] = x
199 numInts++
200 }
201 }
202 addFloat = func(x uintptr) {
203 if numFloats < len(floats) {
204 floats[numFloats] = x
205 numFloats++
206 } else {
207 addStack(x)
208 }
209 }
210 } else {
211 // On Windows amd64 the arguments are passed in the numbered registered.
212 // So the first int is in the first integer register and the first float
213 // is in the second floating register if there is already a first int.
214 // This is in contrast to how macOS and Linux pass arguments which
215 // tries to use as many registers as possible in the calling convention.
216 addStack = func(x uintptr) {
217 sysargs[numStack] = x
218 numStack++
219 }
220 addInt = addStack
221 addFloat = addStack
222 }
223
224 var keepAlive []interface{}
225 defer func() {
226 runtime.KeepAlive(keepAlive)
227 runtime.KeepAlive(args)
228 }()
229 for _, v := range args {
230 switch v.Kind() {
231 case reflect.String:
232 ptr := strings.CString(v.String())
233 keepAlive = append(keepAlive, ptr)
234 addInt(uintptr(unsafe.Pointer(ptr)))
235 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
236 addInt(uintptr(v.Uint()))
237 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
238 addInt(uintptr(v.Int()))
239 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
240 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
241 addInt(v.Pointer())
242 case reflect.Func:
243 addInt(NewCallback(v.Interface()))
244 case reflect.Bool:
245 if v.Bool() {
246 addInt(1)
247 } else {
248 addInt(0)
249 }
250 case reflect.Float32:
251 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
252 case reflect.Float64:
253 addFloat(uintptr(math.Float64bits(v.Float())))
254 case reflect.Struct:
255 keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
256 default:
257 panic("purego: unsupported kind: " + v.Kind().String())
258 }
259 }
260 // TODO: support structs
261 var r1, r2 uintptr
262 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
263 // Use the normal arm64 calling convention even on Windows
264 syscall := syscall15Args{
265 cfn,
266 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
267 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
268 sysargs[12], sysargs[13], sysargs[14],
269 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
270 0, 0, 0,
271 }
272 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
273 r1, r2 = syscall.r1, syscall.r2
274 } else {
275 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
276 r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
277 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
278 sysargs[12], sysargs[13], sysargs[14])
279 }
280 if ty.NumOut() == 0 {
281 return nil
282 }
283 outType := ty.Out(0)
284 v := reflect.New(outType).Elem()
285 switch outType.Kind() {
286 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
287 v.SetUint(uint64(r1))
288 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
289 v.SetInt(int64(r1))
290 case reflect.Bool:
291 v.SetBool(byte(r1) != 0)
292 case reflect.UnsafePointer:
293 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
294 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
295 case reflect.Ptr:
296 // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
297 v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
298 case reflect.Func:
299 // wrap this C function in a nicely typed Go function
300 v = reflect.New(outType)
301 RegisterFunc(v.Interface(), r1)
302 case reflect.String:
303 v.SetString(strings.GoString(r1))
304 case reflect.Float32:
305 // NOTE: r2 is only the floating return value on 64bit platforms.
306 // On 32bit platforms r2 is the upper part of a 64bit return.
307 v.SetFloat(float64(math.Float32frombits(uint32(r2))))
308 case reflect.Float64:
309 // NOTE: r2 is only the floating return value on 64bit platforms.
310 // On 32bit platforms r2 is the upper part of a 64bit return.
311 v.SetFloat(math.Float64frombits(uint64(r2)))
312 default:
313 panic("purego: unsupported return kind: " + outType.Kind().String())
314 }
315 return []reflect.Value{v}
316 })
317 fn.Set(v)
318}
319
320func roundUpTo8(val uintptr) uintptr {
321 return (val + 7) &^ 7
322}
323
324func numOfIntegerRegisters() int {
325 switch runtime.GOARCH {
326 case "arm64":
327 return 8
328 case "amd64":
329 return 6
330 // TODO: figure out why 386 tests are not working
331 /*case "386":
332 return 0
333 case "arm":
334 return 4*/
335 default:
336 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
337 }
338}