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  }