v0.4.0-alpha.2 e145907

func.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build darwin || linux
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 Dlsym fails.
19//
20// Windows does not support this function.
21func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
22	sym, err := Dlsym(handle, name)
23	if err != nil {
24		panic(err)
25	}
26	RegisterFunc(fptr, sym)
27}
28
29// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
30// fptr will be set to a function that when called will call the C function given by cfn with the
31// parameters passed in the correct registers and stack.
32//
33// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
34//
35// These conversions describe how a Go type in the fptr will be used to call
36// the C function. It is important to note that there is no way to verify that fptr
37// matches the C function. This also holds true for struct types where the padding
38// needs to be ensured to match that of C; RegisterFunc does not verify this.
39//
40// Type Conversions (Go => C)
41//
42//	string <=> char*
43//	bool <=> _Bool
44//	uintptr <=> uintptr_t
45//	uint <=> uint32_t or uint64_t
46//	uint8 <=> uint8_t
47//	uint16 <=> uint16_t
48//	uint32 <=> uint32_t
49//	uint64 <=> uint64_t
50//	int <=> int32_t or int64_t
51//	int8 <=> int8_t
52//	int16 <=> int16_t
53//	int32 <=> int32_t
54//	int64 <=> int64_t
55//	float32 <=> float (WIP)
56//	float64 <=> double (WIP)
57//	struct <=> struct (WIP)
58//	func <=> C function
59//	unsafe.Pointer, *T <=> void*
60//	[]T => void*
61//
62// There is a special case when the last argument of fptr is a variadic interface (or []interface}
63// it will be expanded into a call to the C function as if it had the arguments in that slice.
64// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
65// This is not the same as C variadic.
66//
67// There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
68// Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
69// has the same feature parity as Darwin.
70//
71// Windows does not support this function.
72func RegisterFunc(fptr interface{}, cfn uintptr) {
73	fn := reflect.ValueOf(fptr).Elem()
74	ty := fn.Type()
75	if ty.Kind() != reflect.Func {
76		panic("purego: fptr must be a function pointer")
77	}
78	if ty.NumOut() > 1 {
79		panic("purego: function can only return zero or one values")
80	}
81	if cfn == 0 {
82		panic("purego: cfn is nil")
83	}
84	{
85		// this code checks how many registers and stack this function will use
86		// to avoid crashing with too many arguments
87		var ints int
88		var floats int
89		var stack int
90		for i := 0; i < ty.NumIn(); i++ {
91			arg := ty.In(i)
92			switch arg.Kind() {
93			case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
94				reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
95				reflect.Func, reflect.Bool:
96				if ints < numOfIntegerRegisters() {
97					ints++
98				} else {
99					stack++
100				}
101			case reflect.Float32, reflect.Float64:
102				if floats < numOfFloats {
103					floats++
104				} else {
105					stack++
106				}
107			default:
108				panic("purego: unsupported kind " + arg.Kind().String())
109			}
110		}
111		sizeOfStack := maxArgs - numOfIntegerRegisters()
112		if stack > sizeOfStack {
113			panic("purego: too many arguments")
114		}
115	}
116	v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
117		if len(args) > 0 {
118			if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
119				// subtract one from args bc the last argument in args is []interface{}
120				// which we are currently expanding
121				tmp := make([]reflect.Value, len(args)-1+len(variadic))
122				n := copy(tmp, args[:len(args)-1])
123				for i, v := range variadic {
124					tmp[n+i] = reflect.ValueOf(v)
125				}
126				args = tmp
127			}
128		}
129		var sysargs [maxArgs]uintptr
130		var stack = sysargs[numOfIntegerRegisters():]
131		var floats [numOfFloats]uintptr
132		var numInts int
133		var numFloats int
134		var numStack int
135		addStack := func(x uintptr) {
136			stack[numStack] = x
137			numStack++
138		}
139		addInt := func(x uintptr) {
140			if numInts >= numOfIntegerRegisters() {
141				addStack(x)
142			} else {
143				sysargs[numInts] = x
144				numInts++
145			}
146		}
147		var keepAlive []interface{}
148		defer func() {
149			runtime.KeepAlive(keepAlive)
150		}()
151		for _, v := range args {
152			switch v.Kind() {
153			case reflect.String:
154				ptr := strings.CString(v.String())
155				keepAlive = append(keepAlive, ptr)
156				addInt(uintptr(unsafe.Pointer(ptr)))
157			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
158				addInt(uintptr(v.Uint()))
159			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
160				addInt(uintptr(v.Int()))
161			case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
162				keepAlive = append(keepAlive, v.Pointer())
163				addInt(v.Pointer())
164			case reflect.Func:
165				addInt(NewCallback(v.Interface()))
166			case reflect.Bool:
167				if v.Bool() {
168					addInt(1)
169				} else {
170					addInt(0)
171				}
172			case reflect.Float32:
173				if numFloats < len(floats) {
174					floats[numFloats] = uintptr(math.Float32bits(float32(v.Float())))
175					numFloats++
176				} else {
177					addStack(uintptr(math.Float32bits(float32(v.Float()))))
178				}
179			case reflect.Float64:
180				if numFloats < len(floats) {
181					floats[numFloats] = uintptr(math.Float64bits(v.Float()))
182					numFloats++
183				} else {
184					addStack(uintptr(math.Float64bits(v.Float())))
185				}
186			default:
187				panic("purego: unsupported kind: " + v.Kind().String())
188			}
189		}
190		// TODO: support structs
191		syscall := syscall9Args{
192			cfn,
193			sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
194			floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
195			0, 0, 0}
196		runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
197		r1, r2 := syscall.r1, syscall.r2
198
199		if ty.NumOut() == 0 {
200			return nil
201		}
202		outType := ty.Out(0)
203		v := reflect.New(outType).Elem()
204		switch outType.Kind() {
205		case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
206			v.SetUint(uint64(r1))
207		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
208			v.SetInt(int64(r1))
209		case reflect.Bool:
210			v.SetBool(r1 != 0)
211		case reflect.UnsafePointer:
212			// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
213			v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
214		case reflect.Ptr:
215			v = reflect.NewAt(outType, unsafe.Pointer(&r1)).Elem()
216		case reflect.Func:
217			// wrap this C function in a nicely typed Go function
218			v = reflect.New(outType)
219			RegisterFunc(v.Interface(), r1)
220		case reflect.String:
221			v.SetString(strings.GoString(r1))
222		case reflect.Float32, reflect.Float64:
223			// NOTE: r2 is only the floating return value on 64bit platforms.
224			// On 32bit platforms r2 is the upper part of a 64bit return.
225			v.SetFloat(math.Float64frombits(uint64(r2)))
226		default:
227			panic("purego: unsupported return kind: " + outType.Kind().String())
228		}
229		return []reflect.Value{v}
230	})
231	fn.Set(v)
232}
233
234func numOfIntegerRegisters() int {
235	switch runtime.GOARCH {
236	case "arm64":
237		return 8
238	case "amd64":
239		return 6
240	default:
241		panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
242	}
243}

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