v0.11.0-alpha.10 749871c

func.go RawHistoryFolder

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 := 0; i < ty.NumIn(); i++ {
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 := 0; j < arg.NumIn(); j++ {
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 := 0; i < ty.NumField(); i++ {
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 := 0; i < ty.NumField(); i++ {
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 := 0; i < ty.NumIn(); i++ {
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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close