From ae8394b0c14aefd7b159e0e8cf8ad9dc5db6245e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 8 May 2025 09:23:01 +0200 Subject: [PATCH] wip: io-uring --- meson.build | 15 ++- meson_options.txt | 2 + selection.c | 4 +- terminal.c | 226 +++++++++++++++++++++++++++++++++++++++++++++- terminal.h | 12 +++ 5 files changed, 250 insertions(+), 9 deletions(-) diff --git a/meson.build b/meson.build index a884e533..8d1cb7d3 100644 --- a/meson.build +++ b/meson.build @@ -140,6 +140,7 @@ wayland_cursor = dependency('wayland-cursor') xkb = dependency('xkbcommon', version: '>=1.8.0') fontconfig = dependency('fontconfig') utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering')) +io_uring = dependency('liburing', required: get_option('io-uring')) if utf8proc.found() add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c') @@ -149,6 +150,10 @@ if pixman.version().version_compare('>=0.46.0') add_project_arguments('-DHAVE_PIXMAN_RGBA_16', language: 'c') endif +if io_uring.found() + add_project_arguments('-DFOOT_IO_URING=1', language: 'c') +endif + tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft') @@ -260,7 +265,7 @@ vtlib = static_library( builtin_terminfo, srgb_funcs, wl_proto_src + wl_proto_headers, version, - dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc], + dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc, io_uring], link_with: [common, misc], ) @@ -271,7 +276,7 @@ pgolib = static_library( 'terminal.c', 'terminal.h', emoji_variation_sequences, wl_proto_src + wl_proto_headers, - dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc], + dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc, io_uring], link_with: vtlib, ) @@ -287,7 +292,7 @@ if get_option('b_pgo') == 'generate' 'pgo', 'pgo/pgo.c', wl_proto_src + wl_proto_headers, - dependencies: [math, threads, libepoll, pixman, wayland_client, xkb, utf8proc, fcft, tllist], + dependencies: [math, threads, libepoll, pixman, wayland_client, xkb, utf8proc, fcft, tllist, io_uring], link_with: pgolib, ) endif @@ -320,8 +325,7 @@ executable( 'user-notification.c', 'user-notification.h', 'wayland.c', 'wayland.h', 'shm-formats.h', srgb_funcs, wl_proto_src + wl_proto_headers, version, - dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc, - tllist, fcft], + dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc, io_uring, tllist, fcft], link_with: pgolib, install: true) @@ -422,6 +426,7 @@ summary( 'Grapheme clustering': utf8proc.found(), 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, + 'io-uring': io_uring.found(), 'Build terminfo': tic.found(), 'Terminfo base name': terminfo_base_name, 'Terminfo install location': terminfo_install_location, diff --git a/meson_options.txt b/meson_options.txt index ab7a07be..b22626ce 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -27,3 +27,5 @@ option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutemp description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)') option('utmp-default-helper-path', type: 'string', value: 'auto', description: 'Default path to the utmp helper binary. Default: auto-detect') + +option('io-uring', type: 'feature', value: 'enabled', description: 'Build with io-uring support') diff --git a/selection.c b/selection.c index d7aa617a..c044b3b2 100644 --- a/selection.c +++ b/selection.c @@ -1855,7 +1855,7 @@ send_clipboard_or_primary(struct seat *seat, int fd, const char *selection, } static void -send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, +selection_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { struct seat *seat = data; @@ -1900,7 +1900,7 @@ action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) static const struct wl_data_source_listener data_source_listener = { .target = &target, - .send = &send, + .send = &selection_send, .cancelled = &cancelled, .dnd_drop_performed = &dnd_drop_performed, .dnd_finished = &dnd_finished, diff --git a/terminal.c b/terminal.c index 18f3bc9f..74e566b4 100644 --- a/terminal.c +++ b/terminal.c @@ -237,6 +237,152 @@ static bool cursor_blink_rearm_timer(struct terminal *term); /* Externally visible, but not declared in terminal.h, to enable pgo * to call this function directly */ + +#if defined(FOOT_IO_URING) +static bool +fdm_ptmx_eventfd(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + + /* Prevent blinking while typing */ + if (term->cursor_blink.fd >= 0) { + term->cursor_blink.state = CURSOR_BLINK_ON; + cursor_blink_rearm_timer(term); + } + + if (unlikely(term->interactive_resizing.grid != NULL)) { + /* + * Don't consume PTMX while we're doing an interactive resize, + * since the 'normal' grid we're currently using is a + * temporary one - all changes done to it will be lost when + * the interactive resize ends. + */ + return true; + } + + //const size_t max_iterations = !hup ? 10 : SIZE_MAX; + const size_t max_iterations = 100; + + for (size_t i = 0; i < max_iterations; i++) { + struct io_uring_cqe *cqe; + int ret = io_uring_peek_cqe(&term->uring.ring, &cqe); + + if (unlikely(ret < 0)) { + if (-ret == EAGAIN) + break; + + LOG_ERRNO_P(-ret, "failed to get CQE from io-uring"); + return false; + } + + if (!(cqe->flags & IORING_CQE_F_MORE)) { + if (cqe->res >= 0 || -cqe->res == ENOBUFS) { + struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring); + io_uring_prep_read_multishot(sqe, term->ptmx, 0, -1, 123); + io_uring_submit(&term->uring.ring); + } + } + + if (unlikely(cqe->res < 0)) { + if (-cqe->res == EAGAIN || -cqe->res == EIO || -cqe->res == ENOBUFS) { + /* + * EAGAIN: no more to read - FDM will trigger us again + * EIO: assume PTY was closed - we already have, or will get, a EPOLLHUP + */ + io_uring_cqe_seen(&term->uring.ring, cqe); + return true; + } + + LOG_ERRNO_P(-cqe->res, "failed to read from pseudo terminal"); + io_uring_cqe_seen(&term->uring.ring, cqe); + return false; + } + + xassert(cqe->flags & IORING_CQE_F_BUFFER); + + const int buffer_id = cqe->flags >> IORING_CQE_BUFFER_SHIFT; + const ssize_t count = cqe->res; + const uint8_t *buf = term->uring.buffers[buffer_id]; + + xassert(term->interactive_resizing.grid == NULL); + vt_from_slave(term, buf, count); + + io_uring_buf_ring_add(term->uring.buf_ring, (void *)buf, 24 * 1024, buffer_id, io_uring_buf_ring_mask(128), 0); + io_uring_buf_ring_cq_advance(&term->uring.ring, term->uring.buf_ring, 1); + } + + if (!term->render.app_sync_updates.enabled) { + /* + * We likely need to re-render. But, we don't want to do it + * immediately. Often, a single client update is done through + * multiple writes. This could lead to us rendering one frame with + * "intermediate" state. + * + * For example, we might end up rendering a frame + * where the client just erased a line, while in the + * next frame, the client wrote to the same line. This + * causes screen "flickering". + * + * Mitigate by always incuring a small delay before + * rendering the next frame. This gives the client + * some time to finish the operation (and thus gives + * us time to receive the last writes before doing any + * actual rendering). + * + * We incur this delay *every* time we receive + * input. To ensure we don't delay rendering + * indefinitely, we start a second timer that is only + * reset when we render. + * + * Note that when the client is producing data at a + * very high pace, we're rate limited by the wayland + * compositor anyway. The delay we introduce here only + * has any effect when the renderer is idle. + */ + uint64_t lower_ns = term->conf->tweak.delayed_render_lower_ns; + uint64_t upper_ns = term->conf->tweak.delayed_render_upper_ns; + + if (lower_ns > 0 && upper_ns > 0) { +#if PTMX_TIMING + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + if (last.tv_sec > 0 || last.tv_nsec > 0) { + struct timespec diff; + + timespec_sub(&now, &last, &diff); + LOG_INFO("waited %lds %ldns for more input", + (long)diff.tv_sec, diff.tv_nsec); + } + last = now; +#endif + + xassert(lower_ns < 1000000000); + xassert(upper_ns < 1000000000); + xassert(upper_ns > lower_ns); + + timerfd_settime( + term->delayed_render_timer.lower_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = lower_ns}}, + NULL); + + /* Second timeout - only reset when we render. Set to one + * frame (assuming 60Hz) */ + if (!term->delayed_render_timer.is_armed) { + timerfd_settime( + term->delayed_render_timer.upper_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = upper_ns}}, + NULL); + term->delayed_render_timer.is_armed = true; + } + } else + render_refresh(term); + } + + return true; +} +#endif + bool fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) { @@ -292,6 +438,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) xassert(term->interactive_resizing.grid == NULL); vt_from_slave(term, buf, count); + break; } if (!term->render.app_sync_updates.enabled) { @@ -1406,6 +1553,16 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .active_notifications = tll_init(), }; +#if defined(FOOT_IO_URING) + { + int io_ret = io_uring_queue_init(2, &term->uring.ring, 0); + if (io_ret < 0) { + LOG_ERRNO_P(-io_ret, "failed to initialize io_uring queue"); + goto close_fds; + } + } +#endif + pixman_region32_init(&term->render.last_overlay_clip); term_update_ascii_printer(term); @@ -1503,7 +1660,39 @@ term_window_configured(struct terminal *term) /* Enable ptmx FDM callback */ if (!term->shutdown.in_progress) { xassert(term->window->is_configured); + +#if defined(FOOT_IO_URING) + { + const unsigned short bid = 123; + const size_t total_size = 3 * 1024 * 1024; + const size_t buf_size = 24 * 1024; + const size_t buf_count = total_size / buf_size; + xassert(buf_count * buf_size == total_size); + + int ret; + term->uring.buf_ring = io_uring_setup_buf_ring( + &term->uring.ring, buf_count, bid, 0, &ret); + + term->uring.buffers = xmalloc(buf_count * sizeof(term->uring.buffers[0])); + for (unsigned short id = 0; id < buf_count; id++) { + void *buffer = xmalloc(buf_size); + io_uring_buf_ring_add( + term->uring.buf_ring, buffer, buf_size, id, + io_uring_buf_ring_mask(buf_count), id); + term->uring.buffers[id] = buffer; + } + io_uring_buf_ring_advance(term->uring.buf_ring, buf_count); + + struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring); + io_uring_prep_read_multishot(sqe, term->ptmx, 0, -1, bid); + io_uring_submit(&term->uring.ring); + } + + fdm_add(term->fdm, term->uring.ring.enter_ring_fd, EPOLLIN, &fdm_ptmx_eventfd, term); + fdm_add(term->fdm, term->ptmx, 0, &fdm_ptmx, term); +#else fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term); +#endif const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled"); @@ -1723,9 +1912,10 @@ term_shutdown(struct terminal *term) del_utmp_record(term->conf, term->reaper, term->ptmx); - if (term->window != NULL && term->window->is_configured) + if (term->window != NULL && term->window->is_configured) { fdm_del(term->fdm, term->ptmx); - else + //fdm_del(term->fdm, term->io_uring.enter_ring_fd); + } else close(term->ptmx); if (!term->shutdown.client_has_terminated) { @@ -1832,6 +2022,38 @@ term_destroy(struct terminal *term) if (term->shutdown.terminate_timeout_fd >= 0) fdm_del(term->fdm, term->shutdown.terminate_timeout_fd); +#if defined(FOOT_IO_URING) + fdm_del(term->fdm, term->uring.ring.enter_ring_fd); + + { + /* Cancel all pending io-uring requests */ + struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring); + io_uring_prep_cancel(sqe, NULL, IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_ANY); + io_uring_submit(&term->uring.ring); + + while (true) { + struct io_uring_cqe *cqe; + int ret = io_uring_peek_cqe(&term->uring.ring, &cqe); + + if (ret < 0) { + if (-ret != EAGAIN) + LOG_ERRNO_P(-ret, "failed to pull CQE from io-uring"); + break; + } + + io_uring_cqe_seen(&term->uring.ring, cqe); + } + + /* Shutdown io-uring */ + io_uring_queue_exit(&term->uring.ring); + + /* Free io-uring buffers */ + for (size_t i = 0; i < 128; i++) + free(term->uring.buffers[i]); + free(term->uring.buffers); + } +#endif + if (term->window != NULL) { wayl_win_destroy(term->window); term->window = NULL; diff --git a/terminal.h b/terminal.h index 3122cef3..6d02072a 100644 --- a/terminal.h +++ b/terminal.h @@ -11,6 +11,10 @@ #include #endif +#if defined(FOOT_IO_URING) + #include +#endif + #include #include @@ -428,6 +432,14 @@ struct terminal { pid_t slave; int ptmx; +#if defined(FOOT_IO_URING) + struct { + struct io_uring ring; + struct io_uring_buf_ring *buf_ring; + uint8_t **buffers; + } uring; +#endif + struct vt vt; struct grid *grid; struct grid normal;