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