detached d481123

perf(server): cache request metadata and call hot libwayland symbols directly

commit
d4811235c7d6fc7988831693622a7fa4b986bbdd
author
brice · · 2026-09-27 16:29 UTC
committer
brice · · 2026-09-27 18:47 UTC
parent
d14b9a5
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +201 -56
M server/display.go +21 -37
@@ -12,7 +12,8 @@ import (
1212 "golang.org/x/sys/unix"
1313 )
1414
15-// Handler receives every request sent to a resource.
15+// Handler receives every request sent to a resource. args borrows libwayland's
16+// argument array and must not be retained after the handler returns.
1617 type Handler func(r *Resource, opcode uint32, args []Arg)
1718
1819 // BindFunc runs when a client binds a global.
@@ -24,6 +25,9 @@ type Client struct{ c uintptr }
2425 // Resource is a server-side wl_resource.
2526 type Resource struct {
2627 c uintptr
28+ id uint32
29+ version int32
30+ client uintptr
2731 iface *Interface
2832 handler Handler
2933 OnDestroy func()
@@ -222,7 +226,7 @@ func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Han
222226 if rc == 0 {
223227 return nil, fmt.Errorf("purego-libwayland: wl_resource_create(%s) failed", iface.Name)
224228 }
225- r := &Resource{c: rc, iface: iface, handler: h}
229+ r := &Resource{c: rc, id: id, version: version, client: c.c, iface: iface, handler: h}
226230 live.resources[rc] = r
227231 wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy)
228232 return r, nil
@@ -233,19 +237,19 @@ func (r *Resource) ID() uint32 {
233237 if r.gone {
234238 return 0
235239 }
236- return wlResourceGetID(r.c)
240+ return r.id
237241 }
238242 func (r *Resource) Version() int32 {
239243 if r.gone {
240244 return 0
241245 }
242- return wlResourceGetVersion(r.c)
246+ return r.version
243247 }
244248 func (r *Resource) Client() Client {
245249 if r.gone {
246250 return Client{}
247251 }
248- return Client{wlResourceGetClient(r.c)}
252+ return Client{r.client}
249253 }
250254 func (r *Resource) Iface() *Interface { return r.iface }
251255
@@ -268,11 +272,6 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
268272 if len(args) > 0 {
269273 p = unsafe.Pointer(&args[0])
270274 }
271- var pin runtime.Pinner
272- if p != nil {
273- pin.Pin(p)
274- defer pin.Unpin()
275- }
276275 wlResourcePostEventArr(r.c, opcode, p)
277276 runtime.KeepAlive(args)
278277 }
@@ -311,17 +310,19 @@ var marker uintptr
311310
312311 // --- C callbacks (display goroutine) ---
313312
314-func dispatch(_ uintptr, target uintptr, opcode uint32, msg unsafe.Pointer, args unsafe.Pointer) int32 {
313+func dispatch(_ uintptr, target uintptr, opcode uint32, _ unsafe.Pointer, args unsafe.Pointer) int32 {
315314 r := live.resources[target]
316- sig := msgSignature(msg)
317- if r == nil || r.handler == nil {
318- closeRequestFDs(sig, args)
315+ if r == nil || opcode >= uint32(len(r.iface.Requests)) {
316+ return 0
317+ }
318+ m := &r.iface.Requests[opcode]
319+ if r.handler == nil {
320+ closeRequestFDs(m.fdPositions, args)
319321 return 0
320322 }
321- n := argCount(sig)
322323 var a []Arg
323- if n > 0 {
324- a = append([]Arg(nil), unsafe.Slice((*Arg)(args), n)...)
324+ if m.argc > 0 {
325+ a = unsafe.Slice((*Arg)(args), m.argc)
325326 }
326327 r.handler(r, opcode, a)
327328 return 0
@@ -352,27 +353,10 @@ func bind(client uintptr, data uintptr, version uint32, id uint32) {
352353 }
353354 }
354355
355-// msgSignature reads wl_message.signature.
356-func msgSignature(msg unsafe.Pointer) string {
357- p := *(*unsafe.Pointer)(unsafe.Add(msg, 8))
358- n := 0
359- for *(*byte)(unsafe.Add(p, n)) != 0 {
360- n++
361- }
362- return string(unsafe.Slice((*byte)(p), n))
363-}
364-
365356 // closeRequestFDs takes ownership of fd arguments when there is no handler.
366-func closeRequestFDs(sig string, args unsafe.Pointer) {
367- i := 0
368- for _, ch := range sig {
369- if ch == '?' || (ch >= '0' && ch <= '9') {
370- continue
371- }
372- if ch == 'h' {
373- _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
374- }
375- i++
357+func closeRequestFDs(positions []int, args unsafe.Pointer) {
358+ for _, i := range positions {
359+ _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
376360 }
377361 }
378362
@@ -12,7 +12,8 @@ import (
12 "golang.org/x/sys/unix"12 "golang.org/x/sys/unix"
13 )13 )
14 14
15-// Handler receives every request sent to a resource.15+// Handler receives every request sent to a resource. args borrows libwayland's
16+// argument array and must not be retained after the handler returns.
16 type Handler func(r *Resource, opcode uint32, args []Arg)17 type Handler func(r *Resource, opcode uint32, args []Arg)
17 18
18 // BindFunc runs when a client binds a global.19 // BindFunc runs when a client binds a global.
@@ -24,6 +25,9 @@ type Client struct{ c uintptr }
24 // Resource is a server-side wl_resource.25 // Resource is a server-side wl_resource.
25 type Resource struct {26 type Resource struct {
26 c uintptr27 c uintptr
28+ id uint32
29+ version int32
30+ client uintptr
27 iface *Interface31 iface *Interface
28 handler Handler32 handler Handler
29 OnDestroy func()33 OnDestroy func()
@@ -222,7 +226,7 @@ func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Han
222 if rc == 0 {226 if rc == 0 {
223 return nil, fmt.Errorf("purego-libwayland: wl_resource_create(%s) failed", iface.Name)227 return nil, fmt.Errorf("purego-libwayland: wl_resource_create(%s) failed", iface.Name)
224 }228 }
225- r := &Resource{c: rc, iface: iface, handler: h}229+ r := &Resource{c: rc, id: id, version: version, client: c.c, iface: iface, handler: h}
226 live.resources[rc] = r230 live.resources[rc] = r
227 wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy)231 wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy)
228 return r, nil232 return r, nil
@@ -233,19 +237,19 @@ func (r *Resource) ID() uint32 {
233 if r.gone {237 if r.gone {
234 return 0238 return 0
235 }239 }
236- return wlResourceGetID(r.c)240+ return r.id
237 }241 }
238 func (r *Resource) Version() int32 {242 func (r *Resource) Version() int32 {
239 if r.gone {243 if r.gone {
240 return 0244 return 0
241 }245 }
242- return wlResourceGetVersion(r.c)246+ return r.version
243 }247 }
244 func (r *Resource) Client() Client {248 func (r *Resource) Client() Client {
245 if r.gone {249 if r.gone {
246 return Client{}250 return Client{}
247 }251 }
248- return Client{wlResourceGetClient(r.c)}252+ return Client{r.client}
249 }253 }
250 func (r *Resource) Iface() *Interface { return r.iface }254 func (r *Resource) Iface() *Interface { return r.iface }
251 255
@@ -268,11 +272,6 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
268 if len(args) > 0 {272 if len(args) > 0 {
269 p = unsafe.Pointer(&args[0])273 p = unsafe.Pointer(&args[0])
270 }274 }
271- var pin runtime.Pinner
272- if p != nil {
273- pin.Pin(p)
274- defer pin.Unpin()
275- }
276 wlResourcePostEventArr(r.c, opcode, p)275 wlResourcePostEventArr(r.c, opcode, p)
277 runtime.KeepAlive(args)276 runtime.KeepAlive(args)
278 }277 }
@@ -311,17 +310,19 @@ var marker uintptr
311 310
312 // --- C callbacks (display goroutine) ---311 // --- C callbacks (display goroutine) ---
313 312
314-func dispatch(_ uintptr, target uintptr, opcode uint32, msg unsafe.Pointer, args unsafe.Pointer) int32 {313+func dispatch(_ uintptr, target uintptr, opcode uint32, _ unsafe.Pointer, args unsafe.Pointer) int32 {
315 r := live.resources[target]314 r := live.resources[target]
316- sig := msgSignature(msg)315+ if r == nil || opcode >= uint32(len(r.iface.Requests)) {
317- if r == nil || r.handler == nil {316+ return 0
318- closeRequestFDs(sig, args)317+ }
318+ m := &r.iface.Requests[opcode]
319+ if r.handler == nil {
320+ closeRequestFDs(m.fdPositions, args)
319 return 0321 return 0
320 }322 }
321- n := argCount(sig)
322 var a []Arg323 var a []Arg
323- if n > 0 {324+ if m.argc > 0 {
324- a = append([]Arg(nil), unsafe.Slice((*Arg)(args), n)...)325+ a = unsafe.Slice((*Arg)(args), m.argc)
325 }326 }
326 r.handler(r, opcode, a)327 r.handler(r, opcode, a)
327 return 0328 return 0
@@ -352,27 +353,10 @@ func bind(client uintptr, data uintptr, version uint32, id uint32) {
352 }353 }
353 }354 }
354 355
355-// msgSignature reads wl_message.signature.
356-func msgSignature(msg unsafe.Pointer) string {
357- p := *(*unsafe.Pointer)(unsafe.Add(msg, 8))
358- n := 0
359- for *(*byte)(unsafe.Add(p, n)) != 0 {
360- n++
361- }
362- return string(unsafe.Slice((*byte)(p), n))
363-}
364-
365 // closeRequestFDs takes ownership of fd arguments when there is no handler.356 // closeRequestFDs takes ownership of fd arguments when there is no handler.
366-func closeRequestFDs(sig string, args unsafe.Pointer) {357+func closeRequestFDs(positions []int, args unsafe.Pointer) {
367- i := 0358+ for _, i := range positions {
368- for _, ch := range sig {359+ _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
369- if ch == '?' || (ch >= '0' && ch <= '9') {
370- continue
371- }
372- if ch == 'h' {
373- _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
374- }
375- i++
376 }360 }
377 }361 }
378 362
M server/display_test.go +5 -1
@@ -27,7 +27,11 @@ func TestNoHandlerClosesFDs(t *testing.T) {
2727 }
2828 i++
2929 }
30- closeRequestFDs(sig, unsafe.Pointer(&args[0]))
30+ iface := &Interface{Name: "test_fd", Version: 1, Requests: []Message{{Name: "fd", Signature: sig}}}
31+ if err := NewInterfaces(iface); err != nil {
32+ t.Fatal(err)
33+ }
34+ closeRequestFDs(iface.Requests[0].fdPositions, unsafe.Pointer(&args[0]))
3135 if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != unix.EBADF {
3236 t.Fatalf("signature %s: fd not closed: %v", sig, err)
3337 }
@@ -27,7 +27,11 @@ func TestNoHandlerClosesFDs(t *testing.T) {
27 }27 }
28 i++28 i++
29 }29 }
30- closeRequestFDs(sig, unsafe.Pointer(&args[0]))30+ iface := &Interface{Name: "test_fd", Version: 1, Requests: []Message{{Name: "fd", Signature: sig}}}
31+ if err := NewInterfaces(iface); err != nil {
32+ t.Fatal(err)
33+ }
34+ closeRequestFDs(iface.Requests[0].fdPositions, unsafe.Pointer(&args[0]))
31 if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != unix.EBADF {35 if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != unix.EBADF {
32 t.Fatalf("signature %s: fd not closed: %v", sig, err)36 t.Fatalf("signature %s: fd not closed: %v", sig, err)
33 }37 }
A server/hotpath_test.go +119 -0
@@ -0,0 +1,119 @@
1+package server
2+
3+import (
4+ "context"
5+ "testing"
6+ "unsafe"
7+
8+ "github.com/bnema/purego"
9+ "golang.org/x/sys/unix"
10+)
11+
12+// hotResource creates a real wl_client/resource on the display thread without
13+// needing a separate client event loop.
14+func hotResource(t testing.TB, d *Display, iface *Interface, h Handler) *Resource {
15+ t.Helper()
16+ lib, err := purego.Dlopen("libwayland-server.so.0", purego.RTLD_NOW)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ create, err := purego.Dlsym(lib, "wl_client_create")
21+ if err != nil {
22+ t.Fatal(err)
23+ }
24+ fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
25+ if err != nil {
26+ t.Fatal(err)
27+ }
28+ t.Cleanup(func() { _ = unix.Close(fds[1]) })
29+ var r *Resource
30+ if !d.Do(func() {
31+ client, _, _ := purego.SyscallN(create, d.c, uintptr(fds[0]))
32+ if client == 0 {
33+ _ = unix.Close(fds[0])
34+ return
35+ }
36+ r, err = (Client{client}).CreateResource(iface, 1, 2, h)
37+ }) {
38+ t.Fatal("display stopped")
39+ }
40+ if err != nil || r == nil {
41+ t.Fatalf("create resource: %v", err)
42+ }
43+ return r
44+}
45+
46+func hotDisplay(t testing.TB, iface *Interface, h Handler) (*Display, *Resource) {
47+ t.Helper()
48+ if err := NewInterfaces(iface); err != nil {
49+ t.Fatal(err)
50+ }
51+ d, err := NewDisplay()
52+ if err != nil {
53+ t.Fatal(err)
54+ }
55+ ctx, cancel := context.WithCancel(context.Background())
56+ done := make(chan error, 1)
57+ go func() { done <- d.Run(ctx) }()
58+ t.Cleanup(func() {
59+ cancel()
60+ if err := <-done; err != nil {
61+ t.Error(err)
62+ }
63+ })
64+ return d, hotResource(t, d, iface, h)
65+}
66+
67+func requestMessage(iface *Interface) unsafe.Pointer {
68+ return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&tables.mem[0]), iface.c-tables.base+16))
69+}
70+
71+var hotSink uint32
72+
73+func hotHandler(_ *Resource, _ uint32, args []Arg) { hotSink = args[0].Uint() }
74+
75+func TestHotPathAllocs(t *testing.T) {
76+ iface := &Interface{Name: "hot_path_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}}
77+ d, r := hotDisplay(t, iface, hotHandler)
78+ var post, dispatchAllocs float64
79+ if !d.Do(func() {
80+ post = testing.AllocsPerRun(100, func() { r.PostEvent(0, Uint(1), Uint(2), Uint(3)) })
81+ a := [3]Arg{Uint(1), Uint(2), Uint(3)}
82+ dispatchAllocs = testing.AllocsPerRun(100, func() { dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0])) })
83+ }) {
84+ t.Fatal("display stopped")
85+ }
86+ if post != 0 || dispatchAllocs != 0 {
87+ t.Fatalf("allocs: PostEvent=%g dispatch=%g, want zero", post, dispatchAllocs)
88+ }
89+}
90+
91+func BenchmarkDispatch(b *testing.B) {
92+ iface := &Interface{Name: "hot_dispatch_bench", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}}
93+ d, r := hotDisplay(b, iface, hotHandler)
94+ a := [3]Arg{Uint(1), Uint(2), Uint(3)}
95+ if !d.Do(func() {
96+ b.ReportAllocs()
97+ b.ResetTimer()
98+ for i := 0; i < b.N; i++ {
99+ dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0]))
100+ }
101+ b.StopTimer()
102+ }) {
103+ b.Fatal("display stopped")
104+ }
105+}
106+func BenchmarkPostEvent(b *testing.B) {
107+ iface := &Interface{Name: "hot_event_bench", Version: 1, Events: []Message{{Name: "event", Signature: "uuu"}}}
108+ d, r := hotDisplay(b, iface, nil)
109+ if !d.Do(func() {
110+ b.ReportAllocs()
111+ b.ResetTimer()
112+ for i := 0; i < b.N; i++ {
113+ r.PostEvent(0, Uint(1), Uint(2), Uint(3))
114+ }
115+ b.StopTimer()
116+ }) {
117+ b.Fatal("display stopped")
118+ }
119+}
@@ -0,0 +1,119 @@
1+package server
2+
3+import (
4+ "context"
5+ "testing"
6+ "unsafe"
7+
8+ "github.com/bnema/purego"
9+ "golang.org/x/sys/unix"
10+)
11+
12+// hotResource creates a real wl_client/resource on the display thread without
13+// needing a separate client event loop.
14+func hotResource(t testing.TB, d *Display, iface *Interface, h Handler) *Resource {
15+ t.Helper()
16+ lib, err := purego.Dlopen("libwayland-server.so.0", purego.RTLD_NOW)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ create, err := purego.Dlsym(lib, "wl_client_create")
21+ if err != nil {
22+ t.Fatal(err)
23+ }
24+ fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
25+ if err != nil {
26+ t.Fatal(err)
27+ }
28+ t.Cleanup(func() { _ = unix.Close(fds[1]) })
29+ var r *Resource
30+ if !d.Do(func() {
31+ client, _, _ := purego.SyscallN(create, d.c, uintptr(fds[0]))
32+ if client == 0 {
33+ _ = unix.Close(fds[0])
34+ return
35+ }
36+ r, err = (Client{client}).CreateResource(iface, 1, 2, h)
37+ }) {
38+ t.Fatal("display stopped")
39+ }
40+ if err != nil || r == nil {
41+ t.Fatalf("create resource: %v", err)
42+ }
43+ return r
44+}
45+
46+func hotDisplay(t testing.TB, iface *Interface, h Handler) (*Display, *Resource) {
47+ t.Helper()
48+ if err := NewInterfaces(iface); err != nil {
49+ t.Fatal(err)
50+ }
51+ d, err := NewDisplay()
52+ if err != nil {
53+ t.Fatal(err)
54+ }
55+ ctx, cancel := context.WithCancel(context.Background())
56+ done := make(chan error, 1)
57+ go func() { done <- d.Run(ctx) }()
58+ t.Cleanup(func() {
59+ cancel()
60+ if err := <-done; err != nil {
61+ t.Error(err)
62+ }
63+ })
64+ return d, hotResource(t, d, iface, h)
65+}
66+
67+func requestMessage(iface *Interface) unsafe.Pointer {
68+ return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&tables.mem[0]), iface.c-tables.base+16))
69+}
70+
71+var hotSink uint32
72+
73+func hotHandler(_ *Resource, _ uint32, args []Arg) { hotSink = args[0].Uint() }
74+
75+func TestHotPathAllocs(t *testing.T) {
76+ iface := &Interface{Name: "hot_path_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}}
77+ d, r := hotDisplay(t, iface, hotHandler)
78+ var post, dispatchAllocs float64
79+ if !d.Do(func() {
80+ post = testing.AllocsPerRun(100, func() { r.PostEvent(0, Uint(1), Uint(2), Uint(3)) })
81+ a := [3]Arg{Uint(1), Uint(2), Uint(3)}
82+ dispatchAllocs = testing.AllocsPerRun(100, func() { dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0])) })
83+ }) {
84+ t.Fatal("display stopped")
85+ }
86+ if post != 0 || dispatchAllocs != 0 {
87+ t.Fatalf("allocs: PostEvent=%g dispatch=%g, want zero", post, dispatchAllocs)
88+ }
89+}
90+
91+func BenchmarkDispatch(b *testing.B) {
92+ iface := &Interface{Name: "hot_dispatch_bench", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}}
93+ d, r := hotDisplay(b, iface, hotHandler)
94+ a := [3]Arg{Uint(1), Uint(2), Uint(3)}
95+ if !d.Do(func() {
96+ b.ReportAllocs()
97+ b.ResetTimer()
98+ for i := 0; i < b.N; i++ {
99+ dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0]))
100+ }
101+ b.StopTimer()
102+ }) {
103+ b.Fatal("display stopped")
104+ }
105+}
106+func BenchmarkPostEvent(b *testing.B) {
107+ iface := &Interface{Name: "hot_event_bench", Version: 1, Events: []Message{{Name: "event", Signature: "uuu"}}}
108+ d, r := hotDisplay(b, iface, nil)
109+ if !d.Do(func() {
110+ b.ReportAllocs()
111+ b.ResetTimer()
112+ for i := 0; i < b.N; i++ {
113+ r.PostEvent(0, Uint(1), Uint(2), Uint(3))
114+ }
115+ b.StopTimer()
116+ }) {
117+ b.Fatal("display stopped")
118+ }
119+}
M server/interface.go +18 -0
@@ -10,6 +10,9 @@ type Message struct {
1010 Name string
1111 Signature string
1212 Types []*Interface
13+
14+ argc int
15+ fdPositions []int
1316 }
1417
1518 // Interface describes a protocol interface. Build its C table once with
@@ -55,6 +58,21 @@ func NewInterfaces(ifaces ...*Interface) error {
5558 }
5659 // Pass 2: fill messages and headers.
5760 for i, it := range ifaces {
61+ for j := range it.Requests {
62+ m := &it.Requests[j]
63+ m.argc = argCount(m.Signature)
64+ m.fdPositions = nil
65+ pos := 0
66+ for _, ch := range m.Signature {
67+ if ch == '?' || (ch >= '0' && ch <= '9') {
68+ continue
69+ }
70+ if ch == 'h' {
71+ m.fdPositions = append(m.fdPositions, pos)
72+ }
73+ pos++
74+ }
75+ }
5876 o := offs[i]
5977 a.putPtr(o, a.cstring(it.Name))
6078 a.putI32(o+8, it.Version)
@@ -10,6 +10,9 @@ type Message struct {
10 Name string10 Name string
11 Signature string11 Signature string
12 Types []*Interface12 Types []*Interface
13+
14+ argc int
15+ fdPositions []int
13 }16 }
14 17
15 // Interface describes a protocol interface. Build its C table once with18 // Interface describes a protocol interface. Build its C table once with
@@ -55,6 +58,21 @@ func NewInterfaces(ifaces ...*Interface) error {
55 }58 }
56 // Pass 2: fill messages and headers.59 // Pass 2: fill messages and headers.
57 for i, it := range ifaces {60 for i, it := range ifaces {
61+ for j := range it.Requests {
62+ m := &it.Requests[j]
63+ m.argc = argCount(m.Signature)
64+ m.fdPositions = nil
65+ pos := 0
66+ for _, ch := range m.Signature {
67+ if ch == '?' || (ch >= '0' && ch <= '9') {
68+ continue
69+ }
70+ if ch == 'h' {
71+ m.fdPositions = append(m.fdPositions, pos)
72+ }
73+ pos++
74+ }
75+ }
58 o := offs[i]76 o := offs[i]
59 a.putPtr(o, a.cstring(it.Name))77 a.putPtr(o, a.cstring(it.Name))
60 a.putI32(o+8, it.Version)78 a.putI32(o+8, it.Version)
M server/lib.go +38 -18
@@ -15,21 +15,12 @@ var (
1515 wlDisplayDestroyClients func(display uintptr)
1616 wlDisplayAddSocketFD func(display uintptr, fd int32) int32
1717 wlDisplayGetEventLoop func(display uintptr) uintptr
18- wlDisplayFlushClients func(display uintptr)
19- wlEventLoopDispatch func(loop uintptr, timeout int32) int32
2018 wlEventLoopGetFD func(loop uintptr) int32
2119 wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr
2220 wlEventSourceRemove func(source uintptr) int32
2321 wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr
2422 wlGlobalRemove func(global uintptr)
25- wlResourceCreate func(client, iface uintptr, version int32, id uint32) uintptr
26- wlResourceSetDispatcher func(resource, dispatcher, impl, data, destroy uintptr)
27- wlResourcePostEventArr func(resource uintptr, opcode uint32, args unsafe.Pointer)
2823 wlResourcePostError func(resource uintptr, code uint32, msg uintptr)
29- wlResourceDestroy func(resource uintptr)
30- wlResourceGetID func(resource uintptr) uint32
31- wlResourceGetClient func(resource uintptr) uintptr
32- wlResourceGetVersion func(resource uintptr) int32
3324 wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer)
3425 )
3526
@@ -37,6 +28,9 @@ var (
3728 loadOnce sync.Once
3829 loadErr error
3930
31+ symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr
32+ symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy uintptr
33+
4034 // Shared C callbacks. purego callbacks are never freed, so there is one
4135 // of each for the whole process.
4236 cbDispatcher uintptr
@@ -63,22 +57,30 @@ func load() error {
6357 reg(&wlDisplayDestroyClients, "wl_display_destroy_clients")
6458 reg(&wlDisplayAddSocketFD, "wl_display_add_socket_fd")
6559 reg(&wlDisplayGetEventLoop, "wl_display_get_event_loop")
66- reg(&wlDisplayFlushClients, "wl_display_flush_clients")
67- reg(&wlEventLoopDispatch, "wl_event_loop_dispatch")
6860 reg(&wlEventLoopGetFD, "wl_event_loop_get_fd")
6961 reg(&wlEventLoopAddFD, "wl_event_loop_add_fd")
7062 reg(&wlEventSourceRemove, "wl_event_source_remove")
7163 reg(&wlGlobalCreate, "wl_global_create")
7264 reg(&wlGlobalRemove, "wl_global_remove")
73- reg(&wlResourceCreate, "wl_resource_create")
74- reg(&wlResourceSetDispatcher, "wl_resource_set_dispatcher")
75- reg(&wlResourcePostEventArr, "wl_resource_post_event_array")
7665 reg(&wlResourcePostError, "wl_resource_post_error")
77- reg(&wlResourceDestroy, "wl_resource_destroy")
78- reg(&wlResourceGetID, "wl_resource_get_id")
79- reg(&wlResourceGetClient, "wl_resource_get_client")
80- reg(&wlResourceGetVersion, "wl_resource_get_version")
8166 reg(&wlClientGetCredentials, "wl_client_get_credentials")
67+ for _, entry := range []struct {
68+ name string
69+ sym *uintptr
70+ }{
71+ {"wl_display_flush_clients", &symDisplayFlushClients},
72+ {"wl_event_loop_dispatch", &symEventLoopDispatch},
73+ {"wl_resource_create", &symResourceCreate},
74+ {"wl_resource_set_dispatcher", &symResourceSetDispatcher},
75+ {"wl_resource_post_event_array", &symResourcePostEventArr},
76+ {"wl_resource_destroy", &symResourceDestroy},
77+ } {
78+ *entry.sym, err = purego.Dlsym(lib, entry.name)
79+ if err != nil {
80+ loadErr = fmt.Errorf("purego-libwayland: resolve %s: %w", entry.name, err)
81+ return
82+ }
83+ }
8284
8385 cbDispatcher = purego.NewCallback(dispatch)
8486 cbDestroy = purego.NewCallback(destroyed)
@@ -87,3 +89,21 @@ func load() error {
8789 })
8890 return loadErr
8991 }
92+
93+// Integer and pointer-only entry points avoid RegisterLibFunc's reflective call path.
94+func wlDisplayFlushClients(display uintptr) { purego.SyscallN(symDisplayFlushClients, display) }
95+func wlEventLoopDispatch(loop uintptr, timeout int32) int32 {
96+ r, _, _ := purego.SyscallN(symEventLoopDispatch, loop, uintptr(timeout))
97+ return int32(r)
98+}
99+func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr {
100+ r, _, _ := purego.SyscallN(symResourceCreate, client, iface, uintptr(version), uintptr(id))
101+ return r
102+}
103+func wlResourceSetDispatcher(resource, dispatcher, impl, data, destroy uintptr) {
104+ purego.SyscallN(symResourceSetDispatcher, resource, dispatcher, impl, data, destroy)
105+}
106+func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer) {
107+ purego.SyscallN(symResourcePostEventArr, resource, uintptr(opcode), uintptr(args))
108+}
109+func wlResourceDestroy(resource uintptr) { purego.SyscallN(symResourceDestroy, resource) }
@@ -15,21 +15,12 @@ var (
15 wlDisplayDestroyClients func(display uintptr)15 wlDisplayDestroyClients func(display uintptr)
16 wlDisplayAddSocketFD func(display uintptr, fd int32) int3216 wlDisplayAddSocketFD func(display uintptr, fd int32) int32
17 wlDisplayGetEventLoop func(display uintptr) uintptr17 wlDisplayGetEventLoop func(display uintptr) uintptr
18- wlDisplayFlushClients func(display uintptr)
19- wlEventLoopDispatch func(loop uintptr, timeout int32) int32
20 wlEventLoopGetFD func(loop uintptr) int3218 wlEventLoopGetFD func(loop uintptr) int32
21 wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr19 wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr
22 wlEventSourceRemove func(source uintptr) int3220 wlEventSourceRemove func(source uintptr) int32
23 wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr21 wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr
24 wlGlobalRemove func(global uintptr)22 wlGlobalRemove func(global uintptr)
25- wlResourceCreate func(client, iface uintptr, version int32, id uint32) uintptr
26- wlResourceSetDispatcher func(resource, dispatcher, impl, data, destroy uintptr)
27- wlResourcePostEventArr func(resource uintptr, opcode uint32, args unsafe.Pointer)
28 wlResourcePostError func(resource uintptr, code uint32, msg uintptr)23 wlResourcePostError func(resource uintptr, code uint32, msg uintptr)
29- wlResourceDestroy func(resource uintptr)
30- wlResourceGetID func(resource uintptr) uint32
31- wlResourceGetClient func(resource uintptr) uintptr
32- wlResourceGetVersion func(resource uintptr) int32
33 wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer)24 wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer)
34 )25 )
35 26
@@ -37,6 +28,9 @@ var (
37 loadOnce sync.Once28 loadOnce sync.Once
38 loadErr error29 loadErr error
39 30
31+ symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr
32+ symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy uintptr
33+
40 // Shared C callbacks. purego callbacks are never freed, so there is one34 // Shared C callbacks. purego callbacks are never freed, so there is one
41 // of each for the whole process.35 // of each for the whole process.
42 cbDispatcher uintptr36 cbDispatcher uintptr
@@ -63,22 +57,30 @@ func load() error {
63 reg(&wlDisplayDestroyClients, "wl_display_destroy_clients")57 reg(&wlDisplayDestroyClients, "wl_display_destroy_clients")
64 reg(&wlDisplayAddSocketFD, "wl_display_add_socket_fd")58 reg(&wlDisplayAddSocketFD, "wl_display_add_socket_fd")
65 reg(&wlDisplayGetEventLoop, "wl_display_get_event_loop")59 reg(&wlDisplayGetEventLoop, "wl_display_get_event_loop")
66- reg(&wlDisplayFlushClients, "wl_display_flush_clients")
67- reg(&wlEventLoopDispatch, "wl_event_loop_dispatch")
68 reg(&wlEventLoopGetFD, "wl_event_loop_get_fd")60 reg(&wlEventLoopGetFD, "wl_event_loop_get_fd")
69 reg(&wlEventLoopAddFD, "wl_event_loop_add_fd")61 reg(&wlEventLoopAddFD, "wl_event_loop_add_fd")
70 reg(&wlEventSourceRemove, "wl_event_source_remove")62 reg(&wlEventSourceRemove, "wl_event_source_remove")
71 reg(&wlGlobalCreate, "wl_global_create")63 reg(&wlGlobalCreate, "wl_global_create")
72 reg(&wlGlobalRemove, "wl_global_remove")64 reg(&wlGlobalRemove, "wl_global_remove")
73- reg(&wlResourceCreate, "wl_resource_create")
74- reg(&wlResourceSetDispatcher, "wl_resource_set_dispatcher")
75- reg(&wlResourcePostEventArr, "wl_resource_post_event_array")
76 reg(&wlResourcePostError, "wl_resource_post_error")65 reg(&wlResourcePostError, "wl_resource_post_error")
77- reg(&wlResourceDestroy, "wl_resource_destroy")
78- reg(&wlResourceGetID, "wl_resource_get_id")
79- reg(&wlResourceGetClient, "wl_resource_get_client")
80- reg(&wlResourceGetVersion, "wl_resource_get_version")
81 reg(&wlClientGetCredentials, "wl_client_get_credentials")66 reg(&wlClientGetCredentials, "wl_client_get_credentials")
67+ for _, entry := range []struct {
68+ name string
69+ sym *uintptr
70+ }{
71+ {"wl_display_flush_clients", &symDisplayFlushClients},
72+ {"wl_event_loop_dispatch", &symEventLoopDispatch},
73+ {"wl_resource_create", &symResourceCreate},
74+ {"wl_resource_set_dispatcher", &symResourceSetDispatcher},
75+ {"wl_resource_post_event_array", &symResourcePostEventArr},
76+ {"wl_resource_destroy", &symResourceDestroy},
77+ } {
78+ *entry.sym, err = purego.Dlsym(lib, entry.name)
79+ if err != nil {
80+ loadErr = fmt.Errorf("purego-libwayland: resolve %s: %w", entry.name, err)
81+ return
82+ }
83+ }
82 84
83 cbDispatcher = purego.NewCallback(dispatch)85 cbDispatcher = purego.NewCallback(dispatch)
84 cbDestroy = purego.NewCallback(destroyed)86 cbDestroy = purego.NewCallback(destroyed)
@@ -87,3 +89,21 @@ func load() error {
87 })89 })
88 return loadErr90 return loadErr
89 }91 }
92+
93+// Integer and pointer-only entry points avoid RegisterLibFunc's reflective call path.
94+func wlDisplayFlushClients(display uintptr) { purego.SyscallN(symDisplayFlushClients, display) }
95+func wlEventLoopDispatch(loop uintptr, timeout int32) int32 {
96+ r, _, _ := purego.SyscallN(symEventLoopDispatch, loop, uintptr(timeout))
97+ return int32(r)
98+}
99+func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr {
100+ r, _, _ := purego.SyscallN(symResourceCreate, client, iface, uintptr(version), uintptr(id))
101+ return r
102+}
103+func wlResourceSetDispatcher(resource, dispatcher, impl, data, destroy uintptr) {
104+ purego.SyscallN(symResourceSetDispatcher, resource, dispatcher, impl, data, destroy)
105+}
106+func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer) {
107+ purego.SyscallN(symResourcePostEventArr, resource, uintptr(opcode), uintptr(args))
108+}
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