github.com/jwijenbergh/purego@v0.0.0-20240126093400-70ff3a61df13/syscall_sysv.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
     3  
     4  //go:build darwin || freebsd || (linux && (amd64 || arm64))
     5  
     6  package purego
     7  
     8  import (
     9  	"errors"
    10  	"reflect"
    11  	"runtime"
    12  	"sync"
    13  	"unsafe"
    14  
    15  	"github.com/jwijenbergh/purego/internal/strings"
    16  )
    17  
    18  var syscall15XABI0 uintptr
    19  
    20  type syscall15Args struct {
    21  	fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
    22  	f1, f2, f3, f4, f5, f6, f7, f8                                       uintptr
    23  	r1, r2, err                                                          uintptr
    24  }
    25  
    26  //go:nosplit
    27  func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
    28  	args := syscall15Args{
    29  		fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
    30  		a1, a2, a3, a4, a5, a6, a7, a8,
    31  		r1, r2, err,
    32  	}
    33  	runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
    34  	return args.r1, args.r2, args.err
    35  }
    36  
    37  // UnrefCallback unreferences the associated callback (created by NewCallback) by callback pointer.
    38  func UnrefCallback(cb uintptr) error {
    39  	cbs.lock.Lock()
    40  	defer cbs.lock.Unlock()
    41  	idx, ok := cbs.knownIdx[cb]
    42  	if !ok {
    43  		return errors.New(`callback not found`)
    44  	}
    45  	val := cbs.funcs[idx]
    46  	delete(cbs.knownFnPtr, val.Pointer())
    47  	delete(cbs.knownIdx, cb)
    48  	cbs.holes[idx] = struct{}{}
    49  	cbs.funcs[idx] = reflect.Value{}
    50  	return nil
    51  }
    52  
    53  // UnrefCallbackFnPtr unreferences the associated callback (created by NewCallbackFnPtr) by function pointer address
    54  func UnrefCallbackFnPtr(cb any) error {
    55  	val := reflect.ValueOf(cb)
    56  	if val.IsNil() {
    57  		panic("purego: function must not be nil")
    58  	}
    59  	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Func {
    60  		panic("purego: the type must be a function pointer but was not")
    61  	}
    62  
    63  	addr, ok := getCallbackByFnPtr(val)
    64  	if !ok {
    65  		return errors.New(`callback not found`)
    66  	}
    67  
    68  	cbs.lock.Lock()
    69  	defer cbs.lock.Unlock()
    70  	idx := cbs.knownIdx[addr]
    71  	delete(cbs.knownFnPtr, val.Pointer())
    72  	delete(cbs.knownIdx, addr)
    73  	cbs.holes[idx] = struct{}{}
    74  	cbs.funcs[idx] = reflect.Value{}
    75  	return nil
    76  }
    77  
    78  // NewCallback converts a Go function to a function pointer conforming to the C calling convention.
    79  // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
    80  // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
    81  // of uintptr. Only a limited number of callbacks may be live in a single Go process, and any memory allocated
    82  // for these callbacks is not released until CallbackUnref is called. At most 2000 callbacks can always be live.
    83  // Although this function provides similar functionality to windows.NewCallback it is distinct.
    84  func NewCallback(fn interface{}) uintptr {
    85  	val := reflect.ValueOf(fn)
    86  	if val.Kind() != reflect.Func {
    87  		panic("purego: the type must be a function but was not")
    88  	}
    89  	if val.IsNil() {
    90  		panic("purego: function must not be nil")
    91  	}
    92  	return compileCallback(val)
    93  }
    94  
    95  // NewCallbackFnPtr converts a Go function pointer to a function pointer conforming to the C calling convention.
    96  // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
    97  // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
    98  // of uintptr. Only a limited number of callbacks may be live in a single Go process, and any memory allocated
    99  // for these callbacks is not released until CallbackUnrefFnPtr is called. At most 2000 callbacks can always be live.
   100  //
   101  // Calling this function multiple times with the same function pointer will return the originally created callback
   102  // reference, reducing live callback pressure.
   103  func NewCallbackFnPtr(fnptr interface{}) uintptr {
   104  	val := reflect.ValueOf(fnptr)
   105  	if val.IsNil() {
   106  		panic("purego: function must not be nil")
   107  	}
   108  	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Func {
   109  		panic("purego: the type must be a function pointer but was not")
   110  	}
   111  
   112  	// Re-use callback to function pointer if available
   113  	if addr, ok := getCallbackByFnPtr(val); ok {
   114  		return addr
   115  	}
   116  
   117  	addr := compileCallback(val.Elem())
   118  
   119  	cbs.lock.Lock()
   120  	cbs.knownFnPtr[val.Pointer()] = addr
   121  	cbs.lock.Unlock()
   122  	return addr
   123  }
   124  
   125  // maxCb is the maximum number of callbacks
   126  // only increase this if you have added more to the callbackasm function
   127  const maxCB = 2000
   128  
   129  var cbs = struct {
   130  	lock       sync.RWMutex
   131  	holes      map[int]struct{}     // tracks available indexes in the funcs array
   132  	funcs      [maxCB]reflect.Value // the saved callbacks
   133  	knownIdx   map[uintptr]int      // maps callback addresses to index in funcs
   134  	knownFnPtr map[uintptr]uintptr  // maps function pointers to callback addresses
   135  }{
   136  	holes:      make(map[int]struct{}, maxCB),
   137  	knownIdx:   make(map[uintptr]int, maxCB),
   138  	knownFnPtr: make(map[uintptr]uintptr, maxCB),
   139  }
   140  
   141  func init() {
   142  	for i := 0; i < maxCB; i++ {
   143  		cbs.holes[i] = struct{}{}
   144  	}
   145  }
   146  
   147  func getCallbackByFnPtr(val reflect.Value) (uintptr, bool) {
   148  	cbs.lock.RLock()
   149  	defer cbs.lock.RUnlock()
   150  	addr, ok := cbs.knownFnPtr[val.Pointer()]
   151  	return addr, ok
   152  }
   153  
   154  type callbackArgs struct {
   155  	index uintptr
   156  	// args points to the argument block.
   157  	//
   158  	// The structure of the arguments goes
   159  	// float registers followed by the
   160  	// integer registers followed by the stack.
   161  	//
   162  	// This variable is treated as a continuous
   163  	// block of memory containing all of the arguments
   164  	// for this callback.
   165  	args unsafe.Pointer
   166  	// Below are out-args from callbackWrap
   167  	result uintptr
   168  }
   169  
   170  func compileCallback(val reflect.Value) uintptr {
   171  	ty := val.Type()
   172  	for i := 0; i < ty.NumIn(); i++ {
   173  		in := ty.In(i)
   174  		switch in.Kind() {
   175  		case reflect.Struct, reflect.Interface, reflect.Func, reflect.Slice,
   176  			reflect.Chan, reflect.Complex64, reflect.Complex128,
   177  			reflect.Map, reflect.Invalid:
   178  			panic("purego: unsupported argument type: " + in.Kind().String())
   179  		}
   180  	}
   181  output:
   182  	switch {
   183  	case ty.NumOut() == 1:
   184  		switch ty.Out(0).Kind() {
   185  		case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   186  			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
   187  			reflect.Bool, reflect.UnsafePointer:
   188  			break output
   189  		}
   190  		panic("purego: unsupported return type: " + ty.String())
   191  	case ty.NumOut() > 1:
   192  		panic("purego: callbacks can only have one return")
   193  	}
   194  	cbs.lock.Lock()
   195  	defer cbs.lock.Unlock()
   196  	if len(cbs.holes) == 0 {
   197  		panic("purego: the maximum number of callbacks has been reached")
   198  	}
   199  	var idx int
   200  	for i := range cbs.holes {
   201  		idx = i
   202  		break
   203  	}
   204  	delete(cbs.holes, idx)
   205  	cbs.funcs[idx] = val
   206  	addr := callbackasmAddr(idx)
   207  	cbs.knownIdx[addr] = idx
   208  	return addr
   209  }
   210  
   211  const ptrSize = unsafe.Sizeof((*int)(nil))
   212  
   213  const callbackMaxFrame = 64 * ptrSize
   214  
   215  // callbackasm is implemented in zcallback_GOOS_GOARCH.s
   216  //
   217  //go:linkname __callbackasm callbackasm
   218  var __callbackasm byte
   219  var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm))
   220  
   221  // callbackWrap_call allows the calling of the ABIInternal wrapper
   222  // which is required for runtime.cgocallback without the
   223  // <ABIInternal> tag which is only allowed in the runtime.
   224  // This closure is used inside sys_darwin_GOARCH.s
   225  var callbackWrap_call = callbackWrap
   226  
   227  // callbackWrap is called by assembly code which determines which Go function to call.
   228  // This function takes the arguments and passes them to the Go function and returns the result.
   229  func callbackWrap(a *callbackArgs) {
   230  	cbs.lock.RLock()
   231  	fn := cbs.funcs[a.index]
   232  	cbs.lock.RUnlock()
   233  	fnType := fn.Type()
   234  	args := make([]reflect.Value, fnType.NumIn())
   235  	frame := (*[callbackMaxFrame]uintptr)(a.args)
   236  	var floatsN int // floatsN represents the number of float arguments processed
   237  	var intsN int   // intsN represents the number of integer arguments processed
   238  	// stack points to the index into frame of the current stack element.
   239  	// The stack begins after the float and integer registers.
   240  	stack := numOfIntegerRegisters() + numOfFloats
   241  	for i := range args {
   242  		var pos int
   243  		addInt := func() {
   244  			if intsN >= numOfIntegerRegisters() {
   245  				pos = stack
   246  				stack++
   247  			} else {
   248  				// the integers begin after the floats in frame
   249  				pos = intsN + numOfFloats
   250  			}
   251  			intsN++
   252  		}
   253  		switch fnType.In(i).Kind() {
   254  		case reflect.Float32, reflect.Float64:
   255  			if floatsN >= numOfFloats {
   256  				pos = stack
   257  				stack++
   258  			} else {
   259  				pos = floatsN
   260  			}
   261  			floatsN++
   262  			args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem()
   263  		case reflect.String:
   264  			addInt()
   265  			args[i] = reflect.ValueOf(strings.GoString(frame[pos]))
   266  		default:
   267  			addInt()
   268  			args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem()
   269  		}
   270  	}
   271  	ret := fn.Call(args)
   272  	if len(ret) > 0 {
   273  		switch k := ret[0].Kind(); k {
   274  		case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
   275  			a.result = uintptr(ret[0].Uint())
   276  		case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
   277  			a.result = uintptr(ret[0].Int())
   278  		case reflect.Bool:
   279  			if ret[0].Bool() {
   280  				a.result = 1
   281  			} else {
   282  				a.result = 0
   283  			}
   284  		case reflect.Pointer:
   285  			a.result = ret[0].Pointer()
   286  		case reflect.UnsafePointer:
   287  			a.result = ret[0].Pointer()
   288  		default:
   289  			panic("purego: unsupported kind: " + k.String())
   290  		}
   291  	}
   292  }
   293  
   294  // callbackasmAddr returns address of runtime.callbackasm
   295  // function adjusted by i.
   296  // On x86 and amd64, runtime.callbackasm is a series of CALL instructions,
   297  // and we want callback to arrive at
   298  // correspondent call instruction instead of start of
   299  // runtime.callbackasm.
   300  // On ARM, runtime.callbackasm is a series of mov and branch instructions.
   301  // R12 is loaded with the callback index. Each entry is two instructions,
   302  // hence 8 bytes.
   303  func callbackasmAddr(i int) uintptr {
   304  	var entrySize int
   305  	switch runtime.GOARCH {
   306  	default:
   307  		panic("purego: unsupported architecture")
   308  	case "386", "amd64":
   309  		entrySize = 5
   310  	case "arm", "arm64":
   311  		// On ARM and ARM64, each entry is a MOV instruction
   312  		// followed by a branch instruction
   313  		entrySize = 8
   314  	}
   315  	return callbackasmABI0 + uintptr(i*entrySize)
   316  }