v0.11.0-bnema.4 cf1b83e

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

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