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