v0.9.0-alpha.10 3d06096

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	"strconv"
14	"sync"
15	"unsafe"
16
17	"github.com/ebitengine/purego/internal/strings"
18)
19
20var thePool = sync.Pool{New: func() any {
21	return new(syscall15Args)
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 (WIP - darwin only)
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// # Example
98//
99// All functions below call this C function:
100//
101//	char *foo(char *str);
102//
103//	// Let purego convert types
104//	var foo func(s string) string
105//	goString := foo("copied")
106//	// Go will garbage collect this string
107//
108//	// Manually, handle allocations
109//	var foo2 func(b string) *byte
110//	mustFree := foo2("not copied\x00")
111//	defer free(mustFree)
112//
113// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
114func RegisterFunc(fptr any, cfn uintptr) {
115	fn := reflect.ValueOf(fptr).Elem()
116	ty := fn.Type()
117	if ty.Kind() != reflect.Func {
118		panic("purego: fptr must be a function pointer")
119	}
120	if ty.NumOut() > 1 {
121		panic("purego: function can only return zero or one values")
122	}
123	if cfn == 0 {
124		panic("purego: cfn is nil")
125	}
126	if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
127		runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" {
128		panic("purego: float returns are not supported")
129	}
130	{
131		// this code checks how many registers and stack this function will use
132		// to avoid crashing with too many arguments
133		var ints int
134		var floats int
135		var stack int
136		for i := 0; i < ty.NumIn(); i++ {
137			arg := ty.In(i)
138			switch arg.Kind() {
139			case reflect.Func:
140				// This only does preliminary testing to ensure the CDecl argument
141				// is the first argument. Full testing is done when the callback is actually
142				// created in NewCallback.
143				for j := 0; j < arg.NumIn(); j++ {
144					in := arg.In(j)
145					if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
146						continue
147					}
148					if j != 0 {
149						panic("purego: CDecl must be the first argument")
150					}
151				}
152			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
153				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
154				reflect.Slice, reflect.Bool:
155				if ints < numOfIntegerRegisters() {
156					ints++
157				} else {
158					stack++
159				}
160			case reflect.Float32, reflect.Float64:
161				const is32bit = unsafe.Sizeof(uintptr(0)) == 4
162				if is32bit {
163					panic("purego: floats only supported on 64bit platforms")
164				}
165				if floats < numOfFloatRegisters {
166					floats++
167				} else {
168					stack++
169				}
170			case reflect.Struct:
171				if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
172					panic("purego: struct arguments are only supported on darwin amd64 & arm64")
173				}
174				if arg.Size() == 0 {
175					continue
176				}
177				addInt := func(u uintptr) {
178					ints++
179				}
180				addFloat := func(u uintptr) {
181					floats++
182				}
183				addStack := func(u uintptr) {
184					stack++
185				}
186				_ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
187			default:
188				panic("purego: unsupported kind " + arg.Kind().String())
189			}
190		}
191		if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
192			if runtime.GOOS != "darwin" {
193				panic("purego: struct return values only supported on darwin arm64 & amd64")
194			}
195			outType := ty.Out(0)
196			checkStructFieldsSupported(outType)
197			if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
198				// on amd64 if struct is bigger than 16 bytes allocate the return struct
199				// and pass it in as a hidden first argument.
200				ints++
201			}
202		}
203		sizeOfStack := maxArgs - numOfIntegerRegisters()
204		if stack > sizeOfStack {
205			panic("purego: too many arguments")
206		}
207	}
208	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
209		var sysargs [maxArgs]uintptr
210		var floats [numOfFloatRegisters]uintptr
211		var numInts int
212		var numFloats int
213		var numStack int
214		var addStack, addInt, addFloat func(x uintptr)
215		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
216			// Windows arm64 uses the same calling convention as macOS and Linux
217			addStack = func(x uintptr) {
218				sysargs[numOfIntegerRegisters()+numStack] = x
219				numStack++
220			}
221			addInt = func(x uintptr) {
222				if numInts >= numOfIntegerRegisters() {
223					addStack(x)
224				} else {
225					sysargs[numInts] = x
226					numInts++
227				}
228			}
229			addFloat = func(x uintptr) {
230				if numFloats < len(floats) {
231					floats[numFloats] = x
232					numFloats++
233				} else {
234					addStack(x)
235				}
236			}
237		} else {
238			// On Windows amd64 the arguments are passed in the numbered registered.
239			// So the first int is in the first integer register and the first float
240			// is in the second floating register if there is already a first int.
241			// This is in contrast to how macOS and Linux pass arguments which
242			// tries to use as many registers as possible in the calling convention.
243			addStack = func(x uintptr) {
244				sysargs[numStack] = x
245				numStack++
246			}
247			addInt = addStack
248			addFloat = addStack
249		}
250
251		var keepAlive []any
252		defer func() {
253			runtime.KeepAlive(keepAlive)
254			runtime.KeepAlive(args)
255		}()
256
257		var arm64_r8 uintptr
258		if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
259			outType := ty.Out(0)
260			if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
261				val := reflect.New(outType)
262				keepAlive = append(keepAlive, val)
263				addInt(val.Pointer())
264			} else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
265				isAllFloats, numFields := isAllSameFloat(outType)
266				if !isAllFloats || numFields > 4 {
267					val := reflect.New(outType)
268					keepAlive = append(keepAlive, val)
269					arm64_r8 = val.Pointer()
270				}
271			}
272		}
273		for i, v := range args {
274			if variadic, ok := args[i].Interface().([]any); ok {
275				if i != len(args)-1 {
276					panic("purego: can only expand last parameter")
277				}
278				for _, x := range variadic {
279					keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
280				}
281				continue
282			}
283			if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
284				(numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
285				fields := make([]reflect.StructField, len(args[i:]))
286
287				for j, val := range args[i:] {
288					if val.Kind() == reflect.String {
289						ptr := strings.CString(v.String())
290						keepAlive = append(keepAlive, ptr)
291						val = reflect.ValueOf(ptr)
292						args[i+j] = val
293					}
294					fields[j] = reflect.StructField{
295						Name: "X" + strconv.Itoa(j),
296						Type: val.Type(),
297					}
298				}
299				structType := reflect.StructOf(fields)
300				structInstance := reflect.New(structType).Elem()
301				for j, val := range args[i:] {
302					structInstance.Field(j).Set(val)
303				}
304				placeRegisters(structInstance, addFloat, addInt)
305				break
306			}
307			keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
308		}
309
310		syscall := thePool.Get().(*syscall15Args)
311		defer thePool.Put(syscall)
312
313		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
314			// Use the normal arm64 calling convention even on Windows
315			*syscall = syscall15Args{
316				cfn,
317				sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
318				sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
319				sysargs[12], sysargs[13], sysargs[14],
320				floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
321				arm64_r8,
322			}
323			runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
324		} else {
325			*syscall = syscall15Args{}
326			// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
327			syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
328				sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
329				sysargs[12], sysargs[13], sysargs[14])
330			syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
331		}
332		if ty.NumOut() == 0 {
333			return nil
334		}
335		outType := ty.Out(0)
336		v := reflect.New(outType).Elem()
337		switch outType.Kind() {
338		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
339			v.SetUint(uint64(syscall.a1))
340		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
341			v.SetInt(int64(syscall.a1))
342		case reflect.Bool:
343			v.SetBool(byte(syscall.a1) != 0)
344		case reflect.UnsafePointer:
345			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
346			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
347		case reflect.Ptr:
348			v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
349		case reflect.Func:
350			// wrap this C function in a nicely typed Go function
351			v = reflect.New(outType)
352			RegisterFunc(v.Interface(), syscall.a1)
353		case reflect.String:
354			v.SetString(strings.GoString(syscall.a1))
355		case reflect.Float32:
356			// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
357			// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
358			v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
359		case reflect.Float64:
360			// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
361			// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
362			v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
363		case reflect.Struct:
364			v = getStruct(outType, *syscall)
365		default:
366			panic("purego: unsupported return kind: " + outType.Kind().String())
367		}
368		if len(args) > 0 {
369			// reuse args slice instead of allocating one when possible
370			args[0] = v
371			return args[:1]
372		} else {
373			return []reflect.Value{v}
374		}
375	})
376	fn.Set(v)
377}
378
379func 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 {
380	switch v.Kind() {
381	case reflect.String:
382		ptr := strings.CString(v.String())
383		keepAlive = append(keepAlive, ptr)
384		addInt(uintptr(unsafe.Pointer(ptr)))
385	case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
386		addInt(uintptr(v.Uint()))
387	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
388		addInt(uintptr(v.Int()))
389	case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
390		// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
391		addInt(v.Pointer())
392	case reflect.Func:
393		addInt(NewCallback(v.Interface()))
394	case reflect.Bool:
395		if v.Bool() {
396			addInt(1)
397		} else {
398			addInt(0)
399		}
400	case reflect.Float32:
401		addFloat(uintptr(math.Float32bits(float32(v.Float()))))
402	case reflect.Float64:
403		addFloat(uintptr(math.Float64bits(v.Float())))
404	case reflect.Struct:
405		keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
406	default:
407		panic("purego: unsupported kind: " + v.Kind().String())
408	}
409	return keepAlive
410}
411
412// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
413// if it is bigger than this than enough space must be allocated on the heap and then passed into
414// the function as the first parameter on amd64 or in R8 on arm64.
415//
416// If you change this make sure to update it in objc_runtime_darwin.go
417const maxRegAllocStructSize = 16
418
419func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
420	allFloats = true
421	root := ty.Field(0).Type
422	for root.Kind() == reflect.Struct {
423		root = root.Field(0).Type
424	}
425	first := root.Kind()
426	if first != reflect.Float32 && first != reflect.Float64 {
427		allFloats = false
428	}
429	for i := 0; i < ty.NumField(); i++ {
430		f := ty.Field(i).Type
431		if f.Kind() == reflect.Struct {
432			var structNumFields int
433			allFloats, structNumFields = isAllSameFloat(f)
434			numFields += structNumFields
435			continue
436		}
437		numFields++
438		if f.Kind() != first {
439			allFloats = false
440		}
441	}
442	return allFloats, numFields
443}
444
445func checkStructFieldsSupported(ty reflect.Type) {
446	for i := 0; i < ty.NumField(); i++ {
447		f := ty.Field(i).Type
448		if f.Kind() == reflect.Array {
449			f = f.Elem()
450		} else if f.Kind() == reflect.Struct {
451			checkStructFieldsSupported(f)
452			continue
453		}
454		switch f.Kind() {
455		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
456			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
457			reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
458		default:
459			panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
460		}
461	}
462}
463
464func roundUpTo8(val uintptr) uintptr {
465	return (val + 7) &^ 7
466}
467
468func numOfIntegerRegisters() int {
469	switch runtime.GOARCH {
470	case "arm64":
471		return 8
472	case "amd64":
473		return 6
474	default:
475		// since this platform isn't supported and can therefore only access
476		// integer registers it is fine to return the maxArgs
477		return maxArgs
478	}
479}

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