main 3a10209

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/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}

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