detached f0a709a

perf(server): post events and call libwayland without per-call allocations

commit
f0a709aaffee1dc6b87eeedb50c74cde659c35a0
author
brice · · 2026-09-27 16:32 UTC
committer
brice · · 2026-09-27 18:47 UTC
parent
d481123
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +31 -8
M server/display.go +11 -2
@@ -261,6 +261,12 @@ func (r *Resource) Destroy() {
261261 }
262262 }
263263
264+// eventArgs holds the wl_argument array of the event being posted.
265+// libwayland copies it before wl_resource_post_event_array returns.
266+// Display goroutine only; post_event_array does not call back into Go, so
267+// PostEvent is not re-entrant. No protocol event has more than 8 arguments.
268+var eventArgs [16]Arg
269+
264270 // PostEvent sends an event. Arguments must match the event signature; build
265271 // string and array arguments with a Pinner and unpin it after this returns.
266272 // Calling it after destruction is safe and does nothing.
@@ -268,12 +274,15 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
268274 if r.gone {
269275 return
270276 }
277+ if len(args) > len(eventArgs) {
278+ panic("purego-libwayland: too many event arguments")
279+ }
271280 var p unsafe.Pointer
272281 if len(args) > 0 {
273- p = unsafe.Pointer(&args[0])
282+ copy(eventArgs[:], args)
283+ p = unsafe.Pointer(&eventArgs[0])
274284 }
275285 wlResourcePostEventArr(r.c, opcode, p)
276- runtime.KeepAlive(args)
277286 }
278287
279288 // PostError sends a protocol error and disconnects the client.
@@ -261,6 +261,12 @@ func (r *Resource) Destroy() {
261 }261 }
262 }262 }
263 263
264+// eventArgs holds the wl_argument array of the event being posted.
265+// libwayland copies it before wl_resource_post_event_array returns.
266+// Display goroutine only; post_event_array does not call back into Go, so
267+// PostEvent is not re-entrant. No protocol event has more than 8 arguments.
268+var eventArgs [16]Arg
269+
264 // PostEvent sends an event. Arguments must match the event signature; build270 // PostEvent sends an event. Arguments must match the event signature; build
265 // string and array arguments with a Pinner and unpin it after this returns.271 // string and array arguments with a Pinner and unpin it after this returns.
266 // Calling it after destruction is safe and does nothing.272 // Calling it after destruction is safe and does nothing.
@@ -268,12 +274,15 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
268 if r.gone {274 if r.gone {
269 return275 return
270 }276 }
277+ if len(args) > len(eventArgs) {
278+ panic("purego-libwayland: too many event arguments")
279+ }
271 var p unsafe.Pointer280 var p unsafe.Pointer
272 if len(args) > 0 {281 if len(args) > 0 {
273- p = unsafe.Pointer(&args[0])282+ copy(eventArgs[:], args)
283+ p = unsafe.Pointer(&eventArgs[0])
274 }284 }
275 wlResourcePostEventArr(r.c, opcode, p)285 wlResourcePostEventArr(r.c, opcode, p)
276- runtime.KeepAlive(args)
277 }286 }
278 287
279 // PostError sends a protocol error and disconnects the client.288 // PostError sends a protocol error and disconnects the client.
M server/lib.go +20 -6
@@ -90,20 +90,34 @@ func load() error {
9090 return loadErr
9191 }
9292
93+// callArgs is scratch for SyscallN so hot calls do not allocate a variadic
94+// slice. Display goroutine only; all libwayland calls run there.
95+var callArgs [6]uintptr
96+
9397 // Integer and pointer-only entry points avoid RegisterLibFunc's reflective call path.
94-func wlDisplayFlushClients(display uintptr) { purego.SyscallN(symDisplayFlushClients, display) }
98+func wlDisplayFlushClients(display uintptr) {
99+ callArgs[0] = display
100+ purego.SyscallN(symDisplayFlushClients, callArgs[:1]...)
101+}
95102 func wlEventLoopDispatch(loop uintptr, timeout int32) int32 {
96- r, _, _ := purego.SyscallN(symEventLoopDispatch, loop, uintptr(timeout))
103+ callArgs[0], callArgs[1] = loop, uintptr(timeout)
104+ r, _, _ := purego.SyscallN(symEventLoopDispatch, callArgs[:2]...)
97105 return int32(r)
98106 }
99107 func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr {
100- r, _, _ := purego.SyscallN(symResourceCreate, client, iface, uintptr(version), uintptr(id))
108+ callArgs[0], callArgs[1], callArgs[2], callArgs[3] = client, iface, uintptr(version), uintptr(id)
109+ r, _, _ := purego.SyscallN(symResourceCreate, callArgs[:4]...)
101110 return r
102111 }
103112 func wlResourceSetDispatcher(resource, dispatcher, impl, data, destroy uintptr) {
104- purego.SyscallN(symResourceSetDispatcher, resource, dispatcher, impl, data, destroy)
113+ callArgs[0], callArgs[1], callArgs[2], callArgs[3], callArgs[4] = resource, dispatcher, impl, data, destroy
114+ purego.SyscallN(symResourceSetDispatcher, callArgs[:5]...)
105115 }
106116 func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer) {
107- purego.SyscallN(symResourcePostEventArr, resource, uintptr(opcode), uintptr(args))
117+ callArgs[0], callArgs[1], callArgs[2] = resource, uintptr(opcode), uintptr(args)
118+ purego.SyscallN(symResourcePostEventArr, callArgs[:3]...)
119+}
120+func wlResourceDestroy(resource uintptr) {
121+ callArgs[0] = resource
122+ purego.SyscallN(symResourceDestroy, callArgs[:1]...)
108123 }
109-func wlResourceDestroy(resource uintptr) { purego.SyscallN(symResourceDestroy, resource) }
@@ -90,20 +90,34 @@ func load() error {
90 return loadErr90 return loadErr
91 }91 }
92 92
93+// callArgs is scratch for SyscallN so hot calls do not allocate a variadic
94+// slice. Display goroutine only; all libwayland calls run there.
95+var callArgs [6]uintptr
96+
93 // Integer and pointer-only entry points avoid RegisterLibFunc's reflective call path.97 // Integer and pointer-only entry points avoid RegisterLibFunc's reflective call path.
94-func wlDisplayFlushClients(display uintptr) { purego.SyscallN(symDisplayFlushClients, display) }98+func wlDisplayFlushClients(display uintptr) {
99+ callArgs[0] = display
100+ purego.SyscallN(symDisplayFlushClients, callArgs[:1]...)
101+}
95 func wlEventLoopDispatch(loop uintptr, timeout int32) int32 {102 func wlEventLoopDispatch(loop uintptr, timeout int32) int32 {
96- r, _, _ := purego.SyscallN(symEventLoopDispatch, loop, uintptr(timeout))103+ callArgs[0], callArgs[1] = loop, uintptr(timeout)
104+ r, _, _ := purego.SyscallN(symEventLoopDispatch, callArgs[:2]...)
97 return int32(r)105 return int32(r)
98 }106 }
99 func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr {107 func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr {
100- r, _, _ := purego.SyscallN(symResourceCreate, client, iface, uintptr(version), uintptr(id))108+ callArgs[0], callArgs[1], callArgs[2], callArgs[3] = client, iface, uintptr(version), uintptr(id)
109+ r, _, _ := purego.SyscallN(symResourceCreate, callArgs[:4]...)
101 return r110 return r
102 }111 }
103 func wlResourceSetDispatcher(resource, dispatcher, impl, data, destroy uintptr) {112 func wlResourceSetDispatcher(resource, dispatcher, impl, data, destroy uintptr) {
104- purego.SyscallN(symResourceSetDispatcher, resource, dispatcher, impl, data, destroy)113+ callArgs[0], callArgs[1], callArgs[2], callArgs[3], callArgs[4] = resource, dispatcher, impl, data, destroy
114+ purego.SyscallN(symResourceSetDispatcher, callArgs[:5]...)
105 }115 }
106 func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer) {116 func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer) {
107- purego.SyscallN(symResourcePostEventArr, resource, uintptr(opcode), uintptr(args))117+ callArgs[0], callArgs[1], callArgs[2] = resource, uintptr(opcode), uintptr(args)
118+ purego.SyscallN(symResourcePostEventArr, callArgs[:3]...)
119+}
120+func wlResourceDestroy(resource uintptr) {
121+ callArgs[0] = resource
122+ purego.SyscallN(symResourceDestroy, callArgs[:1]...)
108 }123 }
109-func wlResourceDestroy(resource uintptr) { purego.SyscallN(symResourceDestroy, resource) }

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 14

↑↓ moveenter openesc close