From 6fdc12966f10b408420ee5dfd53441f20662e5f0 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 6 Sep 2026 22:43:34 -0500 Subject: [PATCH] st: add bulk ASCII fast path to twrite Streaming text spent ~50% of its cycles in per-character tputc/tsetchar dispatch. tputcrun() writes runs of plain printable ASCII directly into the current line as whole cells, bailing out to tputc() whenever anything non-trivial is in play (escape state, print/insert/sixel modes, GRAPHIC0 charset, pending wrap, active selection, non-ASCII bytes). Cuts user time ~3x (0.79s -> 0.26s) and wall time ~45% when streaming 54MB of text. Verified pixel-identical rendering against the previous build for escape-sequence torture tests, wide/CJK/emoji glyphs, box-drawing, alt screen, scroll regions, insert/delete, resize reflow, X primary selection contents and a syntax-highlighted vim session. --- st.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/st.c b/st.c index d6f0e7f..9e1c061 100644 --- a/st.c +++ b/st.c @@ -3707,6 +3707,84 @@ check_control_code: } } +/* + * Print a run of plain printable ASCII characters using the current cursor + * attributes, writing whole cells directly into the current line. Returns the + * number of bytes consumed, or 0 when the fast path does not apply and the + * caller must fall back to tputc(). + */ +static int +tputcrun(const char *buf, int buflen) +{ + Glyph attr = term.c.attr; + Glyph *gp; + int i, run, avail; + + /* Only handle the simple, overwhelmingly common case. */ + if (term.esc || (term.mode & (MODE_PRINT|MODE_INSERT|MODE_SIXEL)) || + term.trantbl[term.charset] == CS_GRAPHIC0 || + (term.c.state & CURSOR_WRAPNEXT) || + sel.mode != SEL_IDLE || sel.ob.x != -1) + return 0; + + avail = term.col - term.c.x; + if (avail <= 0) + return 0; + + /* Count leading printable ASCII bytes, bounded by the line width. */ + for (run = 0; run < buflen && run < avail; run++) { + unsigned char c = (unsigned char)buf[run]; + if (c < 0x20 || c >= 0x7f) + break; + } + if (run < 2) + return 0; /* not worth it; keep tputc's semantics */ + + attr.mode |= ATTR_SET; + attr.mode &= ~(ATTR_WIDE|ATTR_WDUMMY|ATTR_BOXDRAW|ATTR_KITTYPH); + #if OSC7_OSC8_PATCH + attr.link = curlink; + #endif // OSC7_OSC8_PATCH + #if KITTY_GRAPHICS_PATCH + attr.kitty = 0; + #endif // KITTY_GRAPHICS_PATCH + + gp = &term.line[term.c.y][term.c.x]; + + /* A wide glyph may straddle the start of the run. */ + if (gp->mode & ATTR_WDUMMY) { + if (term.c.x == 0) + return 0; + gp[-1].u = ' '; + gp[-1].mode &= ~ATTR_WIDE; + } + /* ...or the end of it. */ + if (run < avail && (gp[run].mode & ATTR_WDUMMY)) { + gp[run].u = ' '; + gp[run].mode &= ~ATTR_WDUMMY; + } + + for (i = 0; i < run; i++) { + gp[i] = attr; + gp[i].u = (unsigned char)buf[i]; + } + + term.dirty[term.c.y] = 1; + term.lastc = (unsigned char)buf[run-1]; + + if (term.c.x + run < term.col) { + term.c.x += run; + } else { + term.c.x = term.col - 1; + #if REFLOW_PATCH + term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = 1; + #endif // REFLOW_PATCH + term.c.state |= CURSOR_WRAPNEXT; + } + + return run; +} + int twrite(const char *buf, int buflen, int show_ctrl) { @@ -3720,6 +3798,14 @@ twrite(const char *buf, int buflen, int show_ctrl) #endif // SYNC_PATCH for (n = 0; n < buflen; n += charsize) { + /* + * Fast path: a run of plain printable ASCII printed with the + * current attributes onto the current line. This skips the + * per-character tputc/tsetchar dispatch, which dominates the + * profile while streaming large amounts of text. + */ + if (!show_ctrl && (charsize = tputcrun(buf + n, buflen - n)) > 0) + continue; #if SIXEL_PATCH if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) { charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n);