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 }