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/bnema/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.Array, 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 case reflect.Slice:
465 if outType.Elem().Kind() != reflect.String {
466 panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())
467 }
468 // C returns a NULL-terminated char** array.
469 if syscall.a1 != 0 {
470 var result []string
471 p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))
472 for ; *(*uintptr)(p) != 0; p = unsafe.Add(p, unsafe.Sizeof(uintptr(0))) {
473 result = append(result, strings.GoString(*(*uintptr)(p)))
474 }
475 v = reflect.ValueOf(result).Convert(outType)
476 }
477 default:
478 panic("purego: unsupported return kind: " + outType.Kind().String())
479 }
480 if len(args) > 0 {
481 // reuse args slice instead of allocating one when possible
482 args[0] = v
483 return args[:1]
484 } else {
485 return []reflect.Value{v}
486 }
487 })
488 fn.Set(v)
489}
490
491func 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 {
492 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
493 switch v.Kind() {
494 case reflect.String:
495 ptr := strings.CString(v.String())
496 keepAlive = append(keepAlive, ptr)
497 addInt(uintptr(unsafe.Pointer(ptr)))
498 case reflect.Uint64:
499 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
500 addInt(0)
501 }
502 addInt(uintptr(v.Uint()))
503 if is32bit {
504 addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too
505 }
506 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
507 addInt(uintptr(v.Uint()))
508 case reflect.Int64:
509 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
510 addInt(0)
511 }
512 addInt(uintptr(v.Int()))
513 if is32bit {
514 addInt(uintptr(v.Int() >> 32))
515 }
516 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
517 addInt(uintptr(v.Int()))
518 case reflect.Slice:
519 if v.Type().Elem().Kind() != reflect.String {
520 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
521 addInt(v.Pointer())
522 break
523 }
524 if v.IsNil() {
525 addInt(0)
526 break
527 }
528 // Pass []string as a NULL-terminated char** array.
529 res := make([]*byte, v.Len()+1)
530 for i := range v.Len() {
531 res[i] = strings.CString(v.Index(i).String())
532 }
533 keepAlive = append(keepAlive, res)
534 addInt(uintptr(unsafe.Pointer(&res[0])))
535 case reflect.Array:
536 // C array parameters decay to pointers, so pass a pointer to a copy.
537 tmp := reflect.New(v.Type())
538 tmp.Elem().Set(v)
539 keepAlive = append(keepAlive, tmp)
540 addInt(tmp.Pointer())
541 case reflect.Pointer, reflect.UnsafePointer:
542 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
543 addInt(v.Pointer())
544 case reflect.Func:
545 addInt(NewCallback(v.Interface()))
546 case reflect.Bool:
547 if v.Bool() {
548 addInt(1)
549 } else {
550 addInt(0)
551 }
552 case reflect.Float32:
553 switch runtime.GOARCH {
554 case "ppc64le":
555 // A single-precision argument occupies a floating-point register in double format on Power.
556 addFloat(uintptr(math.Float64bits(v.Float())))
557 case "s390x":
558 // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register.
559 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
560 case "arm":
561 if isARMSoftFloat() {
562 // 32-bit ARM with softfloat: float32 goes as integer
563 addInt(uintptr(math.Float32bits(float32(v.Float()))))
564 } else {
565 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
566 }
567 default:
568 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
569 }
570 case reflect.Float64:
571 bits := math.Float64bits(v.Float())
572 if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
573 // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
574 addFloat(0)
575 }
576 if isARMSoftFloat() {
577 // add as uint64
578 if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
579 addInt(0)
580 }
581 addInt(uintptr(bits))
582 addInt(uintptr(bits >> 32))
583 } else if is32bit {
584 addFloat(uintptr(bits))
585 addFloat(uintptr(bits >> 32))
586 } else {
587 addFloat(uintptr(bits))
588 }
589 case reflect.Struct:
590 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
591 default:
592 panic("purego: unsupported kind: " + v.Kind().String())
593 }
594 return keepAlive
595}
596
597// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
598// if it is bigger than this than enough space must be allocated on the heap and then passed into
599// the function as the first parameter on amd64 or in R8 on arm64.
600//
601// If you change this make sure to update it in objc_runtime_darwin.go
602const maxRegAllocStructSize = 16
603
604var hostLayoutType = reflect.TypeFor[structs.HostLayout]()
605
606// isABIField reports whether f takes part in the C ABI of the struct that
607// contains it. Only the structs.HostLayout marker does not.
608func isABIField(f reflect.StructField) bool {
609 return !f.Type.ConvertibleTo(hostLayoutType)
610}
611
612// numABIFields returns how many of ty's fields take part in the C ABI.
613func numABIFields(ty reflect.Type) int {
614 var n int
615 for i := range ty.NumField() {
616 if isABIField(ty.Field(i)) {
617 n++
618 }
619 }
620 return n
621}
622
623// abiField returns the i'th field of ty that takes part in the C ABI. It panics
624// if ty has fewer than i+1 such fields.
625func abiField(ty reflect.Type, i int) reflect.StructField {
626 for j := range ty.NumField() {
627 f := ty.Field(j)
628 if !isABIField(f) {
629 continue
630 }
631 if i == 0 {
632 return f
633 }
634 i--
635 }
636 panic("purego: struct field index out of range")
637}
638
639func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
640 allFloats = true
641 if numABIFields(ty) == 0 {
642 return false, 0
643 }
644 root := abiField(ty, 0).Type
645 for root.Kind() == reflect.Struct {
646 if numABIFields(root) == 0 {
647 return false, 0
648 }
649 root = abiField(root, 0).Type
650 }
651 first := root.Kind()
652 if first != reflect.Float32 && first != reflect.Float64 {
653 allFloats = false
654 }
655 for i := range ty.NumField() {
656 if !isABIField(ty.Field(i)) {
657 continue
658 }
659 f := ty.Field(i).Type
660 if f.Kind() == reflect.Struct {
661 var structNumFields int
662 allFloats, structNumFields = isAllSameFloat(f)
663 numFields += structNumFields
664 continue
665 }
666 numFields++
667 if f.Kind() != first {
668 allFloats = false
669 }
670 }
671 return allFloats, numFields
672}
673
674func checkStructFieldsSupported(ty reflect.Type) {
675 for i := range ty.NumField() {
676 if !isABIField(ty.Field(i)) {
677 continue
678 }
679 f := ty.Field(i).Type
680 if f.Kind() == reflect.Array {
681 f = f.Elem()
682 } else if f.Kind() == reflect.Struct {
683 checkStructFieldsSupported(f)
684 continue
685 }
686 switch f.Kind() {
687 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
688 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
689 reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
690 reflect.Bool:
691 default:
692 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
693 }
694 }
695}
696
697// ensureStructSupported panics if passing or returning structs through a call to
698// a C function is unsupported on the current platform.
699func ensureStructSupported() {
700 switch runtime.GOARCH {
701 case "amd64", "arm64", "loong64", "ppc64le":
702 default:
703 panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le")
704 }
705 switch runtime.GOOS {
706 case "android", "darwin", "ios", "linux", "windows":
707 default:
708 panic("purego: struct arguments/returns are only supported on android, darwin, ios, linux, and windows")
709 }
710}
711
712// ensureCallbackStructSupported panics if passing or returning structs through a
713// callback is unsupported on the current platform. Callbacks support structs on
714// fewer architectures than a direct call to a C function.
715func ensureCallbackStructSupported() {
716 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
717 panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
718 }
719 switch runtime.GOOS {
720 case "android", "darwin", "ios", "linux", "windows":
721 default:
722 panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
723 }
724}
725
726// isDarwin is true on platforms that use Apple's calling convention.
727// iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
728const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
729
730func roundUpTo8(val uintptr) uintptr {
731 return (val + align8ByteMask) &^ align8ByteMask
732}
733
734func numOfFloatRegisters() int {
735 switch runtime.GOARCH {
736 case "amd64", "arm64", "loong64", "ppc64le", "riscv64":
737 return 8
738 case "s390x":
739 return 4
740 case "arm":
741 // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used
742 return 16
743 case "386":
744 // i386 SysV ABI passes all arguments on the stack, including floats
745 return 0
746 default:
747 // since this platform isn't supported and can therefore only access
748 // integer registers it is safest to return 8
749 return 8
750 }
751}
752
753func numOfIntegerRegisters() int {
754 switch runtime.GOARCH {
755 case "arm64", "loong64", "ppc64le", "riscv64":
756 return 8
757 case "amd64":
758 return 6
759 case "s390x":
760 // S390X uses R2-R6 for integer arguments
761 return 5
762 case "arm":
763 return 4
764 case "386":
765 // i386 SysV ABI passes all arguments on the stack
766 return 0
767 default:
768 // since this platform isn't supported and can therefore only access
769 // integer registers it is fine to return the maxArgs
770 return maxArgs
771 }
772}
773
774// estimateStackBytes estimates stack bytes needed for Darwin ARM64 validation.
775// This is a conservative estimate used only for early error detection.
776func estimateStackBytes(ty reflect.Type) int {
777 var numInts, numFloats int
778 var stackBytes int
779
780 for i := range ty.NumIn() {
781 arg := ty.In(i)
782 size := int(arg.Size())
783
784 // Check if this goes to register or stack
785 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
786 if usesInt && numInts < numOfIntegerRegisters() {
787 numInts++
788 } else if !usesInt && numFloats < numOfFloatRegisters() {
789 numFloats++
790 } else {
791 stackBytes += size
792 }
793 }
794 // Round total to 8-byte boundary
795 if stackBytes > 0 && stackBytes%align8ByteSize != 0 {
796 stackBytes = int(roundUpTo8(uintptr(stackBytes)))
797 }
798 return stackBytes
799}
800
801func isARMPaddingNeeded(ty reflect.Type, numInts, numStack int) bool {
802 // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an
803 // even core register (C.3); if they then spill, the stack slot is
804 // 8-byte aligned too (C.7).
805 // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits
806 if runtime.GOARCH != "arm" || ty.Size() != 8 {
807 return false
808 }
809 if numInts >= 0 && numInts < numOfIntegerRegisters() {
810 return numInts%2 != 0
811 }
812 return numStack%2 != 0
813}
814
815func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
816 if runtime.GOARCH != "arm" || ty.Size() != 8 {
817 return false
818 }
819 if numFloats >= 0 && numFloats < numOfFloatRegisters() {
820 // float registers are 64bit so alignment never needed for args in registers
821 return false
822 }
823 // Only check if AAPCS C.7 is applicable here
824 return numStack%2 != 0
825}