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 "structs"
14 "sync"
15 "unsafe"
16
17 "github.com/ebitengine/purego/internal/strings"
18)
19
20const (
21 align8ByteMask = 7 // Mask for 8-byte alignment: (val + 7) &^ 7
22 align8ByteSize = 8 // 8-byte alignment boundary
23)
24
25func isARMSoftFloat() bool {
26 return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0
27}
28
29var thePool = sync.Pool{New: func() any {
30 return new(syscallArgs)
31}}
32
33// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
34// It panics if it can't find the name symbol.
35func RegisterLibFunc(fptr any, handle uintptr, name string) {
36 sym, err := loadSymbol(handle, name)
37 if err != nil {
38 panic(err)
39 }
40 RegisterFunc(fptr, sym)
41}
42
43// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
44// fptr will be set to a function that when called will call the C function given by cfn with the
45// parameters passed in the correct registers and stack.
46//
47// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
48//
49// These conversions describe how a Go type in the fptr will be used to call
50// the C function. It is important to note that there is no way to verify that fptr
51// matches the C function. This also holds true for struct types where the padding
52// needs to be ensured to match that of C; RegisterFunc does not verify this.
53//
54// # Type Conversions (Go <=> C)
55//
56// string <=> char*
57// bool <=> _Bool
58// uintptr <=> uintptr_t
59// uint <=> uint32_t or uint64_t
60// uint8 <=> uint8_t
61// uint16 <=> uint16_t
62// uint32 <=> uint32_t
63// uint64 <=> uint64_t
64// int <=> int32_t or int64_t
65// int8 <=> int8_t
66// int16 <=> int16_t
67// int32 <=> int32_t
68// int64 <=> int64_t
69// float32 <=> float
70// float64 <=> double
71// struct <=> struct (android, darwin, ios, linux, and windows on amd64/arm64)
72// func <=> C function
73// unsafe.Pointer, *T <=> void*
74// []T => void*
75//
76// There is a special case when the last argument of fptr is a variadic interface (or []interface}
77// it will be expanded into a call to the C function as if it had the arguments in that slice.
78// This means that using arg ...any is like a cast to the function with the arguments inside arg.
79// This is not the same as C variadic.
80//
81// # Memory
82//
83// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
84// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
85// hold onto a reference to Go memory. This is the same as the [Cgo rules].
86//
87// However, there are some special cases. When passing a string as an argument if the string does not end in a null
88// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
89// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
90// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
91// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
92// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
93// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
94// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
95// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
96// 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
97// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
98// of the pointer
99//
100// # Structs
101//
102// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
103// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
104// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
105//
106// On Apple ARM64 platforms (macOS and iOS), purego handles proper alignment of struct arguments
107// when passing them on the stack, following the C ABI's byte-level packing rules.
108//
109// On Windows, struct arguments and returns are supported on amd64 and arm64 when calling C functions.
110// Passing or returning structs in callbacks created with [NewCallback] is not supported on Windows.
111//
112// # Example
113//
114// All functions below call this C function:
115//
116// char *foo(char *str);
117//
118// // Let purego convert types
119// var foo func(s string) string
120// goString := foo("copied")
121// // Go will garbage collect this string
122//
123// // Manually, handle allocations
124// var foo2 func(b string) *byte
125// mustFree := foo2("not copied\x00")
126// defer free(mustFree)
127//
128// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
129func RegisterFunc(fptr any, cfn uintptr) {
130 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
131 fn := reflect.ValueOf(fptr).Elem()
132 ty := fn.Type()
133 if ty.Kind() != reflect.Func {
134 panic("purego: fptr must be a function pointer")
135 }
136 if ty.NumOut() > 1 {
137 panic("purego: function can only return zero or one values")
138 }
139 if cfn == 0 {
140 panic("purego: cfn is nil")
141 }
142 if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
143 runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" {
144 panic("purego: float returns are not supported")
145 }
146 {
147 // this code checks how many registers and stack this function will use
148 // to avoid crashing with too many arguments
149 var ints int
150 var floats int
151 floatArgRegs := numOfFloatRegisters()
152 ptrSize := unsafe.Sizeof(uintptr(0))
153 var stack int
154 for i := range ty.NumIn() {
155 arg := ty.In(i)
156 switch arg.Kind() {
157 case reflect.Func:
158 // This only does preliminary testing to ensure the CDecl argument
159 // is the first argument. Full testing is done when the callback is actually
160 // created in NewCallback.
161 for j := range arg.NumIn() {
162 in := arg.In(j)
163 if !in.AssignableTo(reflect.TypeFor[CDecl]()) {
164 continue
165 }
166 if j != 0 {
167 panic("purego: CDecl must be the first argument")
168 }
169 }
170 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
171 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer,
172 reflect.Slice, reflect.Bool:
173 if ints < numOfIntegerRegisters() {
174 ints++
175 } else {
176 stack++
177 }
178 case reflect.Uint64, reflect.Int64:
179 usesSlots := max(1, int(arg.Size()/ptrSize))
180 if isARMPaddingNeeded(arg, ints, stack) {
181 usesSlots++
182 }
183
184 if ints < numOfIntegerRegisters() {
185 ints += usesSlots
186 } else {
187 stack += usesSlots
188 }
189 case reflect.Float32, reflect.Float64:
190 usesSlots := max(1, int(arg.Size()/ptrSize))
191 if isARMSoftFloat() {
192 // float64 for arm with softfloat uses same rules as int64
193 if isARMPaddingNeeded(arg, ints, stack) {
194 usesSlots++
195 }
196 if ints < numOfIntegerRegisters() {
197 ints += usesSlots
198 } else {
199 stack += usesSlots
200 }
201 continue
202 }
203
204 if isARMFloatPaddingNeeded(arg, floats, stack) {
205 usesSlots++
206 }
207 if floats < floatArgRegs {
208 floats++
209 } else {
210 stack += usesSlots
211 }
212 case reflect.Struct:
213 ensureStructSupported()
214 if arg.Size() == 0 && runtime.GOOS != "windows" {
215 // On Windows an empty struct still consumes one argument slot.
216 continue
217 }
218 addInt := func(u uintptr) {
219 ints++
220 }
221 addFloat := func(u uintptr) {
222 floats++
223 }
224 addStack := func(u uintptr) {
225 stack++
226 }
227 _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
228 default:
229 panic("purego: unsupported kind " + arg.Kind().String())
230 }
231 }
232 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
233 ensureStructSupported()
234 outType := ty.Out(0)
235 checkStructFieldsSupported(outType)
236 if structReturnInMemory(outType) {
237 // A struct returned in memory is allocated by the caller and its
238 // pointer is passed as a hidden first integer argument. When the
239 // integer registers are already full, prepending it spills a
240 // regular argument onto the stack.
241 if ints < numOfIntegerRegisters() {
242 ints++
243 } else {
244 stack++
245 }
246 }
247 }
248
249 argsLimit := maxArgs
250 sizeOfStack := argsLimit - numOfIntegerRegisters()
251 if runtime.GOOS == "windows" {
252 if ints+floats+stack > argsLimit {
253 panic("purego: too many stack arguments")
254 }
255 } else if isDarwin && runtime.GOARCH == "arm64" {
256 // On Darwin ARM64, use byte-based validation since arguments pack efficiently.
257 // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
258 stackBytes := estimateStackBytes(ty)
259 maxStackBytes := sizeOfStack * 8
260 if stackBytes > maxStackBytes {
261 panic("purego: too many stack arguments")
262 }
263 } else {
264 if stack > sizeOfStack {
265 panic("purego: too many stack arguments")
266 }
267 }
268 }
269
270 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
271 var sysargs [maxArgs]uintptr
272 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
273 // since numOfFloatRegisters() is a function call, not a constant.
274 // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
275 var floats [maxArgs]uintptr
276 floatArgRegs := numOfFloatRegisters()
277 var numInts int
278 var numFloats int
279 var numStack int
280 var addStack, addInt, addFloat func(x uintptr)
281 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
282 // Windows arm64 uses the same calling convention as macOS and Linux
283 addStack = func(x uintptr) {
284 sysargs[numOfIntegerRegisters()+numStack] = x
285 numStack++
286 }
287 addInt = func(x uintptr) {
288 if numInts >= numOfIntegerRegisters() {
289 addStack(x)
290 } else {
291 sysargs[numInts] = x
292 numInts++
293 }
294 }
295 addFloat = func(x uintptr) {
296 if numFloats < floatArgRegs {
297 floats[numFloats] = x
298 numFloats++
299 } else {
300 addStack(x)
301 }
302 }
303 } else {
304 // On Windows amd64 the arguments are passed in the numbered registered.
305 // So the first int is in the first integer register and the first float
306 // is in the second floating register if there is already a first int.
307 // This is in contrast to how macOS and Linux pass arguments which
308 // tries to use as many registers as possible in the calling convention.
309 addStack = func(x uintptr) {
310 if numStack >= maxArgs {
311 panic("purego: too many stack arguments")
312 }
313 sysargs[numStack] = x
314 numStack++
315 }
316 addInt = addStack
317 addFloat = addStack
318 }
319
320 var keepAlive []any
321 defer func() {
322 runtime.KeepAlive(keepAlive)
323 runtime.KeepAlive(args)
324 }()
325
326 var arm64_r8 uintptr
327 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
328 outType := ty.Out(0)
329 if structReturnInMemory(outType) {
330 // The caller allocates the return value and passes its pointer
331 // as a hidden first integer argument.
332 val := reflect.New(outType)
333 keepAlive = append(keepAlive, val)
334 addInt(val.Pointer())
335 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
336 isAllFloats, numFields := isAllSameFloat(outType)
337 if !isAllFloats || numFields > 4 {
338 val := reflect.New(outType)
339 keepAlive = append(keepAlive, val)
340 arm64_r8 = val.Pointer()
341 }
342 }
343 }
344 for i, v := range args {
345 if variadic, ok := reflect.TypeAssert[[]any](args[i]); ok {
346 if i != len(args)-1 {
347 panic("purego: can only expand last parameter")
348 }
349 for _, x := range variadic {
350 keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
351 }
352 continue
353 }
354 // Check if we need to start Darwin ARM64 C-style stack packing
355 if runtime.GOARCH == "arm64" && isDarwin && shouldBundleStackArgs(v, numInts, numFloats) {
356 // Collect and separate remaining args into register vs stack
357 stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
358 keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
359 keepAlive = newKeepAlive
360
361 // Bundle stack arguments with C-style packing
362 bundleStackArgs(stackArgs, addStack)
363 break
364 }
365 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
366 }
367
368 var syscall *syscallArgs
369 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
370 // Windows amd64, 386, and arm use syscall.SyscallN.
371 syscall = thePool.Get().(*syscallArgs)
372 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
373 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
374 } else {
375 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
376 }
377 defer thePool.Put(syscall)
378 if ty.NumOut() == 0 {
379 return nil
380 }
381 outType := ty.Out(0)
382 v := reflect.New(outType).Elem()
383 switch outType.Kind() {
384 case reflect.Uint64:
385 if is32bit {
386 // high-word is recorded at a2 for 32-bit platforms and 64-bit returns
387 v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32))
388 } else {
389 v.SetUint(uint64(syscall.a1))
390 }
391 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
392 v.SetUint(uint64(syscall.a1))
393 case reflect.Int64:
394 if is32bit {
395 v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32))
396 } else {
397 v.SetInt(int64(syscall.a1))
398 }
399 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
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.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
406 case reflect.Pointer:
407 // Copy syscall.a1 into a local variable to prevent v
408 // from holding a pointer to the pooled syscallArgs field.
409 a1 := syscall.a1
410 v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem()
411 case reflect.Func:
412 // wrap this C function in a nicely typed Go function
413 if syscall.a1 != 0 {
414 RegisterFunc(v.Addr().Interface(), syscall.a1)
415 }
416 case reflect.String:
417 v.SetString(strings.GoString(syscall.a1))
418 case reflect.Float32:
419 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
420 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
421 // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
422 // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
423 // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
424 // On 32bit ARM with softfloat float32 returned as integer
425 switch runtime.GOARCH {
426 case "386":
427 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
428 case "ppc64le":
429 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
430 case "s390x":
431 // S390X is big-endian: float32 in upper 32 bits of 64-bit register
432 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
433 case "arm":
434 if isARMSoftFloat() {
435 v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1))))
436 } else {
437 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
438 }
439 default:
440 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
441 }
442 case reflect.Float64:
443 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
444 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
445 if isARMSoftFloat() {
446 // a1,a2 are populated in this case
447 v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32)))
448 } else if is32bit {
449 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
450 } else {
451 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
452 }
453 case reflect.Struct:
454 v = getStruct(outType, *syscall)
455 default:
456 panic("purego: unsupported return kind: " + outType.Kind().String())
457 }
458 if len(args) > 0 {
459 // reuse args slice instead of allocating one when possible
460 args[0] = v
461 return args[:1]
462 } else {
463 return []reflect.Value{v}
464 }
465 })
466 fn.Set(v)
467}
468
469func 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 {
470 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
471 switch v.Kind() {
472 case reflect.String:
473 ptr := strings.CString(v.String())
474 keepAlive = append(keepAlive, ptr)
475 addInt(uintptr(unsafe.Pointer(ptr)))
476 case reflect.Uint64:
477 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
478 addInt(0)
479 }
480 addInt(uintptr(v.Uint()))
481 if is32bit {
482 addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too
483 }
484 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
485 addInt(uintptr(v.Uint()))
486 case reflect.Int64:
487 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
488 addInt(0)
489 }
490 addInt(uintptr(v.Int()))
491 if is32bit {
492 addInt(uintptr(v.Int() >> 32))
493 }
494 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
495 addInt(uintptr(v.Int()))
496 case reflect.Pointer, reflect.UnsafePointer, reflect.Slice:
497 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
498 addInt(v.Pointer())
499 case reflect.Func:
500 addInt(NewCallback(v.Interface()))
501 case reflect.Bool:
502 if v.Bool() {
503 addInt(1)
504 } else {
505 addInt(0)
506 }
507 case reflect.Float32:
508 switch runtime.GOARCH {
509 case "ppc64le":
510 // A single-precision argument occupies a floating-point register in double format on Power.
511 addFloat(uintptr(math.Float64bits(v.Float())))
512 case "s390x":
513 // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register.
514 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
515 case "arm":
516 if isARMSoftFloat() {
517 // 32-bit ARM with softfloat: float32 goes as integer
518 addInt(uintptr(math.Float32bits(float32(v.Float()))))
519 } else {
520 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
521 }
522 default:
523 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
524 }
525 case reflect.Float64:
526 bits := math.Float64bits(v.Float())
527 if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
528 // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
529 addFloat(0)
530 }
531 if isARMSoftFloat() {
532 // add as uint64
533 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
534 addInt(0)
535 }
536 addInt(uintptr(bits))
537 addInt(uintptr(bits >> 32))
538 } else if is32bit {
539 addFloat(uintptr(bits))
540 addFloat(uintptr(bits >> 32))
541 } else {
542 addFloat(uintptr(bits))
543 }
544 case reflect.Struct:
545 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
546 default:
547 panic("purego: unsupported kind: " + v.Kind().String())
548 }
549 return keepAlive
550}
551
552// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
553// if it is bigger than this than enough space must be allocated on the heap and then passed into
554// the function as the first parameter on amd64 or in R8 on arm64.
555//
556// If you change this make sure to update it in objc_runtime_darwin.go
557const maxRegAllocStructSize = 16
558
559var hostLayoutType = reflect.TypeFor[structs.HostLayout]()
560
561// isABIField reports whether f takes part in the C ABI of the struct that
562// contains it. Only the structs.HostLayout marker does not.
563func isABIField(f reflect.StructField) bool {
564 return !f.Type.ConvertibleTo(hostLayoutType)
565}
566
567// numABIFields returns how many of ty's fields take part in the C ABI.
568func numABIFields(ty reflect.Type) int {
569 var n int
570 for i := range ty.NumField() {
571 if isABIField(ty.Field(i)) {
572 n++
573 }
574 }
575 return n
576}
577
578// abiField returns the i'th field of ty that takes part in the C ABI. It panics
579// if ty has fewer than i+1 such fields.
580func abiField(ty reflect.Type, i int) reflect.StructField {
581 for j := range ty.NumField() {
582 f := ty.Field(j)
583 if !isABIField(f) {
584 continue
585 }
586 if i == 0 {
587 return f
588 }
589 i--
590 }
591 panic("purego: struct field index out of range")
592}
593
594func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
595 allFloats = true
596 if numABIFields(ty) == 0 {
597 return false, 0
598 }
599 root := abiField(ty, 0).Type
600 for root.Kind() == reflect.Struct {
601 if numABIFields(root) == 0 {
602 return false, 0
603 }
604 root = abiField(root, 0).Type
605 }
606 first := root.Kind()
607 if first != reflect.Float32 && first != reflect.Float64 {
608 allFloats = false
609 }
610 for i := range ty.NumField() {
611 if !isABIField(ty.Field(i)) {
612 continue
613 }
614 f := ty.Field(i).Type
615 if f.Kind() == reflect.Struct {
616 var structNumFields int
617 allFloats, structNumFields = isAllSameFloat(f)
618 numFields += structNumFields
619 continue
620 }
621 numFields++
622 if f.Kind() != first {
623 allFloats = false
624 }
625 }
626 return allFloats, numFields
627}
628
629func checkStructFieldsSupported(ty reflect.Type) {
630 for i := range ty.NumField() {
631 if !isABIField(ty.Field(i)) {
632 continue
633 }
634 f := ty.Field(i).Type
635 if f.Kind() == reflect.Array {
636 f = f.Elem()
637 } else if f.Kind() == reflect.Struct {
638 checkStructFieldsSupported(f)
639 continue
640 }
641 switch f.Kind() {
642 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
643 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
644 reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
645 reflect.Bool:
646 default:
647 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
648 }
649 }
650}
651
652// ensureStructSupported panics if passing or returning structs through a call to
653// a C function is unsupported on the current platform.
654func ensureStructSupported() {
655 switch runtime.GOARCH {
656 case "amd64", "arm64", "loong64", "ppc64le":
657 default:
658 panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le")
659 }
660 switch runtime.GOOS {
661 case "android", "darwin", "ios", "linux", "windows":
662 default:
663 panic("purego: struct arguments/returns are only supported on android, darwin, ios, linux, and windows")
664 }
665}
666
667// ensureCallbackStructSupported panics if passing or returning structs through a
668// callback is unsupported on the current platform. Callbacks support structs on
669// fewer architectures than a direct call to a C function.
670func ensureCallbackStructSupported() {
671 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
672 panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
673 }
674 switch runtime.GOOS {
675 case "android", "darwin", "ios", "linux", "windows":
676 default:
677 panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
678 }
679}
680
681// isDarwin is true on platforms that use Apple's calling convention.
682// iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
683const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
684
685func roundUpTo8(val uintptr) uintptr {
686 return (val + align8ByteMask) &^ align8ByteMask
687}
688
689func numOfFloatRegisters() int {
690 switch runtime.GOARCH {
691 case "amd64", "arm64", "loong64", "ppc64le", "riscv64":
692 return 8
693 case "s390x":
694 return 4
695 case "arm":
696 // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used
697 return 16
698 case "386":
699 // i386 SysV ABI passes all arguments on the stack, including floats
700 return 0
701 default:
702 // since this platform isn't supported and can therefore only access
703 // integer registers it is safest to return 8
704 return 8
705 }
706}
707
708func numOfIntegerRegisters() int {
709 switch runtime.GOARCH {
710 case "arm64", "loong64", "ppc64le", "riscv64":
711 return 8
712 case "amd64":
713 return 6
714 case "s390x":
715 // S390X uses R2-R6 for integer arguments
716 return 5
717 case "arm":
718 return 4
719 case "386":
720 // i386 SysV ABI passes all arguments on the stack
721 return 0
722 default:
723 // since this platform isn't supported and can therefore only access
724 // integer registers it is fine to return the maxArgs
725 return maxArgs
726 }
727}
728
729// estimateStackBytes estimates stack bytes needed for Darwin ARM64 validation.
730// This is a conservative estimate used only for early error detection.
731func estimateStackBytes(ty reflect.Type) int {
732 var numInts, numFloats int
733 var stackBytes int
734
735 for i := range ty.NumIn() {
736 arg := ty.In(i)
737 size := int(arg.Size())
738
739 // Check if this goes to register or stack
740 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
741 if usesInt && numInts < numOfIntegerRegisters() {
742 numInts++
743 } else if !usesInt && numFloats < numOfFloatRegisters() {
744 numFloats++
745 } else {
746 stackBytes += size
747 }
748 }
749 // Round total to 8-byte boundary
750 if stackBytes > 0 && stackBytes%align8ByteSize != 0 {
751 stackBytes = int(roundUpTo8(uintptr(stackBytes)))
752 }
753 return stackBytes
754}
755
756func isARMPaddingNeeded(ty reflect.Type, numInts, numStack int) bool {
757 // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an
758 // even core register (C.3); if they then spill, the stack slot is
759 // 8-byte aligned too (C.7).
760 // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits
761 if runtime.GOARCH != "arm" || ty.Size() != 8 {
762 return false
763 }
764 if numInts >= 0 && numInts < numOfIntegerRegisters() {
765 return numInts%2 != 0
766 }
767 return numStack%2 != 0
768}
769
770func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
771 if runtime.GOARCH != "arm" || ty.Size() != 8 {
772 return false
773 }
774 if numFloats >= 0 && numFloats < numOfFloatRegisters() {
775 // float registers are 64bit so alignment never needed for args in registers
776 return false
777 }
778 // Only check if AAPCS C.7 is applicable here
779 return numStack%2 != 0
780}