| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
- //go:build darwin || freebsd || linux || windows
- package purego
- import (
- "fmt"
- "math"
- "reflect"
- "runtime"
- "unsafe"
- "github.com/ebitengine/purego/internal/strings"
- )
- // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
- // It panics if it can't find the name symbol.
- func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
- sym, err := loadSymbol(handle, name)
- if err != nil {
- panic(err)
- }
- RegisterFunc(fptr, sym)
- }
- // RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
- // fptr will be set to a function that when called will call the C function given by cfn with the
- // parameters passed in the correct registers and stack.
- //
- // A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
- //
- // These conversions describe how a Go type in the fptr will be used to call
- // the C function. It is important to note that there is no way to verify that fptr
- // matches the C function. This also holds true for struct types where the padding
- // needs to be ensured to match that of C; RegisterFunc does not verify this.
- //
- // # Type Conversions (Go <=> C)
- //
- // string <=> char*
- // bool <=> _Bool
- // uintptr <=> uintptr_t
- // uint <=> uint32_t or uint64_t
- // uint8 <=> uint8_t
- // uint16 <=> uint16_t
- // uint32 <=> uint32_t
- // uint64 <=> uint64_t
- // int <=> int32_t or int64_t
- // int8 <=> int8_t
- // int16 <=> int16_t
- // int32 <=> int32_t
- // int64 <=> int64_t
- // float32 <=> float
- // float64 <=> double
- // struct <=> struct (WIP - darwin only)
- // func <=> C function
- // unsafe.Pointer, *T <=> void*
- // []T => void*
- //
- // There is a special case when the last argument of fptr is a variadic interface (or []interface}
- // it will be expanded into a call to the C function as if it had the arguments in that slice.
- // This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
- // This is not the same as C variadic.
- //
- // # Memory
- //
- // In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
- // calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
- // hold onto a reference to Go memory. This is the same as the [Cgo rules].
- //
- // However, there are some special cases. When passing a string as an argument if the string does not end in a null
- // terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
- // that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
- // undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
- // It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
- // This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
- // returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
- // and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
- // This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
- // 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
- // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
- // of the pointer
- //
- // # Structs
- //
- // Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
- // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
- // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
- //
- // # Example
- //
- // All functions below call this C function:
- //
- // char *foo(char *str);
- //
- // // Let purego convert types
- // var foo func(s string) string
- // goString := foo("copied")
- // // Go will garbage collect this string
- //
- // // Manually, handle allocations
- // var foo2 func(b string) *byte
- // mustFree := foo2("not copied\x00")
- // defer free(mustFree)
- //
- // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
- func RegisterFunc(fptr interface{}, cfn uintptr) {
- fn := reflect.ValueOf(fptr).Elem()
- ty := fn.Type()
- if ty.Kind() != reflect.Func {
- panic("purego: fptr must be a function pointer")
- }
- if ty.NumOut() > 1 {
- panic("purego: function can only return zero or one values")
- }
- if cfn == 0 {
- panic("purego: cfn is nil")
- }
- if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
- runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" {
- panic("purego: float returns are not supported")
- }
- {
- // this code checks how many registers and stack this function will use
- // to avoid crashing with too many arguments
- var ints int
- var floats int
- var stack int
- for i := 0; i < ty.NumIn(); i++ {
- arg := ty.In(i)
- switch arg.Kind() {
- case reflect.Func:
- // This only does preliminary testing to ensure the CDecl argument
- // is the first argument. Full testing is done when the callback is actually
- // created in NewCallback.
- for j := 0; j < arg.NumIn(); j++ {
- in := arg.In(j)
- if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
- continue
- }
- if j != 0 {
- panic("purego: CDecl must be the first argument")
- }
- }
- case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
- reflect.Slice, reflect.Bool:
- if ints < numOfIntegerRegisters() {
- ints++
- } else {
- stack++
- }
- case reflect.Float32, reflect.Float64:
- const is32bit = unsafe.Sizeof(uintptr(0)) == 4
- if is32bit {
- panic("purego: floats only supported on 64bit platforms")
- }
- if floats < numOfFloats {
- floats++
- } else {
- stack++
- }
- case reflect.Struct:
- if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
- panic("purego: struct arguments are only supported on darwin amd64 & arm64")
- }
- if arg.Size() == 0 {
- continue
- }
- addInt := func(u uintptr) {
- ints++
- }
- addFloat := func(u uintptr) {
- floats++
- }
- addStack := func(u uintptr) {
- stack++
- }
- _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
- default:
- panic("purego: unsupported kind " + arg.Kind().String())
- }
- }
- if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
- if runtime.GOOS != "darwin" {
- panic("purego: struct return values only supported on darwin arm64 & amd64")
- }
- outType := ty.Out(0)
- checkStructFieldsSupported(outType)
- if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
- // on amd64 if struct is bigger than 16 bytes allocate the return struct
- // and pass it in as a hidden first argument.
- ints++
- }
- }
- sizeOfStack := maxArgs - numOfIntegerRegisters()
- if stack > sizeOfStack {
- panic("purego: too many arguments")
- }
- }
- v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
- if len(args) > 0 {
- if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
- // subtract one from args bc the last argument in args is []interface{}
- // which we are currently expanding
- tmp := make([]reflect.Value, len(args)-1+len(variadic))
- n := copy(tmp, args[:len(args)-1])
- for i, v := range variadic {
- tmp[n+i] = reflect.ValueOf(v)
- }
- args = tmp
- }
- }
- var sysargs [maxArgs]uintptr
- stack := sysargs[numOfIntegerRegisters():]
- var floats [numOfFloats]uintptr
- var numInts int
- var numFloats int
- var numStack int
- var addStack, addInt, addFloat func(x uintptr)
- if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
- // Windows arm64 uses the same calling convention as macOS and Linux
- addStack = func(x uintptr) {
- stack[numStack] = x
- numStack++
- }
- addInt = func(x uintptr) {
- if numInts >= numOfIntegerRegisters() {
- addStack(x)
- } else {
- sysargs[numInts] = x
- numInts++
- }
- }
- addFloat = func(x uintptr) {
- if numFloats < len(floats) {
- floats[numFloats] = x
- numFloats++
- } else {
- addStack(x)
- }
- }
- } else {
- // On Windows amd64 the arguments are passed in the numbered registered.
- // So the first int is in the first integer register and the first float
- // is in the second floating register if there is already a first int.
- // This is in contrast to how macOS and Linux pass arguments which
- // tries to use as many registers as possible in the calling convention.
- addStack = func(x uintptr) {
- sysargs[numStack] = x
- numStack++
- }
- addInt = addStack
- addFloat = addStack
- }
- var keepAlive []interface{}
- defer func() {
- runtime.KeepAlive(keepAlive)
- runtime.KeepAlive(args)
- }()
- var syscall syscall15Args
- if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
- outType := ty.Out(0)
- if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
- val := reflect.New(outType)
- keepAlive = append(keepAlive, val)
- addInt(val.Pointer())
- } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
- isAllFloats, numFields := isAllSameFloat(outType)
- if !isAllFloats || numFields > 4 {
- val := reflect.New(outType)
- keepAlive = append(keepAlive, val)
- syscall.arm64_r8 = val.Pointer()
- }
- }
- }
- for _, v := range args {
- switch v.Kind() {
- case reflect.String:
- ptr := strings.CString(v.String())
- keepAlive = append(keepAlive, ptr)
- addInt(uintptr(unsafe.Pointer(ptr)))
- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- addInt(uintptr(v.Uint()))
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- addInt(uintptr(v.Int()))
- case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
- // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
- addInt(v.Pointer())
- case reflect.Func:
- addInt(NewCallback(v.Interface()))
- case reflect.Bool:
- if v.Bool() {
- addInt(1)
- } else {
- addInt(0)
- }
- case reflect.Float32:
- addFloat(uintptr(math.Float32bits(float32(v.Float()))))
- case reflect.Float64:
- addFloat(uintptr(math.Float64bits(v.Float())))
- case reflect.Struct:
- keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
- default:
- panic("purego: unsupported kind: " + v.Kind().String())
- }
- }
- if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
- // Use the normal arm64 calling convention even on Windows
- syscall = syscall15Args{
- cfn,
- sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
- sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
- sysargs[12], sysargs[13], sysargs[14],
- floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
- syscall.arm64_r8,
- }
- runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
- } else {
- // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
- syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
- sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
- sysargs[12], sysargs[13], sysargs[14])
- syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
- }
- if ty.NumOut() == 0 {
- return nil
- }
- outType := ty.Out(0)
- v := reflect.New(outType).Elem()
- switch outType.Kind() {
- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- v.SetUint(uint64(syscall.a1))
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- v.SetInt(int64(syscall.a1))
- case reflect.Bool:
- v.SetBool(byte(syscall.a1) != 0)
- case reflect.UnsafePointer:
- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
- case reflect.Ptr:
- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
- case reflect.Func:
- // wrap this C function in a nicely typed Go function
- v = reflect.New(outType)
- RegisterFunc(v.Interface(), syscall.a1)
- case reflect.String:
- v.SetString(strings.GoString(syscall.a1))
- case reflect.Float32:
- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
- case reflect.Float64:
- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
- case reflect.Struct:
- v = getStruct(outType, syscall)
- default:
- panic("purego: unsupported return kind: " + outType.Kind().String())
- }
- return []reflect.Value{v}
- })
- fn.Set(v)
- }
- // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
- // if it is bigger than this than enough space must be allocated on the heap and then passed into
- // the function as the first parameter on amd64 or in R8 on arm64.
- //
- // If you change this make sure to update it in objc_runtime_darwin.go
- const maxRegAllocStructSize = 16
- func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
- allFloats = true
- root := ty.Field(0).Type
- for root.Kind() == reflect.Struct {
- root = root.Field(0).Type
- }
- first := root.Kind()
- if first != reflect.Float32 && first != reflect.Float64 {
- allFloats = false
- }
- for i := 0; i < ty.NumField(); i++ {
- f := ty.Field(i).Type
- if f.Kind() == reflect.Struct {
- var structNumFields int
- allFloats, structNumFields = isAllSameFloat(f)
- numFields += structNumFields
- continue
- }
- numFields++
- if f.Kind() != first {
- allFloats = false
- }
- }
- return allFloats, numFields
- }
- func checkStructFieldsSupported(ty reflect.Type) {
- for i := 0; i < ty.NumField(); i++ {
- f := ty.Field(i).Type
- if f.Kind() == reflect.Array {
- f = f.Elem()
- } else if f.Kind() == reflect.Struct {
- checkStructFieldsSupported(f)
- continue
- }
- switch f.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
- reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
- default:
- panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
- }
- }
- }
- func roundUpTo8(val uintptr) uintptr {
- return (val + 7) &^ 7
- }
- func numOfIntegerRegisters() int {
- switch runtime.GOARCH {
- case "arm64":
- return 8
- case "amd64":
- return 6
- default:
- // since this platform isn't supported and can therefore only access
- // integer registers it is fine to return the maxArgs
- return maxArgs
- }
- }
|