github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/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 "fmt" 10 "math" 11 "reflect" 12 "runtime" 13 "unsafe" 14 15 "github.com/ebitengine/purego/internal/strings" 16 ) 17 18 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). 19 // It panics if it can't find the name symbol. 20 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { 21 sym, err := loadSymbol(handle, name) 22 if err != nil { 23 panic(err) 24 } 25 RegisterFunc(fptr, sym) 26 } 27 28 // RegisterFunc takes a pointer to a Go function representing the calling convention of the C function. 29 // fptr will be set to a function that when called will call the C function given by cfn with the 30 // parameters passed in the correct registers and stack. 31 // 32 // A panic is produced if the type is not a function pointer or if the function returns more than 1 value. 33 // 34 // These conversions describe how a Go type in the fptr will be used to call 35 // the C function. It is important to note that there is no way to verify that fptr 36 // matches the C function. This also holds true for struct types where the padding 37 // needs to be ensured to match that of C; RegisterFunc does not verify this. 38 // 39 // # Type Conversions (Go <=> C) 40 // 41 // string <=> char* 42 // bool <=> _Bool 43 // uintptr <=> uintptr_t 44 // uint <=> uint32_t or uint64_t 45 // uint8 <=> uint8_t 46 // uint16 <=> uint16_t 47 // uint32 <=> uint32_t 48 // uint64 <=> uint64_t 49 // int <=> int32_t or int64_t 50 // int8 <=> int8_t 51 // int16 <=> int16_t 52 // int32 <=> int32_t 53 // int64 <=> int64_t 54 // float32 <=> float 55 // float64 <=> double 56 // struct <=> struct (WIP - darwin only) 57 // func <=> C function 58 // unsafe.Pointer, *T <=> void* 59 // []T => void* 60 // 61 // There is a special case when the last argument of fptr is a variadic interface (or []interface} 62 // it will be expanded into a call to the C function as if it had the arguments in that slice. 63 // This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. 64 // This is not the same as C variadic. 65 // 66 // # Memory 67 // 68 // In general it is not possible for purego to guarantee the lifetimes of objects returned or received from 69 // calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't 70 // hold onto a reference to Go memory. This is the same as the [Cgo rules]. 71 // 72 // However, there are some special cases. When passing a string as an argument if the string does not end in a null 73 // terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for 74 // that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some 75 // undefined time. However, if the string does already contain a null-terminated byte then no copy is done. 76 // It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory. 77 // This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function 78 // returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory 79 // and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced. 80 // This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue 81 // 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 82 // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime 83 // of the pointer 84 // 85 // # Structs 86 // 87 // Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However, 88 // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure 89 // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. 90 // 91 // # Example 92 // 93 // All functions below call this C function: 94 // 95 // char *foo(char *str); 96 // 97 // // Let purego convert types 98 // var foo func(s string) string 99 // goString := foo("copied") 100 // // Go will garbage collect this string 101 // 102 // // Manually, handle allocations 103 // var foo2 func(b string) *byte 104 // mustFree := foo2("not copied\x00") 105 // defer free(mustFree) 106 // 107 // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C 108 func RegisterFunc(fptr interface{}, cfn uintptr) { 109 fn := reflect.ValueOf(fptr).Elem() 110 ty := fn.Type() 111 if ty.Kind() != reflect.Func { 112 panic("purego: fptr must be a function pointer") 113 } 114 if ty.NumOut() > 1 { 115 panic("purego: function can only return zero or one values") 116 } 117 if cfn == 0 { 118 panic("purego: cfn is nil") 119 } 120 if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && 121 runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { 122 panic("purego: float returns are not supported") 123 } 124 { 125 // this code checks how many registers and stack this function will use 126 // to avoid crashing with too many arguments 127 var ints int 128 var floats int 129 var stack int 130 for i := 0; i < ty.NumIn(); i++ { 131 arg := ty.In(i) 132 switch arg.Kind() { 133 case reflect.Func: 134 // This only does preliminary testing to ensure the CDecl argument 135 // is the first argument. Full testing is done when the callback is actually 136 // created in NewCallback. 137 for j := 0; j < arg.NumIn(); j++ { 138 in := arg.In(j) 139 if !in.AssignableTo(reflect.TypeOf(CDecl{})) { 140 continue 141 } 142 if j != 0 { 143 panic("purego: CDecl must be the first argument") 144 } 145 } 146 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 147 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, 148 reflect.Slice, reflect.Bool: 149 if ints < numOfIntegerRegisters() { 150 ints++ 151 } else { 152 stack++ 153 } 154 case reflect.Float32, reflect.Float64: 155 if is32bit { 156 panic("purego: floats only supported on 64bit platforms") 157 } 158 if floats < numOfFloats { 159 floats++ 160 } else { 161 stack++ 162 } 163 case reflect.Struct: 164 if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { 165 panic("purego: struct arguments are only supported on darwin amd64 & arm64") 166 } 167 if arg.Size() == 0 { 168 continue 169 } 170 addInt := func(u uintptr) { 171 ints++ 172 } 173 addFloat := func(u uintptr) { 174 floats++ 175 } 176 addStack := func(u uintptr) { 177 stack++ 178 } 179 _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil) 180 default: 181 panic("purego: unsupported kind " + arg.Kind().String()) 182 } 183 } 184 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { 185 if runtime.GOOS != "darwin" { 186 panic("purego: struct return values only supported on darwin arm64 & amd64") 187 } 188 outType := ty.Out(0) 189 checkStructFieldsSupported(outType) 190 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { 191 // on amd64 if struct is bigger than 16 bytes allocate the return struct 192 // and pass it in as a hidden first argument. 193 ints++ 194 } 195 } 196 sizeOfStack := maxArgs - numOfIntegerRegisters() 197 if stack > sizeOfStack { 198 panic("purego: too many arguments") 199 } 200 } 201 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { 202 if len(args) > 0 { 203 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok { 204 // subtract one from args bc the last argument in args is []interface{} 205 // which we are currently expanding 206 tmp := make([]reflect.Value, len(args)-1+len(variadic)) 207 n := copy(tmp, args[:len(args)-1]) 208 for i, v := range variadic { 209 tmp[n+i] = reflect.ValueOf(v) 210 } 211 args = tmp 212 } 213 } 214 var sysargs [maxArgs]uintptr 215 stack := sysargs[numOfIntegerRegisters():] 216 var floats [numOfFloats]uintptr 217 var numInts int 218 var numFloats int 219 var numStack int 220 var addStack, addInt, addFloat func(x uintptr) 221 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { 222 // Windows arm64 uses the same calling convention as macOS and Linux 223 addStack = func(x uintptr) { 224 stack[numStack] = x 225 numStack++ 226 } 227 addInt = func(x uintptr) { 228 if numInts >= numOfIntegerRegisters() { 229 addStack(x) 230 } else { 231 sysargs[numInts] = x 232 numInts++ 233 } 234 } 235 addFloat = func(x uintptr) { 236 if numFloats < len(floats) { 237 floats[numFloats] = x 238 numFloats++ 239 } else { 240 addStack(x) 241 } 242 } 243 } else { 244 // On Windows amd64 the arguments are passed in the numbered registered. 245 // So the first int is in the first integer register and the first float 246 // is in the second floating register if there is already a first int. 247 // This is in contrast to how macOS and Linux pass arguments which 248 // tries to use as many registers as possible in the calling convention. 249 addStack = func(x uintptr) { 250 sysargs[numStack] = x 251 numStack++ 252 } 253 addInt = addStack 254 addFloat = addStack 255 } 256 257 var keepAlive []interface{} 258 defer func() { 259 runtime.KeepAlive(keepAlive) 260 runtime.KeepAlive(args) 261 }() 262 var syscall syscall15Args 263 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { 264 outType := ty.Out(0) 265 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { 266 val := reflect.New(outType) 267 keepAlive = append(keepAlive, val) 268 addInt(val.Pointer()) 269 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { 270 isAllFloats, numFields := isAllSameFloat(outType) 271 if !isAllFloats || numFields > 4 { 272 val := reflect.New(outType) 273 keepAlive = append(keepAlive, val) 274 syscall.arm64_r8 = val.Pointer() 275 } 276 } 277 } 278 for _, v := range args { 279 switch v.Kind() { 280 case reflect.String: 281 ptr := strings.CString(v.String()) 282 keepAlive = append(keepAlive, ptr) 283 addInt(uintptr(unsafe.Pointer(ptr))) 284 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 285 addInt(uintptr(v.Uint())) 286 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 287 addInt(uintptr(v.Int())) 288 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: 289 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable 290 addInt(v.Pointer()) 291 case reflect.Func: 292 addInt(NewCallback(v.Interface())) 293 case reflect.Bool: 294 if v.Bool() { 295 addInt(1) 296 } else { 297 addInt(0) 298 } 299 case reflect.Float32: 300 addFloat(uintptr(math.Float32bits(float32(v.Float())))) 301 case reflect.Float64: 302 addFloat(uintptr(math.Float64bits(v.Float()))) 303 case reflect.Struct: 304 keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive) 305 default: 306 panic("purego: unsupported kind: " + v.Kind().String()) 307 } 308 } 309 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { 310 // Use the normal arm64 calling convention even on Windows 311 syscall = syscall15Args{ 312 cfn, 313 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], 314 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], 315 sysargs[12], sysargs[13], sysargs[14], 316 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], 317 syscall.arm64_r8, 318 } 319 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) 320 } else { 321 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats 322 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], 323 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], 324 sysargs[12], sysargs[13], sysargs[14]) 325 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support 326 } 327 if ty.NumOut() == 0 { 328 return nil 329 } 330 outType := ty.Out(0) 331 v := reflect.New(outType).Elem() 332 switch outType.Kind() { 333 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 334 v.SetUint(uint64(syscall.a1)) 335 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 336 v.SetInt(int64(syscall.a1)) 337 case reflect.Bool: 338 v.SetBool(byte(syscall.a1) != 0) 339 case reflect.UnsafePointer: 340 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer 341 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) 342 case reflect.Ptr: 343 // It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem() 344 v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem() 345 case reflect.Func: 346 // wrap this C function in a nicely typed Go function 347 v = reflect.New(outType) 348 RegisterFunc(v.Interface(), syscall.a1) 349 case reflect.String: 350 v.SetString(strings.GoString(syscall.a1)) 351 case reflect.Float32: 352 // NOTE: syscall.r2 is only the floating return value on 64bit platforms. 353 // On 32bit platforms syscall.r2 is the upper part of a 64bit return. 354 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) 355 case reflect.Float64: 356 // NOTE: syscall.r2 is only the floating return value on 64bit platforms. 357 // On 32bit platforms syscall.r2 is the upper part of a 64bit return. 358 v.SetFloat(math.Float64frombits(uint64(syscall.f1))) 359 case reflect.Struct: 360 v = getStruct(outType, syscall) 361 default: 362 panic("purego: unsupported return kind: " + outType.Kind().String()) 363 } 364 return []reflect.Value{v} 365 }) 366 fn.Set(v) 367 } 368 369 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. 370 // if it is bigger than this than enough space must be allocated on the heap and then passed into 371 // the function as the first parameter on amd64 or in R8 on arm64. 372 // 373 // If you change this make sure to update it in objc_runtime_darwin.go 374 const maxRegAllocStructSize = 16 375 376 func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { 377 allFloats = true 378 root := ty.Field(0).Type 379 for root.Kind() == reflect.Struct { 380 root = root.Field(0).Type 381 } 382 first := root.Kind() 383 if first != reflect.Float32 && first != reflect.Float64 { 384 allFloats = false 385 } 386 for i := 0; i < ty.NumField(); i++ { 387 f := ty.Field(i).Type 388 if f.Kind() == reflect.Struct { 389 var structNumFields int 390 allFloats, structNumFields = isAllSameFloat(f) 391 numFields += structNumFields 392 continue 393 } 394 numFields++ 395 if f.Kind() != first { 396 allFloats = false 397 } 398 } 399 return allFloats, numFields 400 } 401 402 func checkStructFieldsSupported(ty reflect.Type) { 403 for i := 0; i < ty.NumField(); i++ { 404 f := ty.Field(i).Type 405 if f.Kind() == reflect.Array { 406 f = f.Elem() 407 } else if f.Kind() == reflect.Struct { 408 checkStructFieldsSupported(f) 409 continue 410 } 411 switch f.Kind() { 412 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 413 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 414 reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32: 415 default: 416 panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) 417 } 418 } 419 } 420 421 const is32bit = unsafe.Sizeof(uintptr(0)) == 4 422 423 func roundUpTo8(val uintptr) uintptr { 424 return (val + 7) &^ 7 425 } 426 427 func numOfIntegerRegisters() int { 428 switch runtime.GOARCH { 429 case "arm64": 430 return 8 431 case "amd64": 432 return 6 433 default: 434 // since this platform isn't supported and can therefore only access 435 // integer registers it is fine to return the maxArgs 436 return maxArgs 437 } 438 }