1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build darwin || freebsd || linux || netbsd || windows
5
6package purego
7
8import (
9 "fmt"
10 "math"
11 "reflect"
12 "runtime"
13 "strconv"
14 "sync"
15 "unsafe"
16
17 "github.com/ebitengine/purego/internal/strings"
18 "github.com/ebitengine/purego/internal/xreflect"
19)
20
21var thePool = sync.Pool{New: func() any {
22 return new(syscall15Args)
23}}
24
25// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
26// It panics if it can't find the name symbol.
27func RegisterLibFunc(fptr any, handle uintptr, name string) {
28 sym, err := loadSymbol(handle, name)
29 if err != nil {
30 panic(err)
31 }
32 RegisterFunc(fptr, sym)
33}
34
35// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
36// fptr will be set to a function that when called will call the C function given by cfn with the
37// parameters passed in the correct registers and stack.
38//
39// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
40//
41// These conversions describe how a Go type in the fptr will be used to call
42// the C function. It is important to note that there is no way to verify that fptr
43// matches the C function. This also holds true for struct types where the padding
44// needs to be ensured to match that of C; RegisterFunc does not verify this.
45//
46// # Type Conversions (Go <=> C)
47//
48// string <=> char*
49// bool <=> _Bool
50// uintptr <=> uintptr_t
51// uint <=> uint32_t or uint64_t
52// uint8 <=> uint8_t
53// uint16 <=> uint16_t
54// uint32 <=> uint32_t
55// uint64 <=> uint64_t
56// int <=> int32_t or int64_t
57// int8 <=> int8_t
58// int16 <=> int16_t
59// int32 <=> int32_t
60// int64 <=> int64_t
61// float32 <=> float
62// float64 <=> double
63// struct <=> struct (WIP - darwin only)
64// func <=> C function
65// unsafe.Pointer, *T <=> void*
66// []T => void*
67//
68// There is a special case when the last argument of fptr is a variadic interface (or []interface}
69// it will be expanded into a call to the C function as if it had the arguments in that slice.
70// This means that using arg ...any is like a cast to the function with the arguments inside arg.
71// This is not the same as C variadic.
72//
73// # Memory
74//
75// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
76// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
77// hold onto a reference to Go memory. This is the same as the [Cgo rules].
78//
79// However, there are some special cases. When passing a string as an argument if the string does not end in a null
80// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
81// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
82// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
83// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
84// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
85// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
86// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
87// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
88// 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
89// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
90// of the pointer
91//
92// # Structs
93//
94// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
95// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
96// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
97//
98// # Example
99//
100// All functions below call this C function:
101//
102// char *foo(char *str);
103//
104// // Let purego convert types
105// var foo func(s string) string
106// goString := foo("copied")
107// // Go will garbage collect this string
108//
109// // Manually, handle allocations
110// var foo2 func(b string) *byte
111// mustFree := foo2("not copied\x00")
112// defer free(mustFree)
113//
114// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
115func RegisterFunc(fptr any, cfn uintptr) {
116 fn := reflect.ValueOf(fptr).Elem()
117 ty := fn.Type()
118 if ty.Kind() != reflect.Func {
119 panic("purego: fptr must be a function pointer")
120 }
121 if ty.NumOut() > 1 {
122 panic("purego: function can only return zero or one values")
123 }
124 if cfn == 0 {
125 panic("purego: cfn is nil")
126 }
127 if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
128 runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" {
129 panic("purego: float returns are not supported")
130 }
131 {
132 // this code checks how many registers and stack this function will use
133 // to avoid crashing with too many arguments
134 var ints int
135 var floats int
136 var stack int
137 for i := 0; i < ty.NumIn(); i++ {
138 arg := ty.In(i)
139 switch arg.Kind() {
140 case reflect.Func:
141 // This only does preliminary testing to ensure the CDecl argument
142 // is the first argument. Full testing is done when the callback is actually
143 // created in NewCallback.
144 for j := 0; j < arg.NumIn(); j++ {
145 in := arg.In(j)
146 if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
147 continue
148 }
149 if j != 0 {
150 panic("purego: CDecl must be the first argument")
151 }
152 }
153 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
154 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
155 reflect.Slice, reflect.Bool:
156 if ints < numOfIntegerRegisters() {
157 ints++
158 } else {
159 stack++
160 }
161 case reflect.Float32, reflect.Float64:
162 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
163 if is32bit {
164 panic("purego: floats only supported on 64bit platforms")
165 }
166 if floats < numOfFloatRegisters {
167 floats++
168 } else {
169 stack++
170 }
171 case reflect.Struct:
172 if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
173 panic("purego: struct arguments are only supported on darwin amd64 & arm64")
174 }
175 if arg.Size() == 0 {
176 continue
177 }
178 addInt := func(u uintptr) {
179 ints++
180 }
181 addFloat := func(u uintptr) {
182 floats++
183 }
184 addStack := func(u uintptr) {
185 stack++
186 }
187 _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
188 default:
189 panic("purego: unsupported kind " + arg.Kind().String())
190 }
191 }
192 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
193 if runtime.GOOS != "darwin" {
194 panic("purego: struct return values only supported on darwin arm64 & amd64")
195 }
196 outType := ty.Out(0)
197 checkStructFieldsSupported(outType)
198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
199 // on amd64 if struct is bigger than 16 bytes allocate the return struct
200 // and pass it in as a hidden first argument.
201 ints++
202 }
203 }
204 sizeOfStack := maxArgs - numOfIntegerRegisters()
205 if stack > sizeOfStack {
206 panic("purego: too many arguments")
207 }
208 }
209 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
210 var sysargs [maxArgs]uintptr
211 var floats [numOfFloatRegisters]uintptr
212 var numInts int
213 var numFloats int
214 var numStack int
215 var addStack, addInt, addFloat func(x uintptr)
216 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
217 // Windows arm64 uses the same calling convention as macOS and Linux
218 addStack = func(x uintptr) {
219 sysargs[numOfIntegerRegisters()+numStack] = x
220 numStack++
221 }
222 addInt = func(x uintptr) {
223 if numInts >= numOfIntegerRegisters() {
224 addStack(x)
225 } else {
226 sysargs[numInts] = x
227 numInts++
228 }
229 }
230 addFloat = func(x uintptr) {
231 if numFloats < len(floats) {
232 floats[numFloats] = x
233 numFloats++
234 } else {
235 addStack(x)
236 }
237 }
238 } else {
239 // On Windows amd64 the arguments are passed in the numbered registered.
240 // So the first int is in the first integer register and the first float
241 // is in the second floating register if there is already a first int.
242 // This is in contrast to how macOS and Linux pass arguments which
243 // tries to use as many registers as possible in the calling convention.
244 addStack = func(x uintptr) {
245 sysargs[numStack] = x
246 numStack++
247 }
248 addInt = addStack
249 addFloat = addStack
250 }
251
252 var keepAlive []any
253 defer func() {
254 runtime.KeepAlive(keepAlive)
255 runtime.KeepAlive(args)
256 }()
257
258 var arm64_r8 uintptr
259 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
260 outType := ty.Out(0)
261 if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize {
262 val := reflect.New(outType)
263 keepAlive = append(keepAlive, val)
264 addInt(val.Pointer())
265 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
266 isAllFloats, numFields := isAllSameFloat(outType)
267 if !isAllFloats || numFields > 4 {
268 val := reflect.New(outType)
269 keepAlive = append(keepAlive, val)
270 arm64_r8 = val.Pointer()
271 }
272 }
273 }
274 for i, v := range args {
275 if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok {
276 if i != len(args)-1 {
277 panic("purego: can only expand last parameter")
278 }
279 for _, x := range variadic {
280 keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
281 }
282 continue
283 }
284 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
285 (numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
286 fields := make([]reflect.StructField, len(args[i:]))
287
288 for j, val := range args[i:] {
289 if val.Kind() == reflect.String {
290 ptr := strings.CString(v.String())
291 keepAlive = append(keepAlive, ptr)
292 val = reflect.ValueOf(ptr)
293 args[i+j] = val
294 }
295 fields[j] = reflect.StructField{
296 Name: "X" + strconv.Itoa(j),
297 Type: val.Type(),
298 }
299 }
300 structType := reflect.StructOf(fields)
301 structInstance := reflect.New(structType).Elem()
302 for j, val := range args[i:] {
303 structInstance.Field(j).Set(val)
304 }
305 placeRegisters(structInstance, addFloat, addInt)
306 break
307 }
308 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
309 }
310
311 syscall := thePool.Get().(*syscall15Args)
312 defer thePool.Put(syscall)
313
314 if runtime.GOARCH == "loong64" {
315 *syscall = syscall15Args{
316 cfn,
317 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
318 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
319 sysargs[12], sysargs[13], sysargs[14],
320 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
321 0,
322 }
323 runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
324 } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
325 // Use the normal arm64 calling convention even on Windows
326 *syscall = syscall15Args{
327 cfn,
328 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
329 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
330 sysargs[12], sysargs[13], sysargs[14],
331 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
332 arm64_r8,
333 }
334 runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
335 } else {
336 *syscall = syscall15Args{}
337 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
338 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
339 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
340 sysargs[12], sysargs[13], sysargs[14])
341 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
342 }
343 if ty.NumOut() == 0 {
344 return nil
345 }
346 outType := ty.Out(0)
347 v := reflect.New(outType).Elem()
348 switch outType.Kind() {
349 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
350 v.SetUint(uint64(syscall.a1))
351 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
352 v.SetInt(int64(syscall.a1))
353 case reflect.Bool:
354 v.SetBool(byte(syscall.a1) != 0)
355 case reflect.UnsafePointer:
356 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
357 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
358 case reflect.Ptr:
359 v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
360 case reflect.Func:
361 // wrap this C function in a nicely typed Go function
362 v = reflect.New(outType)
363 RegisterFunc(v.Interface(), syscall.a1)
364 case reflect.String:
365 v.SetString(strings.GoString(syscall.a1))
366 case reflect.Float32:
367 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
368 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
369 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
370 case reflect.Float64:
371 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
372 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
373 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
374 case reflect.Struct:
375 v = getStruct(outType, *syscall)
376 default:
377 panic("purego: unsupported return kind: " + outType.Kind().String())
378 }
379 if len(args) > 0 {
380 // reuse args slice instead of allocating one when possible
381 args[0] = v
382 return args[:1]
383 } else {
384 return []reflect.Value{v}
385 }
386 })
387 fn.Set(v)
388}
389
390func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {
391 switch v.Kind() {
392 case reflect.String:
393 ptr := strings.CString(v.String())
394 keepAlive = append(keepAlive, ptr)
395 addInt(uintptr(unsafe.Pointer(ptr)))
396 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
397 addInt(uintptr(v.Uint()))
398 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
399 addInt(uintptr(v.Int()))
400 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
401 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
402 addInt(v.Pointer())
403 case reflect.Func:
404 addInt(NewCallback(v.Interface()))
405 case reflect.Bool:
406 if v.Bool() {
407 addInt(1)
408 } else {
409 addInt(0)
410 }
411 case reflect.Float32:
412 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
413 case reflect.Float64:
414 addFloat(uintptr(math.Float64bits(v.Float())))
415 case reflect.Struct:
416 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
417 default:
418 panic("purego: unsupported kind: " + v.Kind().String())
419 }
420 return keepAlive
421}
422
423// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
424// if it is bigger than this than enough space must be allocated on the heap and then passed into
425// the function as the first parameter on amd64 or in R8 on arm64.
426//
427// If you change this make sure to update it in objc_runtime_darwin.go
428const maxRegAllocStructSize = 16
429
430func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
431 allFloats = true
432 root := ty.Field(0).Type
433 for root.Kind() == reflect.Struct {
434 root = root.Field(0).Type
435 }
436 first := root.Kind()
437 if first != reflect.Float32 && first != reflect.Float64 {
438 allFloats = false
439 }
440 for i := 0; i < ty.NumField(); i++ {
441 f := ty.Field(i).Type
442 if f.Kind() == reflect.Struct {
443 var structNumFields int
444 allFloats, structNumFields = isAllSameFloat(f)
445 numFields += structNumFields
446 continue
447 }
448 numFields++
449 if f.Kind() != first {
450 allFloats = false
451 }
452 }
453 return allFloats, numFields
454}
455
456func checkStructFieldsSupported(ty reflect.Type) {
457 for i := 0; i < ty.NumField(); i++ {
458 f := ty.Field(i).Type
459 if f.Kind() == reflect.Array {
460 f = f.Elem()
461 } else if f.Kind() == reflect.Struct {
462 checkStructFieldsSupported(f)
463 continue
464 }
465 switch f.Kind() {
466 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
467 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
468 reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
469 default:
470 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
471 }
472 }
473}
474
475func roundUpTo8(val uintptr) uintptr {
476 return (val + 7) &^ 7
477}
478
479func numOfIntegerRegisters() int {
480 switch runtime.GOARCH {
481 case "arm64", "loong64":
482 return 8
483 case "amd64":
484 return 6
485 default:
486 // since this platform isn't supported and can therefore only access
487 // integer registers it is fine to return the maxArgs
488 return maxArgs
489 }
490}