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