v0.6.0-alpha 4db9e9e

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	"math"
10	"reflect"
11	"runtime"
12	"unsafe"
13
14	"github.com/ebitengine/purego/internal/strings"
15)
16
17// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
18// It panics if it can't find the name symbol.
19func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
20	sym, err := loadSymbol(handle, name)
21	if err != nil {
22		panic(err)
23	}
24	RegisterFunc(fptr, sym)
25}
26
27// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
28// fptr will be set to a function that when called will call the C function given by cfn with the
29// parameters passed in the correct registers and stack.
30//
31// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
32//
33// These conversions describe how a Go type in the fptr will be used to call
34// the C function. It is important to note that there is no way to verify that fptr
35// matches the C function. This also holds true for struct types where the padding
36// needs to be ensured to match that of C; RegisterFunc does not verify this.
37//
38// # Type Conversions (Go <=> C)
39//
40//	string <=> char*
41//	bool <=> _Bool
42//	uintptr <=> uintptr_t
43//	uint <=> uint32_t or uint64_t
44//	uint8 <=> uint8_t
45//	uint16 <=> uint16_t
46//	uint32 <=> uint32_t
47//	uint64 <=> uint64_t
48//	int <=> int32_t or int64_t
49//	int8 <=> int8_t
50//	int16 <=> int16_t
51//	int32 <=> int32_t
52//	int64 <=> int64_t
53//	float32 <=> float (WIP)
54//	float64 <=> double (WIP)
55//	struct <=> struct (WIP)
56//	func <=> C function
57//	unsafe.Pointer, *T <=> void*
58//	[]T => void*
59//
60// There is a special case when the last argument of fptr is a variadic interface (or []interface}
61// it will be expanded into a call to the C function as if it had the arguments in that slice.
62// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
63// This is not the same as C variadic.
64//
65// There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
66// Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
67// has the same feature parity as Darwin.
68//
69// # Memory
70//
71// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
72// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
73// hold onto a reference to Go memory. This is the same as the [Cgo rules].
74//
75// However, there are some special cases. When passing a string as an argument if the string does not end in a null
76// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
77// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
78// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
79// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
80// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
81// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
82// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
83// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
84// 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
85// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
86// of the pointer
87//
88// # Example
89//
90// All functions below call this C function:
91//
92//	char *foo(char *str);
93//
94//	// Let purego convert types
95//	var foo func(s string) string
96//	goString := foo("copied")
97//	// Go will garbage collect this string
98//
99//	// Manually, handle allocations
100//	var foo2 func(b string) *byte
101//	mustFree := foo2("not copied\x00")
102//	defer free(mustFree)
103//
104// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
105func RegisterFunc(fptr interface{}, cfn uintptr) {
106	fn := reflect.ValueOf(fptr).Elem()
107	ty := fn.Type()
108	if ty.Kind() != reflect.Func {
109		panic("purego: fptr must be a function pointer")
110	}
111	if ty.NumOut() > 1 {
112		panic("purego: function can only return zero or one values")
113	}
114	if cfn == 0 {
115		panic("purego: cfn is nil")
116	}
117	{
118		// this code checks how many registers and stack this function will use
119		// to avoid crashing with too many arguments
120		var ints int
121		var floats int
122		var stack int
123		for i := 0; i < ty.NumIn(); i++ {
124			arg := ty.In(i)
125			switch arg.Kind() {
126			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
127				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
128				reflect.Func, reflect.Bool:
129				if ints < numOfIntegerRegisters() {
130					ints++
131				} else {
132					stack++
133				}
134			case reflect.Float32, reflect.Float64:
135				if floats < numOfFloats {
136					floats++
137				} else {
138					stack++
139				}
140			default:
141				panic("purego: unsupported kind " + arg.Kind().String())
142			}
143		}
144		sizeOfStack := maxArgs - numOfIntegerRegisters()
145		if stack > sizeOfStack {
146			panic("purego: too many arguments")
147		}
148	}
149	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
150		if len(args) > 0 {
151			if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
152				// subtract one from args bc the last argument in args is []interface{}
153				// which we are currently expanding
154				tmp := make([]reflect.Value, len(args)-1+len(variadic))
155				n := copy(tmp, args[:len(args)-1])
156				for i, v := range variadic {
157					tmp[n+i] = reflect.ValueOf(v)
158				}
159				args = tmp
160			}
161		}
162		var sysargs [maxArgs]uintptr
163		stack := sysargs[numOfIntegerRegisters():]
164		var floats [numOfFloats]uintptr
165		var numInts int
166		var numFloats int
167		var numStack int
168		var addStack, addInt, addFloat func(x uintptr)
169		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
170			// Windows arm64 uses the same calling convention as macOS and Linux
171			addStack = func(x uintptr) {
172				stack[numStack] = x
173				numStack++
174			}
175			addInt = func(x uintptr) {
176				if numInts >= numOfIntegerRegisters() {
177					addStack(x)
178				} else {
179					sysargs[numInts] = x
180					numInts++
181				}
182			}
183			addFloat = func(x uintptr) {
184				if numFloats < len(floats) {
185					floats[numFloats] = x
186					numFloats++
187				} else {
188					addStack(x)
189				}
190			}
191		} else {
192			// On Windows amd64 the arguments are passed in the numbered registered.
193			// So the first int is in the first integer register and the first float
194			// is in the second floating register if there is already a first int.
195			// This is in contrast to how macOS and Linux pass arguments which
196			// tries to use as many registers as possible in the calling convention.
197			addStack = func(x uintptr) {
198				sysargs[numStack] = x
199				numStack++
200			}
201			addInt = addStack
202			addFloat = addStack
203		}
204
205		var keepAlive []interface{}
206		defer func() {
207			runtime.KeepAlive(keepAlive)
208			runtime.KeepAlive(args)
209		}()
210		for _, v := range args {
211			switch v.Kind() {
212			case reflect.String:
213				ptr := strings.CString(v.String())
214				keepAlive = append(keepAlive, ptr)
215				addInt(uintptr(unsafe.Pointer(ptr)))
216			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
217				addInt(uintptr(v.Uint()))
218			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
219				addInt(uintptr(v.Int()))
220			case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
221				// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
222				addInt(v.Pointer())
223			case reflect.Func:
224				addInt(NewCallback(v.Interface()))
225			case reflect.Bool:
226				if v.Bool() {
227					addInt(1)
228				} else {
229					addInt(0)
230				}
231			case reflect.Float32:
232				addFloat(uintptr(math.Float32bits(float32(v.Float()))))
233			case reflect.Float64:
234				addFloat(uintptr(math.Float64bits(v.Float())))
235			default:
236				panic("purego: unsupported kind: " + v.Kind().String())
237			}
238		}
239		// TODO: support structs
240		var r1, r2 uintptr
241		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
242			// Use the normal arm64 calling convention even on Windows
243			syscall := syscall9Args{
244				cfn,
245				sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
246				floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
247				0, 0, 0,
248			}
249			runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
250			r1, r2 = syscall.r1, syscall.r2
251		} else {
252			// This is a fallback for amd64, 386, and arm. Note this may not support floats
253			r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8])
254		}
255		if ty.NumOut() == 0 {
256			return nil
257		}
258		outType := ty.Out(0)
259		v := reflect.New(outType).Elem()
260		switch outType.Kind() {
261		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
262			v.SetUint(uint64(r1))
263		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
264			v.SetInt(int64(r1))
265		case reflect.Bool:
266			v.SetBool(r1 != 0)
267		case reflect.UnsafePointer:
268			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
269			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
270		case reflect.Ptr:
271			// It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
272			v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
273		case reflect.Func:
274			// wrap this C function in a nicely typed Go function
275			v = reflect.New(outType)
276			RegisterFunc(v.Interface(), r1)
277		case reflect.String:
278			v.SetString(strings.GoString(r1))
279		case reflect.Float32, reflect.Float64:
280			// NOTE: r2 is only the floating return value on 64bit platforms.
281			// On 32bit platforms r2 is the upper part of a 64bit return.
282			v.SetFloat(math.Float64frombits(uint64(r2)))
283		default:
284			panic("purego: unsupported return kind: " + outType.Kind().String())
285		}
286		return []reflect.Value{v}
287	})
288	fn.Set(v)
289}
290
291func numOfIntegerRegisters() int {
292	switch runtime.GOARCH {
293	case "arm64":
294		return 8
295	case "amd64":
296		return 6
297	// TODO: figure out why 386 tests are not working
298	/*case "386":
299		return 0
300	case "arm":
301		return 4*/
302	default:
303		panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
304	}
305}

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