func.go raw

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