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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +201 -56
M server/display.go +21 -37
| @@ -12,7 +12,8 @@ import ( | |||
| 12 | 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 | 17 | type Handler func(r *Resource, opcode uint32, args []Arg) | |
| 17 | 18 | ||
| 18 | 19 | // BindFunc runs when a client binds a global. | |
| @@ -24,6 +25,9 @@ type Client struct{ c uintptr } | |||
| 24 | 25 | // Resource is a server-side wl_resource. | |
| 25 | 26 | type Resource struct { | |
| 26 | 27 | c uintptr | |
| 28 | + | id uint32 | |
| 29 | + | version int32 | |
| 30 | + | client uintptr | |
| 27 | 31 | iface *Interface | |
| 28 | 32 | handler Handler | |
| 29 | 33 | OnDestroy func() | |
| @@ -222,7 +226,7 @@ func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Han | |||
| 222 | 226 | if rc == 0 { | |
| 223 | 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 | 230 | live.resources[rc] = r | |
| 227 | 231 | wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy) | |
| 228 | 232 | return r, nil | |
| @@ -233,19 +237,19 @@ func (r *Resource) ID() uint32 { | |||
| 233 | 237 | if r.gone { | |
| 234 | 238 | return 0 | |
| 235 | 239 | } | |
| 236 | - | return wlResourceGetID(r.c) | |
| 240 | + | return r.id | |
| 237 | 241 | } | |
| 238 | 242 | func (r *Resource) Version() int32 { | |
| 239 | 243 | if r.gone { | |
| 240 | 244 | return 0 | |
| 241 | 245 | } | |
| 242 | - | return wlResourceGetVersion(r.c) | |
| 246 | + | return r.version | |
| 243 | 247 | } | |
| 244 | 248 | func (r *Resource) Client() Client { | |
| 245 | 249 | if r.gone { | |
| 246 | 250 | return Client{} | |
| 247 | 251 | } | |
| 248 | - | return Client{wlResourceGetClient(r.c)} | |
| 252 | + | return Client{r.client} | |
| 249 | 253 | } | |
| 250 | 254 | func (r *Resource) Iface() *Interface { return r.iface } | |
| 251 | 255 | ||
| @@ -268,11 +272,6 @@ func (r *Resource) PostEvent(opcode uint32, args ...Arg) { | |||
| 268 | 272 | if len(args) > 0 { | |
| 269 | 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 | 275 | wlResourcePostEventArr(r.c, opcode, p) | |
| 277 | 276 | runtime.KeepAlive(args) | |
| 278 | 277 | } | |
| @@ -311,17 +310,19 @@ var marker uintptr | |||
| 311 | 310 | ||
| 312 | 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 | 314 | 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) | |
| 319 | 321 | return 0 | |
| 320 | 322 | } | |
| 321 | - | n := argCount(sig) | |
| 322 | 323 | 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) | |
| 325 | 326 | } | |
| 326 | 327 | r.handler(r, opcode, a) | |
| 327 | 328 | 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 | 356 | // 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()) | |
| 376 | 360 | } | |
| 377 | 361 | } | |
| 378 | 362 | ||
| @@ -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 uintptr | 27 | c uintptr | ||
| 28 | + | id uint32 | |||
| 29 | + | version int32 | |||
| 30 | + | client uintptr | |||
| 27 | iface *Interface | 31 | iface *Interface | ||
| 28 | handler Handler | 32 | 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] = r | 230 | live.resources[rc] = r | ||
| 227 | wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy) | 231 | wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy) | ||
| 228 | return r, nil | 232 | return r, nil | ||
| @@ -233,19 +237,19 @@ func (r *Resource) ID() uint32 { | |||||
| 233 | if r.gone { | 237 | if r.gone { | ||
| 234 | return 0 | 238 | 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 0 | 244 | 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 0 | 321 | return 0 | ||
| 320 | } | 322 | } | ||
| 321 | - | n := argCount(sig) | |||
| 322 | var a []Arg | 323 | 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 0 | 328 | 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 := 0 | 358 | + | 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) { | |||
| 27 | 27 | } | |
| 28 | 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 | 35 | if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != unix.EBADF { | |
| 32 | 36 | t.Fatalf("signature %s: fd not closed: %v", sig, err) | |
| 33 | 37 | } | |
| @@ -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 { | |||
| 10 | 10 | Name string | |
| 11 | 11 | Signature string | |
| 12 | 12 | Types []*Interface | |
| 13 | + | ||
| 14 | + | argc int | |
| 15 | + | fdPositions []int | |
| 13 | 16 | } | |
| 14 | 17 | ||
| 15 | 18 | // Interface describes a protocol interface. Build its C table once with | |
| @@ -55,6 +58,21 @@ func NewInterfaces(ifaces ...*Interface) error { | |||
| 55 | 58 | } | |
| 56 | 59 | // Pass 2: fill messages and headers. | |
| 57 | 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 | 76 | o := offs[i] | |
| 59 | 77 | a.putPtr(o, a.cstring(it.Name)) | |
| 60 | 78 | a.putI32(o+8, it.Version) | |
| @@ -10,6 +10,9 @@ type Message struct { | |||||
| 10 | Name string | 10 | Name string | ||
| 11 | Signature string | 11 | Signature string | ||
| 12 | Types []*Interface | 12 | Types []*Interface | ||
| 13 | + | ||||
| 14 | + | argc int | |||
| 15 | + | fdPositions []int | |||
| 13 | } | 16 | } | ||
| 14 | 17 | ||||
| 15 | // Interface describes a protocol interface. Build its C table once with | 18 | // 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 ( | |||
| 15 | 15 | wlDisplayDestroyClients func(display uintptr) | |
| 16 | 16 | wlDisplayAddSocketFD func(display uintptr, fd int32) int32 | |
| 17 | 17 | wlDisplayGetEventLoop func(display uintptr) uintptr | |
| 18 | - | wlDisplayFlushClients func(display uintptr) | |
| 19 | - | wlEventLoopDispatch func(loop uintptr, timeout int32) int32 | |
| 20 | 18 | wlEventLoopGetFD func(loop uintptr) int32 | |
| 21 | 19 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | |
| 22 | 20 | wlEventSourceRemove func(source uintptr) int32 | |
| 23 | 21 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | |
| 24 | 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 | 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 | 24 | wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer) | |
| 34 | 25 | ) | |
| 35 | 26 | ||
| @@ -37,6 +28,9 @@ var ( | |||
| 37 | 28 | loadOnce sync.Once | |
| 38 | 29 | loadErr error | |
| 39 | 30 | ||
| 31 | + | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | |
| 32 | + | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy uintptr | |
| 33 | + | ||
| 40 | 34 | // Shared C callbacks. purego callbacks are never freed, so there is one | |
| 41 | 35 | // of each for the whole process. | |
| 42 | 36 | cbDispatcher uintptr | |
| @@ -63,22 +57,30 @@ func load() error { | |||
| 63 | 57 | reg(&wlDisplayDestroyClients, "wl_display_destroy_clients") | |
| 64 | 58 | reg(&wlDisplayAddSocketFD, "wl_display_add_socket_fd") | |
| 65 | 59 | reg(&wlDisplayGetEventLoop, "wl_display_get_event_loop") | |
| 66 | - | reg(&wlDisplayFlushClients, "wl_display_flush_clients") | |
| 67 | - | reg(&wlEventLoopDispatch, "wl_event_loop_dispatch") | |
| 68 | 60 | reg(&wlEventLoopGetFD, "wl_event_loop_get_fd") | |
| 69 | 61 | reg(&wlEventLoopAddFD, "wl_event_loop_add_fd") | |
| 70 | 62 | reg(&wlEventSourceRemove, "wl_event_source_remove") | |
| 71 | 63 | reg(&wlGlobalCreate, "wl_global_create") | |
| 72 | 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 | 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 | 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 | 85 | cbDispatcher = purego.NewCallback(dispatch) | |
| 84 | 86 | cbDestroy = purego.NewCallback(destroyed) | |
| @@ -87,3 +89,21 @@ func load() error { | |||
| 87 | 89 | }) | |
| 88 | 90 | 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) } | |
| @@ -15,21 +15,12 @@ var ( | |||||
| 15 | wlDisplayDestroyClients func(display uintptr) | 15 | wlDisplayDestroyClients func(display uintptr) | ||
| 16 | wlDisplayAddSocketFD func(display uintptr, fd int32) int32 | 16 | wlDisplayAddSocketFD func(display uintptr, fd int32) int32 | ||
| 17 | wlDisplayGetEventLoop func(display uintptr) uintptr | 17 | wlDisplayGetEventLoop func(display uintptr) uintptr | ||
| 18 | - | wlDisplayFlushClients func(display uintptr) | |||
| 19 | - | wlEventLoopDispatch func(loop uintptr, timeout int32) int32 | |||
| 20 | wlEventLoopGetFD func(loop uintptr) int32 | 18 | wlEventLoopGetFD func(loop uintptr) int32 | ||
| 21 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | 19 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | ||
| 22 | wlEventSourceRemove func(source uintptr) int32 | 20 | wlEventSourceRemove func(source uintptr) int32 | ||
| 23 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | 21 | 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.Once | 28 | loadOnce sync.Once | ||
| 38 | loadErr error | 29 | 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 one | 34 | // 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 uintptr | 36 | 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 loadErr | 90 | 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) } | |||