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  }