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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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() { | |||
| 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 | 270 | // PostEvent sends an event. Arguments must match the event signature; build | |
| 265 | 271 | // string and array arguments with a Pinner and unpin it after this returns. | |
| 266 | 272 | // Calling it after destruction is safe and does nothing. | |
| @@ -268,12 +274,15 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) { | |||
| 268 | 274 | if r.gone { | |
| 269 | 275 | return | |
| 270 | 276 | } | |
| 277 | + | if len(args) > len(eventArgs) { | |
| 278 | + | panic("purego-libwayland: too many event arguments") | |
| 279 | + | } | |
| 271 | 280 | var p unsafe.Pointer | |
| 272 | 281 | if len(args) > 0 { | |
| 273 | - | p = unsafe.Pointer(&args[0]) | |
| 282 | + | copy(eventArgs[:], args) | |
| 283 | + | p = unsafe.Pointer(&eventArgs[0]) | |
| 274 | 284 | } | |
| 275 | 285 | wlResourcePostEventArr(r.c, opcode, p) | |
| 276 | - | runtime.KeepAlive(args) | |
| 277 | 286 | } | |
| 278 | 287 | ||
| 279 | 288 | // 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; build | 270 | // 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 | return | 275 | return | ||
| 270 | } | 276 | } | ||
| 277 | + | if len(args) > len(eventArgs) { | |||
| 278 | + | panic("purego-libwayland: too many event arguments") | |||
| 279 | + | } | |||
| 271 | var p unsafe.Pointer | 280 | 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 { | |||
| 90 | 90 | 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 | 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 | 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 | 105 | return int32(r) | |
| 98 | 106 | } | |
| 99 | 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 | 110 | return r | |
| 102 | 111 | } | |
| 103 | 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 | 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) } | |
| @@ -90,20 +90,34 @@ func load() error { | |||||
| 90 | return loadErr | 90 | 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 r | 110 | 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) } | |||