v0.6.1 36acdc2

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
54//	float64 <=> double
55//	struct <=> struct (WIP - darwin only)
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// # Memory
66//
67// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
68// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
69// hold onto a reference to Go memory. This is the same as the [Cgo rules].
70//
71// However, there are some special cases. When passing a string as an argument if the string does not end in a null
72// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
73// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
74// undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
75// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
76// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
77// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
78// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
79// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
80// 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
81// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
82// of the pointer
83//
84// # Structs
85//
86// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
87// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
88// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
89//
90// # Example
91//
92// All functions below call this C function:
93//
94//	char *foo(char *str);
95//
96//	// Let purego convert types
97//	var foo func(s string) string
98//	goString := foo("copied")
99//	// Go will garbage collect this string
100//
101//	// Manually, handle allocations
102//	var foo2 func(b string) *byte
103//	mustFree := foo2("not copied\x00")
104//	defer free(mustFree)
105//
106// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
107func RegisterFunc(fptr interface{}, cfn uintptr) {
108	fn := reflect.ValueOf(fptr).Elem()
109	ty := fn.Type()
110	if ty.Kind() != reflect.Func {
111		panic("purego: fptr must be a function pointer")
112	}
113	if ty.NumOut() > 1 {
114		panic("purego: function can only return zero or one values")
115	}
116	if cfn == 0 {
117		panic("purego: cfn is nil")
118	}
119	{
120		// this code checks how many registers and stack this function will use
121		// to avoid crashing with too many arguments
122		var ints int
123		var floats int
124		var stack int
125		for i := 0; i < ty.NumIn(); i++ {
126			arg := ty.In(i)
127			switch arg.Kind() {
128			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
129				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
130				reflect.Func, reflect.Bool:
131				if ints < numOfIntegerRegisters() {
132					ints++
133				} else {
134					stack++
135				}
136			case reflect.Float32, reflect.Float64:
137				if floats < numOfFloats {
138					floats++
139				} else {
140					stack++
141				}
142			case reflect.Struct:
143				if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
144					panic("purego: struct arguments are only supported on darwin amd64 & arm64")
145				}
146				if arg.Size() == 0 {
147					continue
148				}
149				addInt := func(u uintptr) {
150					ints++
151				}
152				addFloat := func(u uintptr) {
153					floats++
154				}
155				addStack := func(u uintptr) {
156					stack++
157				}
158				_ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
159			default:
160				panic("purego: unsupported kind " + arg.Kind().String())
161			}
162		}
163		sizeOfStack := maxArgs - numOfIntegerRegisters()
164		if stack > sizeOfStack {
165			panic("purego: too many arguments")
166		}
167	}
168	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
169		if len(args) > 0 {
170			if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
171				// subtract one from args bc the last argument in args is []interface{}
172				// which we are currently expanding
173				tmp := make([]reflect.Value, len(args)-1+len(variadic))
174				n := copy(tmp, args[:len(args)-1])
175				for i, v := range variadic {
176					tmp[n+i] = reflect.ValueOf(v)
177				}
178				args = tmp
179			}
180		}
181		var sysargs [maxArgs]uintptr
182		stack := sysargs[numOfIntegerRegisters():]
183		var floats [numOfFloats]uintptr
184		var numInts int
185		var numFloats int
186		var numStack int
187		var addStack, addInt, addFloat func(x uintptr)
188		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
189			// Windows arm64 uses the same calling convention as macOS and Linux
190			addStack = func(x uintptr) {
191				stack[numStack] = x
192				numStack++
193			}
194			addInt = func(x uintptr) {
195				if numInts >= numOfIntegerRegisters() {
196					addStack(x)
197				} else {
198					sysargs[numInts] = x
199					numInts++
200				}
201			}
202			addFloat = func(x uintptr) {
203				if numFloats < len(floats) {
204					floats[numFloats] = x
205					numFloats++
206				} else {
207					addStack(x)
208				}
209			}
210		} else {
211			// On Windows amd64 the arguments are passed in the numbered registered.
212			// So the first int is in the first integer register and the first float
213			// is in the second floating register if there is already a first int.
214			// This is in contrast to how macOS and Linux pass arguments which
215			// tries to use as many registers as possible in the calling convention.
216			addStack = func(x uintptr) {
217				sysargs[numStack] = x
218				numStack++
219			}
220			addInt = addStack
221			addFloat = addStack
222		}
223
224		var keepAlive []interface{}
225		defer func() {
226			runtime.KeepAlive(keepAlive)
227			runtime.KeepAlive(args)
228		}()
229		for _, v := range args {
230			switch v.Kind() {
231			case reflect.String:
232				ptr := strings.CString(v.String())
233				keepAlive = append(keepAlive, ptr)
234				addInt(uintptr(unsafe.Pointer(ptr)))
235			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
236				addInt(uintptr(v.Uint()))
237			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
238				addInt(uintptr(v.Int()))
239			case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
240				// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
241				addInt(v.Pointer())
242			case reflect.Func:
243				addInt(NewCallback(v.Interface()))
244			case reflect.Bool:
245				if v.Bool() {
246					addInt(1)
247				} else {
248					addInt(0)
249				}
250			case reflect.Float32:
251				addFloat(uintptr(math.Float32bits(float32(v.Float()))))
252			case reflect.Float64:
253				addFloat(uintptr(math.Float64bits(v.Float())))
254			case reflect.Struct:
255				keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
256			default:
257				panic("purego: unsupported kind: " + v.Kind().String())
258			}
259		}
260		// TODO: support structs
261		var r1, r2 uintptr
262		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
263			// Use the normal arm64 calling convention even on Windows
264			syscall := syscall15Args{
265				cfn,
266				sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
267				sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
268				sysargs[12], sysargs[13], sysargs[14],
269				floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
270				0, 0, 0,
271			}
272			runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
273			r1, r2 = syscall.r1, syscall.r2
274		} else {
275			// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
276			r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
277				sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
278				sysargs[12], sysargs[13], sysargs[14])
279		}
280		if ty.NumOut() == 0 {
281			return nil
282		}
283		outType := ty.Out(0)
284		v := reflect.New(outType).Elem()
285		switch outType.Kind() {
286		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
287			v.SetUint(uint64(r1))
288		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
289			v.SetInt(int64(r1))
290		case reflect.Bool:
291			v.SetBool(byte(r1) != 0)
292		case reflect.UnsafePointer:
293			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
294			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
295		case reflect.Ptr:
296			// It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
297			v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
298		case reflect.Func:
299			// wrap this C function in a nicely typed Go function
300			v = reflect.New(outType)
301			RegisterFunc(v.Interface(), r1)
302		case reflect.String:
303			v.SetString(strings.GoString(r1))
304		case reflect.Float32:
305			// NOTE: r2 is only the floating return value on 64bit platforms.
306			// On 32bit platforms r2 is the upper part of a 64bit return.
307			v.SetFloat(float64(math.Float32frombits(uint32(r2))))
308		case reflect.Float64:
309			// NOTE: r2 is only the floating return value on 64bit platforms.
310			// On 32bit platforms r2 is the upper part of a 64bit return.
311			v.SetFloat(math.Float64frombits(uint64(r2)))
312		default:
313			panic("purego: unsupported return kind: " + outType.Kind().String())
314		}
315		return []reflect.Value{v}
316	})
317	fn.Set(v)
318}
319
320func roundUpTo8(val uintptr) uintptr {
321	return (val + 7) &^ 7
322}
323
324func numOfIntegerRegisters() int {
325	switch runtime.GOARCH {
326	case "arm64":
327		return 8
328	case "amd64":
329		return 6
330	// TODO: figure out why 386 tests are not working
331	/*case "386":
332		return 0
333	case "arm":
334		return 4*/
335	default:
336		panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
337	}
338}

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