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 "sync"
14 "unsafe"
15
16 "github.com/bnema/purego/internal/strings"
17 "github.com/bnema/purego/internal/xreflect"
18)
19
20const (
21 align8ByteMask = 7 // Mask for 8-byte alignment: (val + 7) &^ 7
22 align8ByteSize = 8 // 8-byte alignment boundary
23)
24
25var 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.
31func 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
122func 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.Pointer, reflect.UnsafePointer,
163 reflect.Slice, reflect.Array, 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 ensureStructSupported()
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 ensureStructSupported()
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 // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed.
222 // keepAlive is only used for: string args (CString alloc), struct args (addStruct),
223 // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy),
224 // and variadic []any args (unknown types at registration time).
225 needsKeepAlive := false
226 for i := 0; i < ty.NumIn(); i++ {
227 inKind := ty.In(i).Kind()
228 switch inKind {
229 case reflect.String, reflect.Struct, reflect.Array, reflect.Slice:
230 // Slice covers []string (CString allocs) and variadic ...any ([]any)
231 needsKeepAlive = true
232 case reflect.Interface:
233 needsKeepAlive = true
234 }
235 }
236 if ty.NumOut() == 1 {
237 switch ty.Out(0).Kind() {
238 case reflect.Struct:
239 // struct returns may need hidden pointer allocation stored in keepAlive
240 needsKeepAlive = true
241 }
242 }
243
244 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
245 var sysargs [maxArgs]uintptr
246 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
247 // since numOfFloatRegisters() is a function call, not a constant.
248 // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
249 var floats [maxArgs]uintptr
250 var numInts int
251 var numFloats int
252 var numStack int
253 var addStack, addInt, addFloat func(x uintptr)
254 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
255 // Windows arm64 uses the same calling convention as macOS and Linux
256 addStack = func(x uintptr) {
257 sysargs[numOfIntegerRegisters()+numStack] = x
258 numStack++
259 }
260 addInt = func(x uintptr) {
261 if numInts >= numOfIntegerRegisters() {
262 addStack(x)
263 } else {
264 sysargs[numInts] = x
265 numInts++
266 }
267 }
268 addFloat = func(x uintptr) {
269 if numFloats < numOfFloatRegisters() {
270 floats[numFloats] = x
271 numFloats++
272 } else {
273 addStack(x)
274 }
275 }
276 } else {
277 // On Windows amd64 the arguments are passed in the numbered registered.
278 // So the first int is in the first integer register and the first float
279 // is in the second floating register if there is already a first int.
280 // This is in contrast to how macOS and Linux pass arguments which
281 // tries to use as many registers as possible in the calling convention.
282 addStack = func(x uintptr) {
283 sysargs[numStack] = x
284 numStack++
285 }
286 addInt = addStack
287 addFloat = addStack
288 }
289
290 if needsKeepAlive {
291 // Slow path: allocate keepAlive slice and defer cleanup.
292 // Used when args include strings, structs, arrays, or variadic []any.
293 var keepAlive []any
294 defer func() {
295 runtime.KeepAlive(keepAlive)
296 runtime.KeepAlive(args)
297 }()
298
299 var arm64_r8 uintptr
300 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
301 outType := ty.Out(0)
302 if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {
303 val := reflect.New(outType)
304 keepAlive = append(keepAlive, val)
305 addInt(val.Pointer())
306 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
307 isAllFloats, numFields := isAllSameFloat(outType)
308 if !isAllFloats || numFields > 4 {
309 val := reflect.New(outType)
310 keepAlive = append(keepAlive, val)
311 arm64_r8 = val.Pointer()
312 }
313 }
314 }
315 for i, v := range args {
316 if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
317 if i != len(args)-1 {
318 panic("purego: can only expand last parameter")
319 }
320 for _, x := range variadic {
321 keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
322 }
323 continue
324 }
325 // Check if we need to start Darwin ARM64 C-style stack packing
326 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
327 // Collect and separate remaining args into register vs stack
328 stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
329 keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
330 keepAlive = newKeepAlive
331
332 // Bundle stack arguments with C-style packing
333 bundleStackArgs(stackArgs, addStack)
334 break
335 }
336 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
337 }
338 return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args)
339 }
340
341 // Fast path: no keepAlive needed (pointer/integer/bool/float args only).
342 // Skip keepAlive slice allocation and defer overhead.
343 defer runtime.KeepAlive(args)
344
345 for i, v := range args {
346 if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
347 if i != len(args)-1 {
348 panic("purego: can only expand last parameter")
349 }
350 for _, x := range variadic {
351 addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
352 }
353 continue
354 }
355 // Check if we need to start Darwin ARM64 C-style stack packing
356 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
357 stackArgs, _ := collectStackArgs(args, i, numInts, numFloats,
358 nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
359 bundleStackArgs(stackArgs, addStack)
360 break
361 }
362 addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
363 }
364
365 return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args)
366 })
367 fn.Set(v)
368}
369
370// callAndReturn performs the actual C function call and constructs the return value.
371// It is shared between the fast path (no keepAlive) and slow path (with keepAlive).
372func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value {
373 syscall := thePool.Get().(*syscall15Args)
374 defer thePool.Put(syscall)
375
376 if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
377 syscall.Set(cfn, sysargs[:], floats[:], 0)
378 runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
379 } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
380 // Use the normal arm64 calling convention even on Windows
381 syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
382 runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
383 } else {
384 *syscall = syscall15Args{}
385 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
386 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
387 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
388 sysargs[12], sysargs[13], sysargs[14])
389 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
390 }
391 if ty.NumOut() == 0 {
392 return nil
393 }
394 outType := ty.Out(0)
395 v := reflect.New(outType).Elem()
396 switch outType.Kind() {
397 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
398 v.SetUint(uint64(syscall.a1))
399 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
400 v.SetInt(int64(syscall.a1))
401 case reflect.Bool:
402 v.SetBool(byte(syscall.a1) != 0)
403 case reflect.UnsafePointer:
404 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
405 v = reflect.New(outType).Elem()
406 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
407 case reflect.Pointer:
408 v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
409 case reflect.Func:
410 // wrap this C function in a nicely typed Go function
411 v = reflect.New(outType)
412 RegisterFunc(v.Interface(), syscall.a1)
413 case reflect.String:
414 v = reflect.ValueOf(strings.GoString(syscall.a1))
415 case reflect.Float32:
416 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
417 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
418 // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
419 // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
420 // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
421 v = reflect.New(outType).Elem()
422 switch runtime.GOARCH {
423 case "386":
424 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
425 case "ppc64le":
426 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
427 case "s390x":
428 // S390X is big-endian: float32 in upper 32 bits of 64-bit register
429 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
430 default:
431 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
432 }
433 case reflect.Float64:
434 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
435 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
436 v = reflect.New(outType).Elem()
437 if is32bit {
438 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
439 } else {
440 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
441 }
442 case reflect.Struct:
443 v = getStruct(outType, *syscall)
444 case reflect.Slice:
445 if outType.Elem().Kind() == reflect.String {
446 // C returns char** (NULL-terminated array of strings)
447 if syscall.a1 == 0 {
448 v = reflect.Zero(outType)
449 } else {
450 var result []string
451 p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))
452 for {
453 strPtr := *(*uintptr)(p)
454 if strPtr == 0 {
455 break
456 }
457 result = append(result, strings.GoString(strPtr))
458 p = unsafe.Add(p, unsafe.Sizeof(uintptr(0)))
459 }
460 v = reflect.ValueOf(result)
461 }
462 } else {
463 panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())
464 }
465 default:
466 panic("purego: unsupported return kind: " + outType.Kind().String())
467 }
468 if len(args) > 0 {
469 // reuse args slice instead of allocating one when possible
470 args[0] = v
471 return args[:1]
472 } else {
473 return []reflect.Value{v}
474 }
475}
476
477func 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 {
478 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
479 switch v.Kind() {
480 case reflect.String:
481 ptr := strings.CString(v.String())
482 keepAlive = append(keepAlive, ptr)
483 addInt(uintptr(unsafe.Pointer(ptr)))
484 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
485 addInt(uintptr(v.Uint()))
486 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
487 addInt(uintptr(v.Int()))
488 case reflect.Pointer, reflect.UnsafePointer:
489 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
490 addInt(v.Pointer())
491 case reflect.Slice:
492 if v.Type().Elem().Kind() == reflect.String {
493 ss := v.Interface().([]string)
494 if ss == nil {
495 addInt(0)
496 } else {
497 res := make([]*byte, len(ss)+1)
498 for i, s := range ss {
499 ptr := strings.CString(s)
500 keepAlive = append(keepAlive, ptr)
501 res[i] = ptr
502 }
503 // NULL-terminated for C
504 res[len(ss)] = nil
505 keepAlive = append(keepAlive, res)
506 addInt(uintptr(unsafe.Pointer(&res[0])))
507 }
508 } else {
509 addInt(v.Pointer())
510 }
511 case reflect.Func:
512 addInt(NewCallback(v.Interface()))
513 case reflect.Bool:
514 if v.Bool() {
515 addInt(1)
516 } else {
517 addInt(0)
518 }
519 case reflect.Float32:
520 // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register
521 if runtime.GOARCH == "s390x" {
522 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
523 } else {
524 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
525 }
526 case reflect.Float64:
527 if is32bit {
528 bits := math.Float64bits(v.Float())
529 addFloat(uintptr(bits))
530 addFloat(uintptr(bits >> 32))
531 } else {
532 addFloat(uintptr(math.Float64bits(v.Float())))
533 }
534 case reflect.Array:
535 // Task 7: Support fixed-size array arguments by passing a pointer to the array data.
536 if v.CanAddr() {
537 addInt(v.Addr().Pointer())
538 } else {
539 // Array is not addressable (e.g. passed by value); make an addressable copy.
540 tmp := reflect.New(v.Type()).Elem()
541 tmp.Set(v)
542 keepAlive = append(keepAlive, tmp)
543 addInt(tmp.Addr().Pointer())
544 }
545 case reflect.Struct:
546 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
547 default:
548 panic("purego: unsupported kind: " + v.Kind().String())
549 }
550 return keepAlive
551}
552
553// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
554// if it is bigger than this than enough space must be allocated on the heap and then passed into
555// the function as the first parameter on amd64 or in R8 on arm64.
556//
557// If you change this make sure to update it in objc_runtime_darwin.go
558const maxRegAllocStructSize = 16
559
560func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
561 allFloats = true
562 root := ty.Field(0).Type
563 for root.Kind() == reflect.Struct {
564 root = root.Field(0).Type
565 }
566 first := root.Kind()
567 if first != reflect.Float32 && first != reflect.Float64 {
568 allFloats = false
569 }
570 for i := 0; i < ty.NumField(); i++ {
571 f := ty.Field(i).Type
572 if f.Kind() == reflect.Struct {
573 var structNumFields int
574 allFloats, structNumFields = isAllSameFloat(f)
575 numFields += structNumFields
576 continue
577 }
578 numFields++
579 if f.Kind() != first {
580 allFloats = false
581 }
582 }
583 return allFloats, numFields
584}
585
586func checkStructFieldsSupported(ty reflect.Type) {
587 for i := 0; i < ty.NumField(); i++ {
588 f := ty.Field(i).Type
589 if f.Kind() == reflect.Array {
590 f = f.Elem()
591 } else if f.Kind() == reflect.Struct {
592 checkStructFieldsSupported(f)
593 continue
594 }
595 switch f.Kind() {
596 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
597 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
598 reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
599 reflect.Bool:
600 default:
601 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
602 }
603 }
604}
605
606func ensureStructSupported() {
607 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
608 panic("purego: struct arguments/returns are only supported on amd64 and arm64")
609 }
610 if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
611 panic("purego: struct arguments/returns are only supported on darwin and linux")
612 }
613}
614
615func roundUpTo8(val uintptr) uintptr {
616 return (val + align8ByteMask) &^ align8ByteMask
617}
618
619func numOfFloatRegisters() int {
620 switch runtime.GOARCH {
621 case "amd64", "arm64", "loong64", "ppc64le", "riscv64":
622 return 8
623 case "s390x":
624 return 4
625 case "arm":
626 return 16
627 case "386":
628 // i386 SysV ABI passes all arguments on the stack, including floats
629 return 0
630 default:
631 // since this platform isn't supported and can therefore only access
632 // integer registers it is safest to return 8
633 return 8
634 }
635}
636
637func numOfIntegerRegisters() int {
638 switch runtime.GOARCH {
639 case "arm64", "loong64", "ppc64le", "riscv64":
640 return 8
641 case "amd64":
642 return 6
643 case "s390x":
644 // S390X uses R2-R6 for integer arguments
645 return 5
646 case "arm":
647 return 4
648 case "386":
649 // i386 SysV ABI passes all arguments on the stack
650 return 0
651 default:
652 // since this platform isn't supported and can therefore only access
653 // integer registers it is fine to return the maxArgs
654 return maxArgs
655 }
656}
657
658// estimateStackBytes estimates stack bytes needed for Darwin ARM64 validation.
659// This is a conservative estimate used only for early error detection.
660func estimateStackBytes(ty reflect.Type) int {
661 var numInts, numFloats int
662 var stackBytes int
663
664 for i := 0; i < ty.NumIn(); i++ {
665 arg := ty.In(i)
666 size := int(arg.Size())
667
668 // Check if this goes to register or stack
669 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
670 if usesInt && numInts < numOfIntegerRegisters() {
671 numInts++
672 } else if !usesInt && numFloats < numOfFloatRegisters() {
673 numFloats++
674 } else {
675 // Goes to stack - accumulate total bytes
676 stackBytes += size
677 }
678 }
679 // Round total to 8-byte boundary
680 if stackBytes > 0 && stackBytes%align8ByteSize != 0 {
681 stackBytes = int(roundUpTo8(uintptr(stackBytes)))
682 }
683 return stackBytes
684}