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 }