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