chore: switch purego dependency to bnema/purego fork
Replace local path replace directive with GitHub URL pointing to bnema/purego@91aa177 to avoid accidental pushes to upstream.
- commit
- 8b029581aa8aebd90aa84a2b638058062afa9bdd
- author
- brice · · 2026-03-23 19:51 UTC
- parent
- f14f9b6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
51 files · +1860 -488
M go.mod +2 -0
| @@ -3,3 +3,5 @@ module github.com/bnema/puregotk | |||
| 3 | 3 | go 1.26.1 | |
| 4 | 4 | ||
| 5 | 5 | require github.com/ebitengine/purego v0.10.0 | |
| 6 | + | ||
| 7 | + | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | |
| @@ -3,3 +3,5 @@ module github.com/bnema/puregotk | |||||
| 3 | go 1.26.1 | 3 | go 1.26.1 | ||
| 4 | 4 | ||||
| 5 | require github.com/ebitengine/purego v0.10.0 | 5 | require github.com/ebitengine/purego v0.10.0 | ||
| 6 | + | ||||
| 7 | + | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | |||
M go.sum +2 -2 generated file: collapsed
M vendor/github.com/ebitengine/purego/README.md +3 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/dlfcn_nocgo_netbsd.go +2 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/func.go +220 -107
| @@ -159,8 +159,8 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 159 | 159 | } | |
| 160 | 160 | } | |
| 161 | 161 | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | |
| 162 | - | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, | |
| 163 | - | reflect.Slice, reflect.Bool: | |
| 162 | + | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer, | |
| 163 | + | reflect.Slice, reflect.Array, reflect.Bool: | |
| 164 | 164 | if ints < numOfIntegerRegisters() { | |
| 165 | 165 | ints++ | |
| 166 | 166 | } else { | |
| @@ -173,7 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 173 | 173 | stack++ | |
| 174 | 174 | } | |
| 175 | 175 | case reflect.Struct: | |
| 176 | - | ensureStructSupportedForRegisterFunc() | |
| 176 | + | ensureStructSupported() | |
| 177 | 177 | if arg.Size() == 0 { | |
| 178 | 178 | continue | |
| 179 | 179 | } | |
| @@ -192,7 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 192 | 192 | } | |
| 193 | 193 | } | |
| 194 | 194 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 195 | - | ensureStructSupportedForRegisterFunc() | |
| 195 | + | ensureStructSupported() | |
| 196 | 196 | outType := ty.Out(0) | |
| 197 | 197 | checkStructFieldsSupported(outType) | |
| 198 | 198 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | |
| @@ -218,6 +218,29 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 218 | 218 | } | |
| 219 | 219 | } | |
| 220 | 220 | ||
| 221 | + | // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed. | |
| 222 | + | // keepAlive is only used for: string args (CString alloc), struct args (addStruct), | |
| 223 | + | // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy), | |
| 224 | + | // and variadic []any args (unknown types at registration time). | |
| 225 | + | needsKeepAlive := false | |
| 226 | + | for i := 0; i < ty.NumIn(); i++ { | |
| 227 | + | inKind := ty.In(i).Kind() | |
| 228 | + | switch inKind { | |
| 229 | + | case reflect.String, reflect.Struct, reflect.Array, reflect.Slice: | |
| 230 | + | // Slice covers []string (CString allocs) and variadic ...any ([]any) | |
| 231 | + | needsKeepAlive = true | |
| 232 | + | case reflect.Interface: | |
| 233 | + | needsKeepAlive = true | |
| 234 | + | } | |
| 235 | + | } | |
| 236 | + | if ty.NumOut() == 1 { | |
| 237 | + | switch ty.Out(0).Kind() { | |
| 238 | + | case reflect.Struct: | |
| 239 | + | // struct returns may need hidden pointer allocation stored in keepAlive | |
| 240 | + | needsKeepAlive = true | |
| 241 | + | } | |
| 242 | + | } | |
| 243 | + | ||
| 221 | 244 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |
| 222 | 245 | var sysargs [maxArgs]uintptr | |
| 223 | 246 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | |
| @@ -264,132 +287,191 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 264 | 287 | addFloat = addStack | |
| 265 | 288 | } | |
| 266 | 289 | ||
| 267 | - | var keepAlive []any | |
| 268 | - | defer func() { | |
| 269 | - | runtime.KeepAlive(keepAlive) | |
| 270 | - | runtime.KeepAlive(args) | |
| 271 | - | }() | |
| 290 | + | if needsKeepAlive { | |
| 291 | + | // Slow path: allocate keepAlive slice and defer cleanup. | |
| 292 | + | // Used when args include strings, structs, arrays, or variadic []any. | |
| 293 | + | var keepAlive []any | |
| 294 | + | defer func() { | |
| 295 | + | runtime.KeepAlive(keepAlive) | |
| 296 | + | runtime.KeepAlive(args) | |
| 297 | + | }() | |
| 272 | 298 | ||
| 273 | - | var arm64_r8 uintptr | |
| 274 | - | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 275 | - | outType := ty.Out(0) | |
| 276 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | |
| 277 | - | val := reflect.New(outType) | |
| 278 | - | keepAlive = append(keepAlive, val) | |
| 279 | - | addInt(val.Pointer()) | |
| 280 | - | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |
| 281 | - | isAllFloats, numFields := isAllSameFloat(outType) | |
| 282 | - | if !isAllFloats || numFields > 4 { | |
| 299 | + | var arm64_r8 uintptr | |
| 300 | + | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 301 | + | outType := ty.Out(0) | |
| 302 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | |
| 283 | 303 | val := reflect.New(outType) | |
| 284 | 304 | keepAlive = append(keepAlive, val) | |
| 285 | - | arm64_r8 = val.Pointer() | |
| 305 | + | addInt(val.Pointer()) | |
| 306 | + | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |
| 307 | + | isAllFloats, numFields := isAllSameFloat(outType) | |
| 308 | + | if !isAllFloats || numFields > 4 { | |
| 309 | + | val := reflect.New(outType) | |
| 310 | + | keepAlive = append(keepAlive, val) | |
| 311 | + | arm64_r8 = val.Pointer() | |
| 312 | + | } | |
| 286 | 313 | } | |
| 287 | 314 | } | |
| 315 | + | for i, v := range args { | |
| 316 | + | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { | |
| 317 | + | if i != len(args)-1 { | |
| 318 | + | panic("purego: can only expand last parameter") | |
| 319 | + | } | |
| 320 | + | for _, x := range variadic { | |
| 321 | + | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 322 | + | } | |
| 323 | + | continue | |
| 324 | + | } | |
| 325 | + | // Check if we need to start Darwin ARM64 C-style stack packing | |
| 326 | + | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | |
| 327 | + | // Collect and separate remaining args into register vs stack | |
| 328 | + | stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats, | |
| 329 | + | keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 330 | + | keepAlive = newKeepAlive | |
| 331 | + | ||
| 332 | + | // Bundle stack arguments with C-style packing | |
| 333 | + | bundleStackArgs(stackArgs, addStack) | |
| 334 | + | break | |
| 335 | + | } | |
| 336 | + | keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 337 | + | } | |
| 338 | + | return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args) | |
| 288 | 339 | } | |
| 340 | + | ||
| 341 | + | // Fast path: no keepAlive needed (pointer/integer/bool/float args only). | |
| 342 | + | // Skip keepAlive slice allocation and defer overhead. | |
| 343 | + | defer runtime.KeepAlive(args) | |
| 344 | + | ||
| 289 | 345 | for i, v := range args { | |
| 290 | - | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { | |
| 346 | + | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { | |
| 291 | 347 | if i != len(args)-1 { | |
| 292 | 348 | panic("purego: can only expand last parameter") | |
| 293 | 349 | } | |
| 294 | 350 | for _, x := range variadic { | |
| 295 | - | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 351 | + | addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 296 | 352 | } | |
| 297 | 353 | continue | |
| 298 | 354 | } | |
| 299 | 355 | // Check if we need to start Darwin ARM64 C-style stack packing | |
| 300 | 356 | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | |
| 301 | - | // Collect and separate remaining args into register vs stack | |
| 302 | - | stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats, | |
| 303 | - | keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 304 | - | keepAlive = newKeepAlive | |
| 305 | - | ||
| 306 | - | // Bundle stack arguments with C-style packing | |
| 357 | + | stackArgs, _ := collectStackArgs(args, i, numInts, numFloats, | |
| 358 | + | nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 307 | 359 | bundleStackArgs(stackArgs, addStack) | |
| 308 | 360 | break | |
| 309 | 361 | } | |
| 310 | - | keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 362 | + | addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 311 | 363 | } | |
| 312 | 364 | ||
| 313 | - | syscall := thePool.Get().(*syscall15Args) | |
| 314 | - | defer thePool.Put(syscall) | |
| 365 | + | return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args) | |
| 366 | + | }) | |
| 367 | + | fn.Set(v) | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | // callAndReturn performs the actual C function call and constructs the return value. | |
| 371 | + | // It is shared between the fast path (no keepAlive) and slow path (with keepAlive). | |
| 372 | + | func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value { | |
| 373 | + | syscall := thePool.Get().(*syscall15Args) | |
| 374 | + | defer thePool.Put(syscall) | |
| 315 | 375 | ||
| 316 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | |
| 317 | - | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| 318 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 319 | - | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| 320 | - | // Use the normal arm64 calling convention even on Windows | |
| 321 | - | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | |
| 322 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 323 | - | } else { | |
| 324 | - | *syscall = syscall15Args{} | |
| 325 | - | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | |
| 326 | - | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], | |
| 327 | - | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| 328 | - | sysargs[12], sysargs[13], sysargs[14]) | |
| 329 | - | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support | |
| 376 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | |
| 377 | + | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| 378 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 379 | + | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| 380 | + | // Use the normal arm64 calling convention even on Windows | |
| 381 | + | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | |
| 382 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 383 | + | } else { | |
| 384 | + | *syscall = syscall15Args{} | |
| 385 | + | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | |
| 386 | + | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], | |
| 387 | + | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| 388 | + | sysargs[12], sysargs[13], sysargs[14]) | |
| 389 | + | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support | |
| 390 | + | } | |
| 391 | + | if ty.NumOut() == 0 { | |
| 392 | + | return nil | |
| 393 | + | } | |
| 394 | + | outType := ty.Out(0) | |
| 395 | + | v := reflect.New(outType).Elem() | |
| 396 | + | switch outType.Kind() { | |
| 397 | + | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
| 398 | + | v.SetUint(uint64(syscall.a1)) | |
| 399 | + | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 400 | + | v.SetInt(int64(syscall.a1)) | |
| 401 | + | case reflect.Bool: | |
| 402 | + | v.SetBool(byte(syscall.a1) != 0) | |
| 403 | + | case reflect.UnsafePointer: | |
| 404 | + | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |
| 405 | + | v = reflect.New(outType).Elem() | |
| 406 | + | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |
| 407 | + | case reflect.Pointer: | |
| 408 | + | v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() | |
| 409 | + | case reflect.Func: | |
| 410 | + | // wrap this C function in a nicely typed Go function | |
| 411 | + | v = reflect.New(outType) | |
| 412 | + | RegisterFunc(v.Interface(), syscall.a1) | |
| 413 | + | case reflect.String: | |
| 414 | + | v = reflect.ValueOf(strings.GoString(syscall.a1)) | |
| 415 | + | case reflect.Float32: | |
| 416 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 417 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 418 | + | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |
| 419 | + | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |
| 420 | + | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |
| 421 | + | v = reflect.New(outType).Elem() | |
| 422 | + | switch runtime.GOARCH { | |
| 423 | + | case "386": | |
| 424 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 425 | + | case "ppc64le": | |
| 426 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 427 | + | case "s390x": | |
| 428 | + | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |
| 429 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |
| 430 | + | default: | |
| 431 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 330 | 432 | } | |
| 331 | - | if ty.NumOut() == 0 { | |
| 332 | - | return nil | |
| 433 | + | case reflect.Float64: | |
| 434 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 435 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 436 | + | v = reflect.New(outType).Elem() | |
| 437 | + | if is32bit { | |
| 438 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 439 | + | } else { | |
| 440 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 333 | 441 | } | |
| 334 | - | outType := ty.Out(0) | |
| 335 | - | v := reflect.New(outType).Elem() | |
| 336 | - | switch outType.Kind() { | |
| 337 | - | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
| 338 | - | v.SetUint(uint64(syscall.a1)) | |
| 339 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 340 | - | v.SetInt(int64(syscall.a1)) | |
| 341 | - | case reflect.Bool: | |
| 342 | - | v.SetBool(byte(syscall.a1) != 0) | |
| 343 | - | case reflect.UnsafePointer: | |
| 344 | - | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |
| 345 | - | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |
| 346 | - | case reflect.Ptr: | |
| 347 | - | v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() | |
| 348 | - | case reflect.Func: | |
| 349 | - | // wrap this C function in a nicely typed Go function | |
| 350 | - | v = reflect.New(outType) | |
| 351 | - | RegisterFunc(v.Interface(), syscall.a1) | |
| 352 | - | case reflect.String: | |
| 353 | - | v.SetString(strings.GoString(syscall.a1)) | |
| 354 | - | case reflect.Float32: | |
| 355 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 356 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 357 | - | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |
| 358 | - | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |
| 359 | - | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |
| 360 | - | switch runtime.GOARCH { | |
| 361 | - | case "386": | |
| 362 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 363 | - | case "ppc64le": | |
| 364 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 365 | - | case "s390x": | |
| 366 | - | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |
| 367 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |
| 368 | - | default: | |
| 369 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 370 | - | } | |
| 371 | - | case reflect.Float64: | |
| 372 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 373 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 374 | - | if is32bit { | |
| 375 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 442 | + | case reflect.Struct: | |
| 443 | + | v = getStruct(outType, *syscall) | |
| 444 | + | case reflect.Slice: | |
| 445 | + | if outType.Elem().Kind() == reflect.String { | |
| 446 | + | // C returns char** (NULL-terminated array of strings) | |
| 447 | + | if syscall.a1 == 0 { | |
| 448 | + | v = reflect.Zero(outType) | |
| 376 | 449 | } else { | |
| 377 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 450 | + | var result []string | |
| 451 | + | ptr := syscall.a1 | |
| 452 | + | for { | |
| 453 | + | strPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |
| 454 | + | if strPtr == 0 { | |
| 455 | + | break | |
| 456 | + | } | |
| 457 | + | result = append(result, strings.GoString(strPtr)) | |
| 458 | + | ptr += unsafe.Sizeof(uintptr(0)) | |
| 459 | + | } | |
| 460 | + | v = reflect.ValueOf(result) | |
| 378 | 461 | } | |
| 379 | - | case reflect.Struct: | |
| 380 | - | v = getStruct(outType, *syscall) | |
| 381 | - | default: | |
| 382 | - | panic("purego: unsupported return kind: " + outType.Kind().String()) | |
| 383 | - | } | |
| 384 | - | if len(args) > 0 { | |
| 385 | - | // reuse args slice instead of allocating one when possible | |
| 386 | - | args[0] = v | |
| 387 | - | return args[:1] | |
| 388 | 462 | } else { | |
| 389 | - | return []reflect.Value{v} | |
| 463 | + | panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String()) | |
| 390 | 464 | } | |
| 391 | - | }) | |
| 392 | - | fn.Set(v) | |
| 465 | + | default: | |
| 466 | + | panic("purego: unsupported return kind: " + outType.Kind().String()) | |
| 467 | + | } | |
| 468 | + | if len(args) > 0 { | |
| 469 | + | // reuse args slice instead of allocating one when possible | |
| 470 | + | args[0] = v | |
| 471 | + | return args[:1] | |
| 472 | + | } else { | |
| 473 | + | return []reflect.Value{v} | |
| 474 | + | } | |
| 393 | 475 | } | |
| 394 | 476 | ||
| 395 | 477 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | |
| @@ -403,9 +485,29 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 403 | 485 | addInt(uintptr(v.Uint())) | |
| 404 | 486 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 405 | 487 | addInt(uintptr(v.Int())) | |
| 406 | - | case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: | |
| 488 | + | case reflect.Pointer, reflect.UnsafePointer: | |
| 407 | 489 | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | |
| 408 | 490 | addInt(v.Pointer()) | |
| 491 | + | case reflect.Slice: | |
| 492 | + | if v.Type().Elem().Kind() == reflect.String { | |
| 493 | + | ss := v.Interface().([]string) | |
| 494 | + | if ss == nil { | |
| 495 | + | addInt(0) | |
| 496 | + | } else { | |
| 497 | + | res := make([]*byte, len(ss)+1) | |
| 498 | + | for i, s := range ss { | |
| 499 | + | ptr := strings.CString(s) | |
| 500 | + | keepAlive = append(keepAlive, ptr) | |
| 501 | + | res[i] = ptr | |
| 502 | + | } | |
| 503 | + | // NULL-terminated for C | |
| 504 | + | res[len(ss)] = nil | |
| 505 | + | keepAlive = append(keepAlive, res) | |
| 506 | + | addInt(uintptr(unsafe.Pointer(&res[0]))) | |
| 507 | + | } | |
| 508 | + | } else { | |
| 509 | + | addInt(v.Pointer()) | |
| 510 | + | } | |
| 409 | 511 | case reflect.Func: | |
| 410 | 512 | addInt(NewCallback(v.Interface())) | |
| 411 | 513 | case reflect.Bool: | |
| @@ -429,6 +531,17 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 429 | 531 | } else { | |
| 430 | 532 | addFloat(uintptr(math.Float64bits(v.Float()))) | |
| 431 | 533 | } | |
| 534 | + | case reflect.Array: | |
| 535 | + | // Task 7: Support fixed-size array arguments by passing a pointer to the array data. | |
| 536 | + | if v.CanAddr() { | |
| 537 | + | addInt(v.Addr().Pointer()) | |
| 538 | + | } else { | |
| 539 | + | // Array is not addressable (e.g. passed by value); make an addressable copy. | |
| 540 | + | tmp := reflect.New(v.Type()).Elem() | |
| 541 | + | tmp.Set(v) | |
| 542 | + | keepAlive = append(keepAlive, tmp) | |
| 543 | + | addInt(tmp.Addr().Pointer()) | |
| 544 | + | } | |
| 432 | 545 | case reflect.Struct: | |
| 433 | 546 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | |
| 434 | 547 | default: | |
| @@ -482,7 +595,7 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||
| 482 | 595 | switch f.Kind() { | |
| 483 | 596 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
| 484 | 597 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | |
| 485 | - | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32, | |
| 598 | + | reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32, | |
| 486 | 599 | reflect.Bool: | |
| 487 | 600 | default: | |
| 488 | 601 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | |
| @@ -490,12 +603,12 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||
| 490 | 603 | } | |
| 491 | 604 | } | |
| 492 | 605 | ||
| 493 | - | func ensureStructSupportedForRegisterFunc() { | |
| 606 | + | func ensureStructSupported() { | |
| 494 | 607 | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | |
| 495 | - | panic("purego: struct arguments are only supported on amd64 and arm64") | |
| 608 | + | panic("purego: struct arguments/returns are only supported on amd64 and arm64") | |
| 496 | 609 | } | |
| 497 | 610 | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | |
| 498 | - | panic("purego: struct arguments are only supported on darwin and linux") | |
| 611 | + | panic("purego: struct arguments/returns are only supported on darwin and linux") | |
| 499 | 612 | } | |
| 500 | 613 | } | |
| 501 | 614 | ||
| @@ -159,8 +159,8 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 159 | } | 159 | } | ||
| 160 | } | 160 | } | ||
| 161 | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | 161 | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | ||
| 162 | - | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, | 162 | + | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer, |
| 163 | - | reflect.Slice, reflect.Bool: | 163 | + | reflect.Slice, reflect.Array, reflect.Bool: |
| 164 | if ints < numOfIntegerRegisters() { | 164 | if ints < numOfIntegerRegisters() { | ||
| 165 | ints++ | 165 | ints++ | ||
| 166 | } else { | 166 | } else { | ||
| @@ -173,7 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 173 | stack++ | 173 | stack++ | ||
| 174 | } | 174 | } | ||
| 175 | case reflect.Struct: | 175 | case reflect.Struct: | ||
| 176 | - | ensureStructSupportedForRegisterFunc() | 176 | + | ensureStructSupported() |
| 177 | if arg.Size() == 0 { | 177 | if arg.Size() == 0 { | ||
| 178 | continue | 178 | continue | ||
| 179 | } | 179 | } | ||
| @@ -192,7 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 192 | } | 192 | } | ||
| 193 | } | 193 | } | ||
| 194 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 194 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 195 | - | ensureStructSupportedForRegisterFunc() | 195 | + | ensureStructSupported() |
| 196 | outType := ty.Out(0) | 196 | outType := ty.Out(0) | ||
| 197 | checkStructFieldsSupported(outType) | 197 | checkStructFieldsSupported(outType) | ||
| 198 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | 198 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | ||
| @@ -218,6 +218,29 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 218 | } | 218 | } | ||
| 219 | } | 219 | } | ||
| 220 | 220 | ||||
| 221 | + | // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed. | |||
| 222 | + | // keepAlive is only used for: string args (CString alloc), struct args (addStruct), | |||
| 223 | + | // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy), | |||
| 224 | + | // and variadic []any args (unknown types at registration time). | |||
| 225 | + | needsKeepAlive := false | |||
| 226 | + | for i := 0; i < ty.NumIn(); i++ { | |||
| 227 | + | inKind := ty.In(i).Kind() | |||
| 228 | + | switch inKind { | |||
| 229 | + | case reflect.String, reflect.Struct, reflect.Array, reflect.Slice: | |||
| 230 | + | // Slice covers []string (CString allocs) and variadic ...any ([]any) | |||
| 231 | + | needsKeepAlive = true | |||
| 232 | + | case reflect.Interface: | |||
| 233 | + | needsKeepAlive = true | |||
| 234 | + | } | |||
| 235 | + | } | |||
| 236 | + | if ty.NumOut() == 1 { | |||
| 237 | + | switch ty.Out(0).Kind() { | |||
| 238 | + | case reflect.Struct: | |||
| 239 | + | // struct returns may need hidden pointer allocation stored in keepAlive | |||
| 240 | + | needsKeepAlive = true | |||
| 241 | + | } | |||
| 242 | + | } | |||
| 243 | + | ||||
| 221 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | 244 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | ||
| 222 | var sysargs [maxArgs]uintptr | 245 | var sysargs [maxArgs]uintptr | ||
| 223 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | 246 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | ||
| @@ -264,132 +287,191 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 264 | addFloat = addStack | 287 | addFloat = addStack | ||
| 265 | } | 288 | } | ||
| 266 | 289 | ||||
| 267 | - | var keepAlive []any | 290 | + | if needsKeepAlive { |
| 268 | - | defer func() { | 291 | + | // Slow path: allocate keepAlive slice and defer cleanup. |
| 269 | - | runtime.KeepAlive(keepAlive) | 292 | + | // Used when args include strings, structs, arrays, or variadic []any. |
| 270 | - | runtime.KeepAlive(args) | 293 | + | var keepAlive []any |
| 271 | - | }() | 294 | + | defer func() { |
| 295 | + | runtime.KeepAlive(keepAlive) | |||
| 296 | + | runtime.KeepAlive(args) | |||
| 297 | + | }() | |||
| 272 | 298 | ||||
| 273 | - | var arm64_r8 uintptr | 299 | + | var arm64_r8 uintptr |
| 274 | - | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 300 | + | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { |
| 275 | - | outType := ty.Out(0) | 301 | + | outType := ty.Out(0) |
| 276 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | 302 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { |
| 277 | - | val := reflect.New(outType) | |||
| 278 | - | keepAlive = append(keepAlive, val) | |||
| 279 | - | addInt(val.Pointer()) | |||
| 280 | - | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |||
| 281 | - | isAllFloats, numFields := isAllSameFloat(outType) | |||
| 282 | - | if !isAllFloats || numFields > 4 { | |||
| 283 | val := reflect.New(outType) | 303 | val := reflect.New(outType) | ||
| 284 | keepAlive = append(keepAlive, val) | 304 | keepAlive = append(keepAlive, val) | ||
| 285 | - | arm64_r8 = val.Pointer() | 305 | + | addInt(val.Pointer()) |
| 306 | + | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |||
| 307 | + | isAllFloats, numFields := isAllSameFloat(outType) | |||
| 308 | + | if !isAllFloats || numFields > 4 { | |||
| 309 | + | val := reflect.New(outType) | |||
| 310 | + | keepAlive = append(keepAlive, val) | |||
| 311 | + | arm64_r8 = val.Pointer() | |||
| 312 | + | } | |||
| 286 | } | 313 | } | ||
| 287 | } | 314 | } | ||
| 315 | + | for i, v := range args { | |||
| 316 | + | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { | |||
| 317 | + | if i != len(args)-1 { | |||
| 318 | + | panic("purego: can only expand last parameter") | |||
| 319 | + | } | |||
| 320 | + | for _, x := range variadic { | |||
| 321 | + | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |||
| 322 | + | } | |||
| 323 | + | continue | |||
| 324 | + | } | |||
| 325 | + | // Check if we need to start Darwin ARM64 C-style stack packing | |||
| 326 | + | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | |||
| 327 | + | // Collect and separate remaining args into register vs stack | |||
| 328 | + | stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats, | |||
| 329 | + | keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |||
| 330 | + | keepAlive = newKeepAlive | |||
| 331 | + | ||||
| 332 | + | // Bundle stack arguments with C-style packing | |||
| 333 | + | bundleStackArgs(stackArgs, addStack) | |||
| 334 | + | break | |||
| 335 | + | } | |||
| 336 | + | keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |||
| 337 | + | } | |||
| 338 | + | return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args) | |||
| 288 | } | 339 | } | ||
| 340 | + | ||||
| 341 | + | // Fast path: no keepAlive needed (pointer/integer/bool/float args only). | |||
| 342 | + | // Skip keepAlive slice allocation and defer overhead. | |||
| 343 | + | defer runtime.KeepAlive(args) | |||
| 344 | + | ||||
| 289 | for i, v := range args { | 345 | for i, v := range args { | ||
| 290 | - | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { | 346 | + | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { |
| 291 | if i != len(args)-1 { | 347 | if i != len(args)-1 { | ||
| 292 | panic("purego: can only expand last parameter") | 348 | panic("purego: can only expand last parameter") | ||
| 293 | } | 349 | } | ||
| 294 | for _, x := range variadic { | 350 | for _, x := range variadic { | ||
| 295 | - | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | 351 | + | addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) |
| 296 | } | 352 | } | ||
| 297 | continue | 353 | continue | ||
| 298 | } | 354 | } | ||
| 299 | // Check if we need to start Darwin ARM64 C-style stack packing | 355 | // Check if we need to start Darwin ARM64 C-style stack packing | ||
| 300 | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | 356 | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | ||
| 301 | - | // Collect and separate remaining args into register vs stack | 357 | + | stackArgs, _ := collectStackArgs(args, i, numInts, numFloats, |
| 302 | - | stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats, | 358 | + | nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) |
| 303 | - | keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |||
| 304 | - | keepAlive = newKeepAlive | |||
| 305 | - | ||||
| 306 | - | // Bundle stack arguments with C-style packing | |||
| 307 | bundleStackArgs(stackArgs, addStack) | 359 | bundleStackArgs(stackArgs, addStack) | ||
| 308 | break | 360 | break | ||
| 309 | } | 361 | } | ||
| 310 | - | keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | 362 | + | addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) |
| 311 | } | 363 | } | ||
| 312 | 364 | ||||
| 313 | - | syscall := thePool.Get().(*syscall15Args) | 365 | + | return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args) |
| 314 | - | defer thePool.Put(syscall) | 366 | + | }) |
| 367 | + | fn.Set(v) | |||
| 368 | + | } | |||
| 369 | + | ||||
| 370 | + | // callAndReturn performs the actual C function call and constructs the return value. | |||
| 371 | + | // It is shared between the fast path (no keepAlive) and slow path (with keepAlive). | |||
| 372 | + | func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value { | |||
| 373 | + | syscall := thePool.Get().(*syscall15Args) | |||
| 374 | + | defer thePool.Put(syscall) | |||
| 315 | 375 | ||||
| 316 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | 376 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { |
| 317 | - | syscall.Set(cfn, sysargs[:], floats[:], 0) | 377 | + | syscall.Set(cfn, sysargs[:], floats[:], 0) |
| 318 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 378 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) |
| 319 | - | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | 379 | + | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { |
| 320 | - | // Use the normal arm64 calling convention even on Windows | 380 | + | // Use the normal arm64 calling convention even on Windows |
| 321 | - | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | 381 | + | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) |
| 322 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 382 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) |
| 323 | - | } else { | 383 | + | } else { |
| 324 | - | *syscall = syscall15Args{} | 384 | + | *syscall = syscall15Args{} |
| 325 | - | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | 385 | + | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats |
| 326 | - | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], | 386 | + | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], |
| 327 | - | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | 387 | + | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], |
| 328 | - | sysargs[12], sysargs[13], sysargs[14]) | 388 | + | sysargs[12], sysargs[13], sysargs[14]) |
| 329 | - | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support | 389 | + | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support |
| 390 | + | } | |||
| 391 | + | if ty.NumOut() == 0 { | |||
| 392 | + | return nil | |||
| 393 | + | } | |||
| 394 | + | outType := ty.Out(0) | |||
| 395 | + | v := reflect.New(outType).Elem() | |||
| 396 | + | switch outType.Kind() { | |||
| 397 | + | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| 398 | + | v.SetUint(uint64(syscall.a1)) | |||
| 399 | + | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| 400 | + | v.SetInt(int64(syscall.a1)) | |||
| 401 | + | case reflect.Bool: | |||
| 402 | + | v.SetBool(byte(syscall.a1) != 0) | |||
| 403 | + | case reflect.UnsafePointer: | |||
| 404 | + | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |||
| 405 | + | v = reflect.New(outType).Elem() | |||
| 406 | + | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |||
| 407 | + | case reflect.Pointer: | |||
| 408 | + | v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() | |||
| 409 | + | case reflect.Func: | |||
| 410 | + | // wrap this C function in a nicely typed Go function | |||
| 411 | + | v = reflect.New(outType) | |||
| 412 | + | RegisterFunc(v.Interface(), syscall.a1) | |||
| 413 | + | case reflect.String: | |||
| 414 | + | v = reflect.ValueOf(strings.GoString(syscall.a1)) | |||
| 415 | + | case reflect.Float32: | |||
| 416 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 417 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 418 | + | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |||
| 419 | + | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |||
| 420 | + | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |||
| 421 | + | v = reflect.New(outType).Elem() | |||
| 422 | + | switch runtime.GOARCH { | |||
| 423 | + | case "386": | |||
| 424 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 425 | + | case "ppc64le": | |||
| 426 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 427 | + | case "s390x": | |||
| 428 | + | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |||
| 429 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |||
| 430 | + | default: | |||
| 431 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 330 | } | 432 | } | ||
| 331 | - | if ty.NumOut() == 0 { | 433 | + | case reflect.Float64: |
| 332 | - | return nil | 434 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. |
| 435 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 436 | + | v = reflect.New(outType).Elem() | |||
| 437 | + | if is32bit { | |||
| 438 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 439 | + | } else { | |||
| 440 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 333 | } | 441 | } | ||
| 334 | - | outType := ty.Out(0) | 442 | + | case reflect.Struct: |
| 335 | - | v := reflect.New(outType).Elem() | 443 | + | v = getStruct(outType, *syscall) |
| 336 | - | switch outType.Kind() { | 444 | + | case reflect.Slice: |
| 337 | - | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | 445 | + | if outType.Elem().Kind() == reflect.String { |
| 338 | - | v.SetUint(uint64(syscall.a1)) | 446 | + | // C returns char** (NULL-terminated array of strings) |
| 339 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | 447 | + | if syscall.a1 == 0 { |
| 340 | - | v.SetInt(int64(syscall.a1)) | 448 | + | v = reflect.Zero(outType) |
| 341 | - | case reflect.Bool: | |||
| 342 | - | v.SetBool(byte(syscall.a1) != 0) | |||
| 343 | - | case reflect.UnsafePointer: | |||
| 344 | - | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |||
| 345 | - | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |||
| 346 | - | case reflect.Ptr: | |||
| 347 | - | v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() | |||
| 348 | - | case reflect.Func: | |||
| 349 | - | // wrap this C function in a nicely typed Go function | |||
| 350 | - | v = reflect.New(outType) | |||
| 351 | - | RegisterFunc(v.Interface(), syscall.a1) | |||
| 352 | - | case reflect.String: | |||
| 353 | - | v.SetString(strings.GoString(syscall.a1)) | |||
| 354 | - | case reflect.Float32: | |||
| 355 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 356 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 357 | - | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |||
| 358 | - | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |||
| 359 | - | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |||
| 360 | - | switch runtime.GOARCH { | |||
| 361 | - | case "386": | |||
| 362 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 363 | - | case "ppc64le": | |||
| 364 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 365 | - | case "s390x": | |||
| 366 | - | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |||
| 367 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |||
| 368 | - | default: | |||
| 369 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 370 | - | } | |||
| 371 | - | case reflect.Float64: | |||
| 372 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 373 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 374 | - | if is32bit { | |||
| 375 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 376 | } else { | 449 | } else { | ||
| 377 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | 450 | + | var result []string |
| 451 | + | ptr := syscall.a1 | |||
| 452 | + | for { | |||
| 453 | + | strPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |||
| 454 | + | if strPtr == 0 { | |||
| 455 | + | break | |||
| 456 | + | } | |||
| 457 | + | result = append(result, strings.GoString(strPtr)) | |||
| 458 | + | ptr += unsafe.Sizeof(uintptr(0)) | |||
| 459 | + | } | |||
| 460 | + | v = reflect.ValueOf(result) | |||
| 378 | } | 461 | } | ||
| 379 | - | case reflect.Struct: | |||
| 380 | - | v = getStruct(outType, *syscall) | |||
| 381 | - | default: | |||
| 382 | - | panic("purego: unsupported return kind: " + outType.Kind().String()) | |||
| 383 | - | } | |||
| 384 | - | if len(args) > 0 { | |||
| 385 | - | // reuse args slice instead of allocating one when possible | |||
| 386 | - | args[0] = v | |||
| 387 | - | return args[:1] | |||
| 388 | } else { | 462 | } else { | ||
| 389 | - | return []reflect.Value{v} | 463 | + | panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String()) |
| 390 | } | 464 | } | ||
| 391 | - | }) | 465 | + | default: |
| 392 | - | fn.Set(v) | 466 | + | panic("purego: unsupported return kind: " + outType.Kind().String()) |
| 467 | + | } | |||
| 468 | + | if len(args) > 0 { | |||
| 469 | + | // reuse args slice instead of allocating one when possible | |||
| 470 | + | args[0] = v | |||
| 471 | + | return args[:1] | |||
| 472 | + | } else { | |||
| 473 | + | return []reflect.Value{v} | |||
| 474 | + | } | |||
| 393 | } | 475 | } | ||
| 394 | 476 | ||||
| 395 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | 477 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | ||
| @@ -403,9 +485,29 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 403 | addInt(uintptr(v.Uint())) | 485 | addInt(uintptr(v.Uint())) | ||
| 404 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | 486 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
| 405 | addInt(uintptr(v.Int())) | 487 | addInt(uintptr(v.Int())) | ||
| 406 | - | case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: | 488 | + | case reflect.Pointer, reflect.UnsafePointer: |
| 407 | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | 489 | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | ||
| 408 | addInt(v.Pointer()) | 490 | addInt(v.Pointer()) | ||
| 491 | + | case reflect.Slice: | |||
| 492 | + | if v.Type().Elem().Kind() == reflect.String { | |||
| 493 | + | ss := v.Interface().([]string) | |||
| 494 | + | if ss == nil { | |||
| 495 | + | addInt(0) | |||
| 496 | + | } else { | |||
| 497 | + | res := make([]*byte, len(ss)+1) | |||
| 498 | + | for i, s := range ss { | |||
| 499 | + | ptr := strings.CString(s) | |||
| 500 | + | keepAlive = append(keepAlive, ptr) | |||
| 501 | + | res[i] = ptr | |||
| 502 | + | } | |||
| 503 | + | // NULL-terminated for C | |||
| 504 | + | res[len(ss)] = nil | |||
| 505 | + | keepAlive = append(keepAlive, res) | |||
| 506 | + | addInt(uintptr(unsafe.Pointer(&res[0]))) | |||
| 507 | + | } | |||
| 508 | + | } else { | |||
| 509 | + | addInt(v.Pointer()) | |||
| 510 | + | } | |||
| 409 | case reflect.Func: | 511 | case reflect.Func: | ||
| 410 | addInt(NewCallback(v.Interface())) | 512 | addInt(NewCallback(v.Interface())) | ||
| 411 | case reflect.Bool: | 513 | case reflect.Bool: | ||
| @@ -429,6 +531,17 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 429 | } else { | 531 | } else { | ||
| 430 | addFloat(uintptr(math.Float64bits(v.Float()))) | 532 | addFloat(uintptr(math.Float64bits(v.Float()))) | ||
| 431 | } | 533 | } | ||
| 534 | + | case reflect.Array: | |||
| 535 | + | // Task 7: Support fixed-size array arguments by passing a pointer to the array data. | |||
| 536 | + | if v.CanAddr() { | |||
| 537 | + | addInt(v.Addr().Pointer()) | |||
| 538 | + | } else { | |||
| 539 | + | // Array is not addressable (e.g. passed by value); make an addressable copy. | |||
| 540 | + | tmp := reflect.New(v.Type()).Elem() | |||
| 541 | + | tmp.Set(v) | |||
| 542 | + | keepAlive = append(keepAlive, tmp) | |||
| 543 | + | addInt(tmp.Addr().Pointer()) | |||
| 544 | + | } | |||
| 432 | case reflect.Struct: | 545 | case reflect.Struct: | ||
| 433 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | 546 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | ||
| 434 | default: | 547 | default: | ||
| @@ -482,7 +595,7 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||||
| 482 | switch f.Kind() { | 595 | switch f.Kind() { | ||
| 483 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | 596 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | ||
| 484 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | 597 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | ||
| 485 | - | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32, | 598 | + | reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32, |
| 486 | reflect.Bool: | 599 | reflect.Bool: | ||
| 487 | default: | 600 | default: | ||
| 488 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | 601 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | ||
| @@ -490,12 +603,12 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||||
| 490 | } | 603 | } | ||
| 491 | } | 604 | } | ||
| 492 | 605 | ||||
| 493 | - | func ensureStructSupportedForRegisterFunc() { | 606 | + | func ensureStructSupported() { |
| 494 | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | 607 | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | ||
| 495 | - | panic("purego: struct arguments are only supported on amd64 and arm64") | 608 | + | panic("purego: struct arguments/returns are only supported on amd64 and arm64") |
| 496 | } | 609 | } | ||
| 497 | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | 610 | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | ||
| 498 | - | panic("purego: struct arguments are only supported on darwin and linux") | 611 | + | panic("purego: struct arguments/returns are only supported on darwin and linux") |
| 499 | } | 612 | } | ||
| 500 | } | 613 | } | ||
| 501 | 614 | ||||