v0.4.0 2fcc65d

func.go RawHistoryFolder

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

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