v0.11.0-alpha.9 f9d41fc

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

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