fix(purego): remove syscall args pooling
- commit
- 89af0a6b8a222d157d7acd7e89aa6852320f1f0f
- author
- brice · · 2026-05-26 16:26 UTC
- parent
- c5aa04a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +35 -12
M func.go +1 -8
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | "math" | |
| 11 | 11 | "reflect" | |
| 12 | 12 | "runtime" | |
| 13 | - | "sync" | |
| 14 | 13 | "unsafe" | |
| 15 | 14 | ||
| 16 | 15 | "github.com/bnema/purego/internal/strings" | |
| @@ -22,10 +21,6 @@ const ( | |||
| 22 | 21 | align8ByteSize = 8 // 8-byte alignment boundary | |
| 23 | 22 | ) | |
| 24 | 23 | ||
| 25 | - | var thePool = sync.Pool{New: func() any { | |
| 26 | - | return new(syscall15Args) | |
| 27 | - | }} | |
| 28 | - | ||
| 29 | 24 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | |
| 30 | 25 | // It panics if it can't find the name symbol. | |
| 31 | 26 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | |
| @@ -370,8 +365,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 370 | 365 | // callAndReturn performs the actual C function call and constructs the return value. | |
| 371 | 366 | // It is shared between the fast path (no keepAlive) and slow path (with keepAlive). | |
| 372 | 367 | 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) | |
| 368 | + | syscall := &syscall15Args{} | |
| 375 | 369 | ||
| 376 | 370 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | |
| 377 | 371 | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| @@ -381,7 +375,6 @@ func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs | |||
| 381 | 375 | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | |
| 382 | 376 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 383 | 377 | } else { | |
| 384 | - | *syscall = syscall15Args{} | |
| 385 | 378 | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | |
| 386 | 379 | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], | |
| 387 | 380 | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | "math" | 10 | "math" | ||
| 11 | "reflect" | 11 | "reflect" | ||
| 12 | "runtime" | 12 | "runtime" | ||
| 13 | - | "sync" | |||
| 14 | "unsafe" | 13 | "unsafe" | ||
| 15 | 14 | ||||
| 16 | "github.com/bnema/purego/internal/strings" | 15 | "github.com/bnema/purego/internal/strings" | ||
| @@ -22,10 +21,6 @@ const ( | |||||
| 22 | align8ByteSize = 8 // 8-byte alignment boundary | 21 | align8ByteSize = 8 // 8-byte alignment boundary | ||
| 23 | ) | 22 | ) | ||
| 24 | 23 | ||||
| 25 | - | var thePool = sync.Pool{New: func() any { | |||
| 26 | - | return new(syscall15Args) | |||
| 27 | - | }} | |||
| 28 | - | ||||
| 29 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | 24 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | ||
| 30 | // It panics if it can't find the name symbol. | 25 | // It panics if it can't find the name symbol. | ||
| 31 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | 26 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | ||
| @@ -370,8 +365,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 370 | // callAndReturn performs the actual C function call and constructs the return value. | 365 | // 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). | 366 | // 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 { | 367 | 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) | 368 | + | syscall := &syscall15Args{} |
| 374 | - | defer thePool.Put(syscall) | |||
| 375 | 369 | ||||
| 376 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | 370 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | ||
| 377 | syscall.Set(cfn, sysargs[:], floats[:], 0) | 371 | syscall.Set(cfn, sysargs[:], floats[:], 0) | ||
| @@ -381,7 +375,6 @@ func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs | |||||
| 381 | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | 375 | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | ||
| 382 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 376 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | ||
| 383 | } else { | 377 | } else { | ||
| 384 | - | *syscall = syscall15Args{} | |||
| 385 | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | 378 | // 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], | 379 | 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], | 380 | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | ||
M func_test.go +33 -0
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "path/filepath" | |
| 12 | 12 | "runtime" | |
| 13 | 13 | "strings" | |
| 14 | + | "sync" | |
| 14 | 15 | "testing" | |
| 15 | 16 | "unsafe" | |
| 16 | 17 | ||
| @@ -35,6 +36,38 @@ func getSystemLibrary() (string, error) { | |||
| 35 | 36 | } | |
| 36 | 37 | } | |
| 37 | 38 | ||
| 39 | + | func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |
| 40 | + | library, err := getSystemLibrary() | |
| 41 | + | if err != nil { | |
| 42 | + | t.Fatalf("couldn't get system library: %s", err) | |
| 43 | + | } | |
| 44 | + | libc, err := load.OpenLibrary(library) | |
| 45 | + | if err != nil { | |
| 46 | + | t.Fatalf("failed to dlopen: %s", err) | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | var alloc func(uint64) *byte | |
| 50 | + | var free func(*byte) | |
| 51 | + | purego.RegisterLibFunc(&alloc, libc, "malloc") | |
| 52 | + | purego.RegisterLibFunc(&free, libc, "free") | |
| 53 | + | ||
| 54 | + | var wg sync.WaitGroup | |
| 55 | + | for i := 0; i < runtime.NumCPU(); i++ { | |
| 56 | + | wg.Add(1) | |
| 57 | + | go func() { | |
| 58 | + | defer wg.Done() | |
| 59 | + | for j := 0; j < 400_000; j++ { | |
| 60 | + | ptr := alloc(5) | |
| 61 | + | if ptr == nil { | |
| 62 | + | continue | |
| 63 | + | } | |
| 64 | + | free(ptr) | |
| 65 | + | } | |
| 66 | + | }() | |
| 67 | + | } | |
| 68 | + | wg.Wait() | |
| 69 | + | } | |
| 70 | + | ||
| 38 | 71 | func TestRegisterFunc(t *testing.T) { | |
| 39 | 72 | library, err := getSystemLibrary() | |
| 40 | 73 | if err != nil { | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "path/filepath" | 11 | "path/filepath" | ||
| 12 | "runtime" | 12 | "runtime" | ||
| 13 | "strings" | 13 | "strings" | ||
| 14 | + | "sync" | |||
| 14 | "testing" | 15 | "testing" | ||
| 15 | "unsafe" | 16 | "unsafe" | ||
| 16 | 17 | ||||
| @@ -35,6 +36,38 @@ func getSystemLibrary() (string, error) { | |||||
| 35 | } | 36 | } | ||
| 36 | } | 37 | } | ||
| 37 | 38 | ||||
| 39 | + | func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |||
| 40 | + | library, err := getSystemLibrary() | |||
| 41 | + | if err != nil { | |||
| 42 | + | t.Fatalf("couldn't get system library: %s", err) | |||
| 43 | + | } | |||
| 44 | + | libc, err := load.OpenLibrary(library) | |||
| 45 | + | if err != nil { | |||
| 46 | + | t.Fatalf("failed to dlopen: %s", err) | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | var alloc func(uint64) *byte | |||
| 50 | + | var free func(*byte) | |||
| 51 | + | purego.RegisterLibFunc(&alloc, libc, "malloc") | |||
| 52 | + | purego.RegisterLibFunc(&free, libc, "free") | |||
| 53 | + | ||||
| 54 | + | var wg sync.WaitGroup | |||
| 55 | + | for i := 0; i < runtime.NumCPU(); i++ { | |||
| 56 | + | wg.Add(1) | |||
| 57 | + | go func() { | |||
| 58 | + | defer wg.Done() | |||
| 59 | + | for j := 0; j < 400_000; j++ { | |||
| 60 | + | ptr := alloc(5) | |||
| 61 | + | if ptr == nil { | |||
| 62 | + | continue | |||
| 63 | + | } | |||
| 64 | + | free(ptr) | |||
| 65 | + | } | |||
| 66 | + | }() | |||
| 67 | + | } | |||
| 68 | + | wg.Wait() | |||
| 69 | + | } | |||
| 70 | + | ||||
| 38 | func TestRegisterFunc(t *testing.T) { | 71 | func TestRegisterFunc(t *testing.T) { | ||
| 39 | library, err := getSystemLibrary() | 72 | library, err := getSystemLibrary() | ||
| 40 | if err != nil { | 73 | if err != nil { | ||
M syscall_unix.go +1 -4
| @@ -19,10 +19,7 @@ import ( | |||
| 19 | 19 | var syscall15XABI0 uintptr | |
| 20 | 20 | ||
| 21 | 21 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { | |
| 22 | - | args := thePool.Get().(*syscall15Args) | |
| 23 | - | defer thePool.Put(args) | |
| 24 | - | ||
| 25 | - | *args = syscall15Args{ | |
| 22 | + | args := &syscall15Args{ | |
| 26 | 23 | fn: fn, | |
| 27 | 24 | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, | |
| 28 | 25 | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, | |
| @@ -19,10 +19,7 @@ import ( | |||||
| 19 | var syscall15XABI0 uintptr | 19 | var syscall15XABI0 uintptr | ||
| 20 | 20 | ||||
| 21 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { | 21 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { | ||
| 22 | - | args := thePool.Get().(*syscall15Args) | 22 | + | args := &syscall15Args{ |
| 23 | - | defer thePool.Put(args) | |||
| 24 | - | ||||
| 25 | - | *args = syscall15Args{ | |||
| 26 | fn: fn, | 23 | fn: fn, | ||
| 27 | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, | 24 | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, | ||
| 28 | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, | 25 | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, | ||