v0.8.0-alpha.2 bcc8fd4

func.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build darwin || freebsd || linux || windows
5
6package purego
7
8import (
9	"fmt"
10	"math"
11	"reflect"
12	"runtime"
13	"unsafe"
14
15	"github.com/ebitengine/purego/internal/strings"
16)
17
18// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
19// It panics if it can't find the name symbol.
20func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
21	sym, err := loadSymbol(handle, name)
22	if err != nil {
23		panic(err)
24	}
25	RegisterFunc(fptr, sym)
26}
27
28// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
29// fptr will be set to a function that when called will call the C function given by cfn with the
30// parameters passed in the correct registers and stack.
31//
32// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
33//
34// These conversions describe how a Go type in the fptr will be used to call
35// the C function. It is important to note that there is no way to verify that fptr
36// matches the C function. This also holds true for struct types where the padding
37// needs to be ensured to match that of C; RegisterFunc does not verify this.
38//
39// # Type Conversions (Go <=> C)
40//
41//	string <=> char*
42//	bool <=> _Bool
43//	uintptr <=> uintptr_t
44//	uint <=> uint32_t or uint64_t
45//	uint8 <=> uint8_t
46//	uint16 <=> uint16_t
47//	uint32 <=> uint32_t
48//	uint64 <=> uint64_t
49//	int <=> int32_t or int64_t
50//	int8 <=> int8_t
51//	int16 <=> int16_t
52//	int32 <=> int32_t
53//	int64 <=> int64_t
54//	float32 <=> float
55//	float64 <=> double
56//	struct <=> struct (WIP - darwin only)
57//	func <=> C function
58//	unsafe.Pointer, *T <=> void*
59//	[]T => void*
60//
61// There is a special case when the last argument of fptr is a variadic interface (or []interface}
62// it will be expanded into a call to the C function as if it had the arguments in that slice.
63// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
64// This is not the same as C variadic.
65//
66// # Memory
67//
68// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
69// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
70// hold onto a reference to Go memory. This is the same as the [Cgo rules].
71//
72// However, there are some special cases. When passing a string as an argument if the string does not end in a null
73// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
74// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
75// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
76// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
77// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
78// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
79// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
80// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
81// 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
82// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
83// of the pointer
84//
85// # Structs
86//
87// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
88// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
89// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
90//
91// # Example
92//
93// All functions below call this C function:
94//
95//	char *foo(char *str);
96//
97//	// Let purego convert types
98//	var foo func(s string) string
99//	goString := foo("copied")
100//	// Go will garbage collect this string
101//
102//	// Manually, handle allocations
103//	var foo2 func(b string) *byte
104//	mustFree := foo2("not copied\x00")
105//	defer free(mustFree)
106//
107// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
108func RegisterFunc(fptr interface{}, cfn uintptr) {
109	fn := reflect.ValueOf(fptr).Elem()
110	ty := fn.Type()
111	if ty.Kind() != reflect.Func {
112		panic("purego: fptr must be a function pointer")
113	}
114	if ty.NumOut() > 1 {
115		panic("purego: function can only return zero or one values")
116	}
117	if cfn == 0 {
118		panic("purego: cfn is nil")
119	}
120	if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
121		runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" {
122		panic("purego: float returns are not supported")
123	}
124	{
125		// this code checks how many registers and stack this function will use
126		// to avoid crashing with too many arguments
127		var ints int
128		var floats int
129		var stack int
130		for i := 0; i < ty.NumIn(); i++ {
131			arg := ty.In(i)
132			switch arg.Kind() {
133			case reflect.Func:
134				// This only does preliminary testing to ensure the CDecl argument
135				// is the first argument. Full testing is done when the callback is actually
136				// created in NewCallback.
137				for j := 0; j < arg.NumIn(); j++ {
138					in := arg.In(j)
139					if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
140						continue
141					}
142					if j != 0 {
143						panic("purego: CDecl must be the first argument")
144					}
145				}
146			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
147				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
148				reflect.Slice, reflect.Bool:
149				if ints < numOfIntegerRegisters() {
150					ints++
151				} else {
152					stack++
153				}
154			case reflect.Float32, reflect.Float64:
155				if is32bit {
156					panic("purego: floats only supported on 64bit platforms")
157				}
158				if floats < numOfFloats {
159					floats++
160				} else {
161					stack++
162				}
163			case reflect.Struct:
164				if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
165					panic("purego: struct arguments are only supported on darwin amd64 & arm64")
166				}
167				if arg.Size() == 0 {
168					continue
169				}
170				addInt := func(u uintptr) {
171					ints++
172				}
173				addFloat := func(u uintptr) {
174					floats++
175				}
176				addStack := func(u uintptr) {
177					stack++
178				}
179				_ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
180			default:
181				panic("purego: unsupported kind " + arg.Kind().String())
182			}
183		}
184		if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
185			if runtime.GOOS != "darwin" {
186				panic("purego: struct return values only supported on darwin arm64 & amd64")
187			}
188			outType := ty.Out(0)
189			checkStructFieldsSupported(outType)
190			if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
191				// on amd64 if struct is bigger than 16 bytes allocate the return struct
192				// and pass it in as a hidden first argument.
193				ints++
194			}
195		}
196		sizeOfStack := maxArgs - numOfIntegerRegisters()
197		if stack > sizeOfStack {
198			panic("purego: too many arguments")
199		}
200	}
201	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
202		if len(args) > 0 {
203			if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
204				// subtract one from args bc the last argument in args is []interface{}
205				// which we are currently expanding
206				tmp := make([]reflect.Value, len(args)-1+len(variadic))
207				n := copy(tmp, args[:len(args)-1])
208				for i, v := range variadic {
209					tmp[n+i] = reflect.ValueOf(v)
210				}
211				args = tmp
212			}
213		}
214		var sysargs [maxArgs]uintptr
215		stack := sysargs[numOfIntegerRegisters():]
216		var floats [numOfFloats]uintptr
217		var numInts int
218		var numFloats int
219		var numStack int
220		var addStack, addInt, addFloat func(x uintptr)
221		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
222			// Windows arm64 uses the same calling convention as macOS and Linux
223			addStack = func(x uintptr) {
224				stack[numStack] = x
225				numStack++
226			}
227			addInt = func(x uintptr) {
228				if numInts >= numOfIntegerRegisters() {
229					addStack(x)
230				} else {
231					sysargs[numInts] = x
232					numInts++
233				}
234			}
235			addFloat = func(x uintptr) {
236				if numFloats < len(floats) {
237					floats[numFloats] = x
238					numFloats++
239				} else {
240					addStack(x)
241				}
242			}
243		} else {
244			// On Windows amd64 the arguments are passed in the numbered registered.
245			// So the first int is in the first integer register and the first float
246			// is in the second floating register if there is already a first int.
247			// This is in contrast to how macOS and Linux pass arguments which
248			// tries to use as many registers as possible in the calling convention.
249			addStack = func(x uintptr) {
250				sysargs[numStack] = x
251				numStack++
252			}
253			addInt = addStack
254			addFloat = addStack
255		}
256
257		var keepAlive []interface{}
258		defer func() {
259			runtime.KeepAlive(keepAlive)
260			runtime.KeepAlive(args)
261		}()
262		var syscall syscall15Args
263		if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
264			outType := ty.Out(0)
265			if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
266				val := reflect.New(outType)
267				keepAlive = append(keepAlive, val)
268				addInt(val.Pointer())
269			} else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
270				isAllFloats, numFields := isAllSameFloat(outType)
271				if !isAllFloats || numFields > 4 {
272					val := reflect.New(outType)
273					keepAlive = append(keepAlive, val)
274					syscall.arm64_r8 = val.Pointer()
275				}
276			}
277		}
278		for _, v := range args {
279			switch v.Kind() {
280			case reflect.String:
281				ptr := strings.CString(v.String())
282				keepAlive = append(keepAlive, ptr)
283				addInt(uintptr(unsafe.Pointer(ptr)))
284			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
285				addInt(uintptr(v.Uint()))
286			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
287				addInt(uintptr(v.Int()))
288			case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
289				// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
290				addInt(v.Pointer())
291			case reflect.Func:
292				addInt(NewCallback(v.Interface()))
293			case reflect.Bool:
294				if v.Bool() {
295					addInt(1)
296				} else {
297					addInt(0)
298				}
299			case reflect.Float32:
300				addFloat(uintptr(math.Float32bits(float32(v.Float()))))
301			case reflect.Float64:
302				addFloat(uintptr(math.Float64bits(v.Float())))
303			case reflect.Struct:
304				keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
305			default:
306				panic("purego: unsupported kind: " + v.Kind().String())
307			}
308		}
309		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
310			// Use the normal arm64 calling convention even on Windows
311			syscall = syscall15Args{
312				cfn,
313				sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
314				sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
315				sysargs[12], sysargs[13], sysargs[14],
316				floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
317				syscall.arm64_r8,
318			}
319			runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
320		} else {
321			// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
322			syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
323				sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
324				sysargs[12], sysargs[13], sysargs[14])
325			syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
326		}
327		if ty.NumOut() == 0 {
328			return nil
329		}
330		outType := ty.Out(0)
331		v := reflect.New(outType).Elem()
332		switch outType.Kind() {
333		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
334			v.SetUint(uint64(syscall.a1))
335		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
336			v.SetInt(int64(syscall.a1))
337		case reflect.Bool:
338			v.SetBool(byte(syscall.a1) != 0)
339		case reflect.UnsafePointer:
340			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
341			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
342		case reflect.Ptr:
343			// It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem()
344			v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
345		case reflect.Func:
346			// wrap this C function in a nicely typed Go function
347			v = reflect.New(outType)
348			RegisterFunc(v.Interface(), syscall.a1)
349		case reflect.String:
350			v.SetString(strings.GoString(syscall.a1))
351		case reflect.Float32:
352			// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
353			// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
354			v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
355		case reflect.Float64:
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(math.Float64frombits(uint64(syscall.f1)))
359		case reflect.Struct:
360			v = getStruct(outType, syscall)
361		default:
362			panic("purego: unsupported return kind: " + outType.Kind().String())
363		}
364		return []reflect.Value{v}
365	})
366	fn.Set(v)
367}
368
369// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
370// if it is bigger than this than enough space must be allocated on the heap and then passed into
371// the function as the first parameter on amd64 or in R8 on arm64.
372//
373// If you change this make sure to update it in objc_runtime_darwin.go
374const maxRegAllocStructSize = 16
375
376func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
377	allFloats = true
378	root := ty.Field(0).Type
379	for root.Kind() == reflect.Struct {
380		root = root.Field(0).Type
381	}
382	first := root.Kind()
383	if first != reflect.Float32 && first != reflect.Float64 {
384		allFloats = false
385	}
386	for i := 0; i < ty.NumField(); i++ {
387		f := ty.Field(i).Type
388		if f.Kind() == reflect.Struct {
389			var structNumFields int
390			allFloats, structNumFields = isAllSameFloat(f)
391			numFields += structNumFields
392			continue
393		}
394		numFields++
395		if f.Kind() != first {
396			allFloats = false
397		}
398	}
399	return allFloats, numFields
400}
401
402func checkStructFieldsSupported(ty reflect.Type) {
403	for i := 0; i < ty.NumField(); i++ {
404		f := ty.Field(i).Type
405		if f.Kind() == reflect.Array {
406			f = f.Elem()
407		} else if f.Kind() == reflect.Struct {
408			checkStructFieldsSupported(f)
409			continue
410		}
411		switch f.Kind() {
412		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
413			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
414			reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
415		default:
416			panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
417		}
418	}
419}
420
421const is32bit = unsafe.Sizeof(uintptr(0)) == 4
422
423func roundUpTo8(val uintptr) uintptr {
424	return (val + 7) &^ 7
425}
426
427func numOfIntegerRegisters() int {
428	switch runtime.GOARCH {
429	case "arm64":
430		return 8
431	case "amd64":
432		return 6
433	default:
434		// since this platform isn't supported and can therefore only access
435		// integer registers it is fine to return the maxArgs
436		return maxArgs
437	}
438}

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