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}