detached 11272d2

purego: remove pooling of syscall (#452)

commit
11272d2804cdba6e4e4e6635e80f035a14f9ccbe
author
TotallyGamerJet · · 2026-05-24 12:47 UTC
committer
GitHub · · 2026-05-24 12:47 UTC
parent
f8849c8
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.11.0-alpha.3
5 files · +39 -16
M func.go +1 -7
@@ -10,7 +10,6 @@ import (
1010 "math"
1111 "reflect"
1212 "runtime"
13- "sync"
1413 "unsafe"
1514
1615 "github.com/ebitengine/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(syscallArgs)
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) {
@@ -323,13 +318,12 @@ func RegisterFunc(fptr any, cfn uintptr) {
323318 var syscall *syscallArgs
324319 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
325320 // Windows amd64, 386, and arm use syscall.SyscallN.
326- syscall = thePool.Get().(*syscallArgs)
321+ syscall = &syscallArgs{}
327322 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
328323 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
329324 } else {
330325 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
331326 }
332- defer thePool.Put(syscall)
333327 if ty.NumOut() == 0 {
334328 return nil
335329 }
@@ -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/ebitengine/purego/internal/strings"15 "github.com/ebitengine/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(syscallArgs)
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) {
@@ -323,13 +318,12 @@ func RegisterFunc(fptr any, cfn uintptr) {
323 var syscall *syscallArgs318 var syscall *syscallArgs
324 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {319 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
325 // Windows amd64, 386, and arm use syscall.SyscallN.320 // Windows amd64, 386, and arm use syscall.SyscallN.
326- syscall = thePool.Get().(*syscallArgs)321+ syscall = &syscallArgs{}
327 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)322 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
328 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support323 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
329 } else {324 } else {
330 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)325 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
331 }326 }
332- defer thePool.Put(syscall)
333 if ty.NumOut() == 0 {327 if ty.NumOut() == 0 {
334 return nil328 return nil
335 }329 }
M func_test.go +35 -0
@@ -11,6 +11,7 @@ import (
1111 "path/filepath"
1212 "runtime"
1313 "strings"
14+ "sync"
1415 "testing"
1516 "unsafe"
1617
@@ -35,6 +36,40 @@ 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+
56+ for i := 0; i < runtime.NumCPU(); i++ {
57+ wg.Add(1)
58+ go func(id int) {
59+ defer wg.Done()
60+ for j := 0; j < 400_000; j++ {
61+ ptr := alloc(5)
62+ if ptr == nil {
63+ continue
64+ }
65+ free(ptr)
66+ }
67+ }(i)
68+ }
69+
70+ wg.Wait()
71+}
72+
3873 func TestRegisterFunc(t *testing.T) {
3974 library, err := getSystemLibrary()
4075 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,40 @@ 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+
56+ for i := 0; i < runtime.NumCPU(); i++ {
57+ wg.Add(1)
58+ go func(id int) {
59+ defer wg.Done()
60+ for j := 0; j < 400_000; j++ {
61+ ptr := alloc(5)
62+ if ptr == nil {
63+ continue
64+ }
65+ free(ptr)
66+ }
67+ }(i)
68+ }
69+
70+ wg.Wait()
71+}
72+
38 func TestRegisterFunc(t *testing.T) {73 func TestRegisterFunc(t *testing.T) {
39 library, err := getSystemLibrary()74 library, err := getSystemLibrary()
40 if err != nil {75 if err != nil {
M syscall.go +1 -3
@@ -22,8 +22,7 @@ type syscallArgs struct {
2222 }
2323
2424 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := thePool.Get().(*syscallArgs)
26- *s = syscallArgs{
25+ s := &syscallArgs{
2726 fn: fn,
2827 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2928 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -79,6 +78,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
7978 var floats [maxArgs]uintptr
8079 copy(floats[:], tmp[:])
8180 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
82- defer thePool.Put(s)
8381 return s.a1, s.a2, s.a3
8482 }
@@ -22,8 +22,7 @@ type syscallArgs struct {
22 }22 }
23 23
24 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {24 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := thePool.Get().(*syscallArgs)25+ s := &syscallArgs{
26- *s = syscallArgs{
27 fn: fn,26 fn: fn,
28 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],27 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
29 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],28 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -79,6 +78,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
79 var floats [maxArgs]uintptr78 var floats [maxArgs]uintptr
80 copy(floats[:], tmp[:])79 copy(floats[:], tmp[:])
81 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)80 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
82- defer thePool.Put(s)
83 return s.a1, s.a2, s.a381 return s.a1, s.a2, s.a3
84 }82 }
M syscall_32bit.go +1 -3
@@ -22,8 +22,7 @@ type syscallArgs struct {
2222 }
2323
2424 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := thePool.Get().(*syscallArgs)
26- *s = syscallArgs{
25+ s := &syscallArgs{
2726 fn: fn,
2827 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2928 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -81,6 +80,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
8180 var floats [16]uintptr
8281 copy(floats[:], tmp[:16])
8382 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
84- defer thePool.Put(s)
8583 return s.a1, s.a2, s.a3
8684 }
@@ -22,8 +22,7 @@ type syscallArgs struct {
22 }22 }
23 23
24 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {24 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := thePool.Get().(*syscallArgs)25+ s := &syscallArgs{
26- *s = syscallArgs{
27 fn: fn,26 fn: fn,
28 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],27 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
29 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],28 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -81,6 +80,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
81 var floats [16]uintptr80 var floats [16]uintptr
82 copy(floats[:], tmp[:16])81 copy(floats[:], tmp[:16])
83 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)82 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
84- defer thePool.Put(s)
85 return s.a1, s.a2, s.a383 return s.a1, s.a2, s.a3
86 }84 }
M syscall_ppc64le.go +1 -3
@@ -20,8 +20,7 @@ type syscallArgs struct {
2020 }
2121
2222 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
23- s := thePool.Get().(*syscallArgs)
24- *s = syscallArgs{
23+ s := &syscallArgs{
2524 fn: fn,
2625 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2726 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -67,6 +66,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
6766 var floats [maxArgs]uintptr
6867 copy(floats[:], tmp[:])
6968 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
70- defer thePool.Put(s)
7169 return s.a1, s.a2, s.a3
7270 }
@@ -20,8 +20,7 @@ type syscallArgs struct {
20 }20 }
21 21
22 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {22 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
23- s := thePool.Get().(*syscallArgs)23+ s := &syscallArgs{
24- *s = syscallArgs{
25 fn: fn,24 fn: fn,
26 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],25 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
27 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],26 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -67,6 +66,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
67 var floats [maxArgs]uintptr66 var floats [maxArgs]uintptr
68 copy(floats[:], tmp[:])67 copy(floats[:], tmp[:])
69 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)68 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
70- defer thePool.Put(s)
71 return s.a1, s.a2, s.a369 return s.a1, s.a2, s.a3
72 }70 }

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