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