v0.6.0-alpha.2 192c5e8

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// # 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// # Example
85//
86// All functions below call this C function:
87//
88//	char *foo(char *str);
89//
90//	// Let purego convert types
91//	var foo func(s string) string
92//	goString := foo("copied")
93//	// Go will garbage collect this string
94//
95//	// Manually, handle allocations
96//	var foo2 func(b string) *byte
97//	mustFree := foo2("not copied\x00")
98//	defer free(mustFree)
99//
100// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
101func RegisterFunc(fptr interface{}, cfn uintptr) {
102	fn := reflect.ValueOf(fptr).Elem()
103	ty := fn.Type()
104	if ty.Kind() != reflect.Func {
105		panic("purego: fptr must be a function pointer")
106	}
107	if ty.NumOut() > 1 {
108		panic("purego: function can only return zero or one values")
109	}
110	if cfn == 0 {
111		panic("purego: cfn is nil")
112	}
113	{
114		// this code checks how many registers and stack this function will use
115		// to avoid crashing with too many arguments
116		var ints int
117		var floats int
118		var stack int
119		for i := 0; i < ty.NumIn(); i++ {
120			arg := ty.In(i)
121			switch arg.Kind() {
122			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
123				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
124				reflect.Func, reflect.Bool:
125				if ints < numOfIntegerRegisters() {
126					ints++
127				} else {
128					stack++
129				}
130			case reflect.Float32, reflect.Float64:
131				if floats < numOfFloats {
132					floats++
133				} else {
134					stack++
135				}
136			default:
137				panic("purego: unsupported kind " + arg.Kind().String())
138			}
139		}
140		sizeOfStack := maxArgs - numOfIntegerRegisters()
141		if stack > sizeOfStack {
142			panic("purego: too many arguments")
143		}
144	}
145	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
146		if len(args) > 0 {
147			if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
148				// subtract one from args bc the last argument in args is []interface{}
149				// which we are currently expanding
150				tmp := make([]reflect.Value, len(args)-1+len(variadic))
151				n := copy(tmp, args[:len(args)-1])
152				for i, v := range variadic {
153					tmp[n+i] = reflect.ValueOf(v)
154				}
155				args = tmp
156			}
157		}
158		var sysargs [maxArgs]uintptr
159		stack := sysargs[numOfIntegerRegisters():]
160		var floats [numOfFloats]uintptr
161		var numInts int
162		var numFloats int
163		var numStack int
164		var addStack, addInt, addFloat func(x uintptr)
165		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
166			// Windows arm64 uses the same calling convention as macOS and Linux
167			addStack = func(x uintptr) {
168				stack[numStack] = x
169				numStack++
170			}
171			addInt = func(x uintptr) {
172				if numInts >= numOfIntegerRegisters() {
173					addStack(x)
174				} else {
175					sysargs[numInts] = x
176					numInts++
177				}
178			}
179			addFloat = func(x uintptr) {
180				if numFloats < len(floats) {
181					floats[numFloats] = x
182					numFloats++
183				} else {
184					addStack(x)
185				}
186			}
187		} else {
188			// On Windows amd64 the arguments are passed in the numbered registered.
189			// So the first int is in the first integer register and the first float
190			// is in the second floating register if there is already a first int.
191			// This is in contrast to how macOS and Linux pass arguments which
192			// tries to use as many registers as possible in the calling convention.
193			addStack = func(x uintptr) {
194				sysargs[numStack] = x
195				numStack++
196			}
197			addInt = addStack
198			addFloat = addStack
199		}
200
201		var keepAlive []interface{}
202		defer func() {
203			runtime.KeepAlive(keepAlive)
204			runtime.KeepAlive(args)
205		}()
206		for _, v := range args {
207			switch v.Kind() {
208			case reflect.String:
209				ptr := strings.CString(v.String())
210				keepAlive = append(keepAlive, ptr)
211				addInt(uintptr(unsafe.Pointer(ptr)))
212			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
213				addInt(uintptr(v.Uint()))
214			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
215				addInt(uintptr(v.Int()))
216			case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
217				// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
218				addInt(v.Pointer())
219			case reflect.Func:
220				addInt(NewCallback(v.Interface()))
221			case reflect.Bool:
222				if v.Bool() {
223					addInt(1)
224				} else {
225					addInt(0)
226				}
227			case reflect.Float32:
228				addFloat(uintptr(math.Float32bits(float32(v.Float()))))
229			case reflect.Float64:
230				addFloat(uintptr(math.Float64bits(v.Float())))
231			default:
232				panic("purego: unsupported kind: " + v.Kind().String())
233			}
234		}
235		// TODO: support structs
236		var r1, r2 uintptr
237		if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
238			// Use the normal arm64 calling convention even on Windows
239			syscall := syscall9Args{
240				cfn,
241				sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
242				floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
243				0, 0, 0,
244			}
245			runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
246			r1, r2 = syscall.r1, syscall.r2
247		} else {
248			// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
249			r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8])
250		}
251		if ty.NumOut() == 0 {
252			return nil
253		}
254		outType := ty.Out(0)
255		v := reflect.New(outType).Elem()
256		switch outType.Kind() {
257		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
258			v.SetUint(uint64(r1))
259		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
260			v.SetInt(int64(r1))
261		case reflect.Bool:
262			v.SetBool(byte(r1) != 0)
263		case reflect.UnsafePointer:
264			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
265			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
266		case reflect.Ptr:
267			// It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
268			v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
269		case reflect.Func:
270			// wrap this C function in a nicely typed Go function
271			v = reflect.New(outType)
272			RegisterFunc(v.Interface(), r1)
273		case reflect.String:
274			v.SetString(strings.GoString(r1))
275		case reflect.Float32:
276			// NOTE: r2 is only the floating return value on 64bit platforms.
277			// On 32bit platforms r2 is the upper part of a 64bit return.
278			v.SetFloat(float64(math.Float32frombits(uint32(r2))))
279		case 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