detached 89af0a6

fix(purego): remove syscall args pooling

commit
89af0a6b8a222d157d7acd7e89aa6852320f1f0f
author
brice · · 2026-05-26 16:26 UTC
parent
c5aa04a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +35 -12
M func.go +1 -8
@@ -10,7 +10,6 @@ import (
1010 "math"
1111 "reflect"
1212 "runtime"
13- "sync"
1413 "unsafe"
1514
1615 "github.com/bnema/purego/internal/strings"
@@ -22,10 +21,6 @@ const (
2221 align8ByteSize = 8 // 8-byte alignment boundary
2322 )
2423
25-var thePool = sync.Pool{New: func() any {
26- return new(syscall15Args)
27-}}
28-
2924 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
3025 // It panics if it can't find the name symbol.
3126 func RegisterLibFunc(fptr any, handle uintptr, name string) {
@@ -370,8 +365,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
370365 // callAndReturn performs the actual C function call and constructs the return value.
371366 // It is shared between the fast path (no keepAlive) and slow path (with keepAlive).
372367 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{}
375369
376370 if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
377371 syscall.Set(cfn, sysargs[:], floats[:], 0)
@@ -381,7 +375,6 @@ func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs
381375 syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
382376 runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
383377 } else {
384- *syscall = syscall15Args{}
385378 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
386379 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
387380 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 boundary21 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 floats378 // 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 (
1111 "path/filepath"
1212 "runtime"
1313 "strings"
14+ "sync"
1415 "testing"
1516 "unsafe"
1617
@@ -35,6 +36,38 @@ func getSystemLibrary() (string, error) {
3536 }
3637 }
3738
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+
3871 func TestRegisterFunc(t *testing.T) {
3972 library, err := getSystemLibrary()
4073 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 (
1919 var syscall15XABI0 uintptr
2020
2121 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{
2623 fn: fn,
2724 a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8,
2825 a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15,
@@ -19,10 +19,7 @@ import (
19 var syscall15XABI0 uintptr19 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,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close