detached d339eb0

purego: restore sync.Pool (#457)

Revert the sync.Pool removal #452 and copy syscall.a1 into a local variable
before taking its address, so the reflect.Value points to an independent location
that is not affected by pool recycling.

Updates #451
commit
d339eb0fd4d3c57f9b641f4781615650183c7043
author
Alexander Yastrebov · · 2026-06-04 01:59 UTC
committer
GitHub · · 2026-06-04 01:59 UTC
parent
1512f32
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.11.0-alpha.4
4 files · +20 -5
M func.go +11 -2
@@ -10,6 +10,7 @@ import (
1010 "math"
1111 "reflect"
1212 "runtime"
13+ "sync"
1314 "unsafe"
1415
1516 "github.com/ebitengine/purego/internal/strings"
@@ -21,6 +22,10 @@ const (
2122 align8ByteSize = 8 // 8-byte alignment boundary
2223 )
2324
25+var thePool = sync.Pool{New: func() any {
26+ return new(syscallArgs)
27+}}
28+
2429 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
2530 // It panics if it can't find the name symbol.
2631 func RegisterLibFunc(fptr any, handle uintptr, name string) {
@@ -318,12 +323,13 @@ func RegisterFunc(fptr any, cfn uintptr) {
318323 var syscall *syscallArgs
319324 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
320325 // Windows amd64, 386, and arm use syscall.SyscallN.
321- syscall = &syscallArgs{}
326+ syscall = thePool.Get().(*syscallArgs)
322327 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
323328 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
324329 } else {
325330 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
326331 }
332+ defer thePool.Put(syscall)
327333 if ty.NumOut() == 0 {
328334 return nil
329335 }
@@ -340,7 +346,10 @@ func RegisterFunc(fptr any, cfn uintptr) {
340346 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
341347 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
342348 case reflect.Pointer:
343- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
349+ // Copy syscall.a1 into a local variable to prevent v
350+ // from holding a pointer to the pooled syscallArgs field.
351+ a1 := syscall.a1
352+ v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem()
344353 case reflect.Func:
345354 // wrap this C function in a nicely typed Go function
346355 v = reflect.New(outType)
@@ -10,6 +10,7 @@ import (
10 "math"10 "math"
11 "reflect"11 "reflect"
12 "runtime"12 "runtime"
13+ "sync"
13 "unsafe"14 "unsafe"
14 15
15 "github.com/ebitengine/purego/internal/strings"16 "github.com/ebitengine/purego/internal/strings"
@@ -21,6 +22,10 @@ const (
21 align8ByteSize = 8 // 8-byte alignment boundary22 align8ByteSize = 8 // 8-byte alignment boundary
22 )23 )
23 24
25+var thePool = sync.Pool{New: func() any {
26+ return new(syscallArgs)
27+}}
28+
24 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).29 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
25 // It panics if it can't find the name symbol.30 // It panics if it can't find the name symbol.
26 func RegisterLibFunc(fptr any, handle uintptr, name string) {31 func RegisterLibFunc(fptr any, handle uintptr, name string) {
@@ -318,12 +323,13 @@ func RegisterFunc(fptr any, cfn uintptr) {
318 var syscall *syscallArgs323 var syscall *syscallArgs
319 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {324 if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
320 // Windows amd64, 386, and arm use syscall.SyscallN.325 // Windows amd64, 386, and arm use syscall.SyscallN.
321- syscall = &syscallArgs{}326+ syscall = thePool.Get().(*syscallArgs)
322 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)327 syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
323 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support328 syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
324 } else {329 } else {
325 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)330 syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
326 }331 }
332+ defer thePool.Put(syscall)
327 if ty.NumOut() == 0 {333 if ty.NumOut() == 0 {
328 return nil334 return nil
329 }335 }
@@ -340,7 +346,10 @@ func RegisterFunc(fptr any, cfn uintptr) {
340 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer346 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
341 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))347 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
342 case reflect.Pointer:348 case reflect.Pointer:
343- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()349+ // Copy syscall.a1 into a local variable to prevent v
350+ // from holding a pointer to the pooled syscallArgs field.
351+ a1 := syscall.a1
352+ v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem()
344 case reflect.Func:353 case reflect.Func:
345 // wrap this C function in a nicely typed Go function354 // wrap this C function in a nicely typed Go function
346 v = reflect.New(outType)355 v = reflect.New(outType)
M syscall.go +3 -1
@@ -22,7 +22,8 @@ type syscallArgs struct {
2222 }
2323
2424 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := &syscallArgs{
25+ s := thePool.Get().(*syscallArgs)
26+ *s = syscallArgs{
2627 fn: fn,
2728 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2829 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -78,5 +79,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
7879 var floats [maxArgs]uintptr
7980 copy(floats[:], tmp[:])
8081 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
82+ defer thePool.Put(s)
8183 return s.a1, s.a2, s.a3
8284 }
@@ -22,7 +22,8 @@ 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 := &syscallArgs{25+ s := thePool.Get().(*syscallArgs)
26+ *s = syscallArgs{
26 fn: fn,27 fn: fn,
27 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],28 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
28 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],29 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -78,5 +79,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
78 var floats [maxArgs]uintptr79 var floats [maxArgs]uintptr
79 copy(floats[:], tmp[:])80 copy(floats[:], tmp[:])
80 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)81 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
82+ defer thePool.Put(s)
81 return s.a1, s.a2, s.a383 return s.a1, s.a2, s.a3
82 }84 }
M syscall_32bit.go +3 -1
@@ -22,7 +22,8 @@ type syscallArgs struct {
2222 }
2323
2424 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
25- s := &syscallArgs{
25+ s := thePool.Get().(*syscallArgs)
26+ *s = syscallArgs{
2627 fn: fn,
2728 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2829 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -80,5 +81,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
8081 var floats [16]uintptr
8182 copy(floats[:], tmp[:16])
8283 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
84+ defer thePool.Put(s)
8385 return s.a1, s.a2, s.a3
8486 }
@@ -22,7 +22,8 @@ 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 := &syscallArgs{25+ s := thePool.Get().(*syscallArgs)
26+ *s = syscallArgs{
26 fn: fn,27 fn: fn,
27 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],28 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
28 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],29 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -80,5 +81,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
80 var floats [16]uintptr81 var floats [16]uintptr
81 copy(floats[:], tmp[:16])82 copy(floats[:], tmp[:16])
82 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)83 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
84+ defer thePool.Put(s)
83 return s.a1, s.a2, s.a385 return s.a1, s.a2, s.a3
84 }86 }
M syscall_ppc64le.go +3 -1
@@ -20,7 +20,8 @@ type syscallArgs struct {
2020 }
2121
2222 func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
23- s := &syscallArgs{
23+ s := thePool.Get().(*syscallArgs)
24+ *s = syscallArgs{
2425 fn: fn,
2526 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
2627 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -66,5 +67,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
6667 var floats [maxArgs]uintptr
6768 copy(floats[:], tmp[:])
6869 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
70+ defer thePool.Put(s)
6971 return s.a1, s.a2, s.a3
7072 }
@@ -20,7 +20,8 @@ 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 := &syscallArgs{23+ s := thePool.Get().(*syscallArgs)
24+ *s = syscallArgs{
24 fn: fn,25 fn: fn,
25 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],26 a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
26 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],27 a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
@@ -66,5 +67,6 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
66 var floats [maxArgs]uintptr67 var floats [maxArgs]uintptr
67 copy(floats[:], tmp[:])68 copy(floats[:], tmp[:])
68 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)69 s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
70+ defer thePool.Put(s)
69 return s.a1, s.a2, s.a371 return s.a1, s.a2, s.a3
70 }72 }

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