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}