github.com/jwijenbergh/purego@v0.0.0-20240126093400-70ff3a61df13/func.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 4 //go:build darwin || freebsd || linux || windows 5 6 package purego 7 8 import ( 9 "math" 10 "reflect" 11 "runtime" 12 "unsafe" 13 14 "github.com/jwijenbergh/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. 19 func 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 101 func 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 return 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 if g, ok := v.Interface().([]string); ok { 218 res := strings.ByteSlice(g) 219 keepAlive = append(keepAlive, res) 220 addInt(uintptr(unsafe.Pointer(res))) 221 } else { 222 keepAlive = append(keepAlive, v.Pointer()) 223 addInt(v.Pointer()) 224 } 225 case reflect.Func: 226 addInt(NewCallback(v.Interface())) 227 case reflect.Bool: 228 if v.Bool() { 229 addInt(1) 230 } else { 231 addInt(0) 232 } 233 case reflect.Float32: 234 addFloat(uintptr(math.Float32bits(float32(v.Float())))) 235 case reflect.Float64: 236 addFloat(uintptr(math.Float64bits(v.Float()))) 237 default: 238 panic("purego: unsupported kind: " + v.Kind().String()) 239 } 240 } 241 // TODO: support structs 242 var r1, r2 uintptr 243 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { 244 // Use the normal arm64 calling convention even on Windows 245 syscall := syscall15Args{ 246 cfn, 247 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], 248 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], 249 sysargs[12], sysargs[13], sysargs[14], 250 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], 251 0, 0, 0, 252 } 253 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) 254 r1, r2 = syscall.r1, syscall.r2 255 } else { 256 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats 257 r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], 258 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], 259 sysargs[12], sysargs[13], sysargs[14]) 260 } 261 if ty.NumOut() == 0 { 262 return nil 263 } 264 outType := ty.Out(0) 265 v := reflect.New(outType).Elem() 266 switch outType.Kind() { 267 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 268 v.SetUint(uint64(r1)) 269 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 270 v.SetInt(int64(r1)) 271 case reflect.Bool: 272 v.SetBool(byte(r1) != 0) 273 case reflect.UnsafePointer: 274 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer 275 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1))) 276 case reflect.Ptr: 277 // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem() 278 v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem() 279 case reflect.Func: 280 // wrap this C function in a nicely typed Go function 281 v = reflect.New(outType) 282 RegisterFunc(v.Interface(), r1) 283 case reflect.String: 284 v.SetString(strings.GoString(r1)) 285 case reflect.Float32: 286 // NOTE: r2 is only the floating return value on 64bit platforms. 287 // On 32bit platforms r2 is the upper part of a 64bit return. 288 v.SetFloat(float64(math.Float32frombits(uint32(r2)))) 289 case reflect.Float64: 290 // NOTE: r2 is only the floating return value on 64bit platforms. 291 // On 32bit platforms r2 is the upper part of a 64bit return. 292 v.SetFloat(math.Float64frombits(uint64(r2))) 293 default: 294 panic("purego: unsupported return kind: " + outType.Kind().String()) 295 } 296 return []reflect.Value{v} 297 }) 298 fn.Set(v) 299 } 300 301 func numOfIntegerRegisters() int { 302 switch runtime.GOARCH { 303 case "arm64": 304 return 8 305 case "amd64": 306 return 6 307 // TODO: figure out why 386 tests are not working 308 /*case "386": 309 return 0 310 case "arm": 311 return 4*/ 312 default: 313 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")") 314 } 315 }