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