syscall_sysv.go raw

   1  // SPDX-License-Identifier: Apache-2.0
   2  // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
   3  
   4  //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd
   5  
   6  package purego
   7  
   8  import (
   9  	"reflect"
  10  	"runtime"
  11  	"sync"
  12  	"unsafe"
  13  )
  14  
  15  var syscall15XABI0 uintptr
  16  
  17  func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
  18  	args := thePool.Get().(*syscall15Args)
  19  	defer thePool.Put(args)
  20  
  21  	*args = syscall15Args{
  22  		fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
  23  		a1, a2, a3, a4, a5, a6, a7, a8,
  24  		0,
  25  	}
  26  
  27  	runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
  28  	return args.a1, args.a2, 0
  29  }
  30  
  31  // NewCallback converts a Go function to a function pointer conforming to the C calling convention.
  32  // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
  33  // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
  34  // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
  35  // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
  36  // provides similar functionality to windows.NewCallback it is distinct.
  37  func NewCallback(fn any) uintptr {
  38  	ty := reflect.TypeOf(fn)
  39  	for i := 0; i < ty.NumIn(); i++ {
  40  		in := ty.In(i)
  41  		if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
  42  			continue
  43  		}
  44  		if i != 0 {
  45  			panic("purego: CDecl must be the first argument")
  46  		}
  47  	}
  48  	return compileCallback(fn)
  49  }
  50  
  51  // maxCb is the maximum number of callbacks
  52  // only increase this if you have added more to the callbackasm function
  53  const maxCB = 2000
  54  
  55  var cbs struct {
  56  	lock  sync.Mutex
  57  	numFn int                  // the number of functions currently in cbs.funcs
  58  	funcs [maxCB]reflect.Value // the saved callbacks
  59  }
  60  
  61  type callbackArgs struct {
  62  	index uintptr
  63  	// args points to the argument block.
  64  	//
  65  	// The structure of the arguments goes
  66  	// float registers followed by the
  67  	// integer registers followed by the stack.
  68  	//
  69  	// This variable is treated as a continuous
  70  	// block of memory containing all of the arguments
  71  	// for this callback.
  72  	args unsafe.Pointer
  73  	// Below are out-args from callbackWrap
  74  	result uintptr
  75  }
  76  
  77  func compileCallback(fn any) uintptr {
  78  	val := reflect.ValueOf(fn)
  79  	if val.Kind() != reflect.Func {
  80  		panic("purego: the type must be a function but was not")
  81  	}
  82  	if val.IsNil() {
  83  		panic("purego: function must not be nil")
  84  	}
  85  	ty := val.Type()
  86  	for i := 0; i < ty.NumIn(); i++ {
  87  		in := ty.In(i)
  88  		switch in.Kind() {
  89  		case reflect.Struct:
  90  			if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {
  91  				continue
  92  			}
  93  			fallthrough
  94  		case reflect.Interface, reflect.Func, reflect.Slice,
  95  			reflect.Chan, reflect.Complex64, reflect.Complex128,
  96  			reflect.String, reflect.Map, reflect.Invalid:
  97  			panic("purego: unsupported argument type: " + in.Kind().String())
  98  		}
  99  	}
 100  output:
 101  	switch {
 102  	case ty.NumOut() == 1:
 103  		switch ty.Out(0).Kind() {
 104  		case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
 105  			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
 106  			reflect.Bool, reflect.UnsafePointer:
 107  			break output
 108  		}
 109  		panic("purego: unsupported return type: " + ty.String())
 110  	case ty.NumOut() > 1:
 111  		panic("purego: callbacks can only have one return")
 112  	}
 113  	cbs.lock.Lock()
 114  	defer cbs.lock.Unlock()
 115  	if cbs.numFn >= maxCB {
 116  		panic("purego: the maximum number of callbacks has been reached")
 117  	}
 118  	cbs.funcs[cbs.numFn] = val
 119  	cbs.numFn++
 120  	return callbackasmAddr(cbs.numFn - 1)
 121  }
 122  
 123  const ptrSize = unsafe.Sizeof((*int)(nil))
 124  
 125  const callbackMaxFrame = 64 * ptrSize
 126  
 127  // callbackasm is implemented in zcallback_GOOS_GOARCH.s
 128  //
 129  //go:linkname __callbackasm callbackasm
 130  var __callbackasm byte
 131  var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm))
 132  
 133  // callbackWrap_call allows the calling of the ABIInternal wrapper
 134  // which is required for runtime.cgocallback without the
 135  // <ABIInternal> tag which is only allowed in the runtime.
 136  // This closure is used inside sys_darwin_GOARCH.s
 137  var callbackWrap_call = callbackWrap
 138  
 139  // callbackWrap is called by assembly code which determines which Go function to call.
 140  // This function takes the arguments and passes them to the Go function and returns the result.
 141  func callbackWrap(a *callbackArgs) {
 142  	cbs.lock.Lock()
 143  	fn := cbs.funcs[a.index]
 144  	cbs.lock.Unlock()
 145  	fnType := fn.Type()
 146  	args := make([]reflect.Value, fnType.NumIn())
 147  	frame := (*[callbackMaxFrame]uintptr)(a.args)
 148  	var floatsN int // floatsN represents the number of float arguments processed
 149  	var intsN int   // intsN represents the number of integer arguments processed
 150  	// stack points to the index into frame of the current stack element.
 151  	// The stack begins after the float and integer registers.
 152  	stack := numOfIntegerRegisters() + numOfFloatRegisters
 153  	for i := range args {
 154  		var pos int
 155  		switch fnType.In(i).Kind() {
 156  		case reflect.Float32, reflect.Float64:
 157  			if floatsN >= numOfFloatRegisters {
 158  				pos = stack
 159  				stack++
 160  			} else {
 161  				pos = floatsN
 162  			}
 163  			floatsN++
 164  		case reflect.Struct:
 165  			// This is the CDecl field
 166  			args[i] = reflect.Zero(fnType.In(i))
 167  			continue
 168  		default:
 169  
 170  			if intsN >= numOfIntegerRegisters() {
 171  				pos = stack
 172  				stack++
 173  			} else {
 174  				// the integers begin after the floats in frame
 175  				pos = intsN + numOfFloatRegisters
 176  			}
 177  			intsN++
 178  		}
 179  		args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem()
 180  	}
 181  	ret := fn.Call(args)
 182  	if len(ret) > 0 {
 183  		switch k := ret[0].Kind(); k {
 184  		case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
 185  			a.result = uintptr(ret[0].Uint())
 186  		case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
 187  			a.result = uintptr(ret[0].Int())
 188  		case reflect.Bool:
 189  			if ret[0].Bool() {
 190  				a.result = 1
 191  			} else {
 192  				a.result = 0
 193  			}
 194  		case reflect.Pointer:
 195  			a.result = ret[0].Pointer()
 196  		case reflect.UnsafePointer:
 197  			a.result = ret[0].Pointer()
 198  		default:
 199  			panic("purego: unsupported kind: " + k.String())
 200  		}
 201  	}
 202  }
 203  
 204  // callbackasmAddr returns address of runtime.callbackasm
 205  // function adjusted by i.
 206  // On x86 and amd64, runtime.callbackasm is a series of CALL instructions,
 207  // and we want callback to arrive at
 208  // correspondent call instruction instead of start of
 209  // runtime.callbackasm.
 210  // On ARM, runtime.callbackasm is a series of mov and branch instructions.
 211  // R12 is loaded with the callback index. Each entry is two instructions,
 212  // hence 8 bytes.
 213  func callbackasmAddr(i int) uintptr {
 214  	var entrySize int
 215  	switch runtime.GOARCH {
 216  	default:
 217  		panic("purego: unsupported architecture")
 218  	case "386", "amd64":
 219  		entrySize = 5
 220  	case "arm", "arm64", "loong64":
 221  		// On ARM and ARM64, each entry is a MOV instruction
 222  		// followed by a branch instruction
 223  		entrySize = 8
 224  	}
 225  	return callbackasmABI0 + uintptr(i*entrySize)
 226  }
 227