diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 17:36:22 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 17:36:22 +0300 |
| commit | 13bf7edad251df9943bd75665a3510535849b0ac (patch) | |
| tree | 4597aae71940edac0b7d8b416c4803cd72ac5345 | |
| parent | 513bf031a589d9e2a1a2730adf56f95e5e316a06 (diff) | |
Deduplicate scroll and SIMD paths while preserving performance
| -rw-r--r-- | src/core.c | 95 | ||||
| -rw-r--r-- | src/kernels.h | 151 | ||||
| -rw-r--r-- | src/palette.c | 60 | ||||
| -rw-r--r-- | src/screen.c | 128 | ||||
| -rw-r--r-- | src/text_run.c | 102 | ||||
| -rw-r--r-- | tests/osc_test.c | 8 | ||||
| -rw-r--r-- | tests/term_test.c | 32 |
7 files changed, 213 insertions, 363 deletions
@@ -596,7 +596,6 @@ struct stream_emit_ctx { int orig_y; int orig_rem; int cols; - bool def_pen; }; static void flush_stream_queue(struct stream_emit_ctx *ctx) @@ -604,7 +603,6 @@ static void flush_stream_queue(struct stream_emit_ctx *ctx) if (ctx->orig_rem > 0) flush_orig_rows(ctx->term, ctx->screen, &ctx->orig_y, &ctx->orig_rem, ctx->orig_rem); int flush_n = ctx->q_count - ctx->q_cap; - (void)ctx->def_pen; term_hist_push_ascii_lines(&ctx->term->hist, ctx->q, flush_n, ctx->attr); if (ctx->q_cap > 0) { if (flush_n >= ctx->q_cap) @@ -1066,8 +1064,6 @@ static inline TERM_ALWAYS_INLINE size_t bulk_ascii_lines_body(struct term *t, co .flags = (uint16_t)flags, .link = (uint16_t)(flags >> 16), }; - bool def_pen = (t->cur_fg == TERM_COLOR_FG && t->cur_bg == TERM_COLOR_BG && flags == 0); - if (t->pending_wrap && (len == 0 || (unsigned char)buf[0] < 0x20 || (unsigned char)buf[0] >= 0x7f)) return 0; @@ -1084,7 +1080,6 @@ static inline TERM_ALWAYS_INLINE size_t bulk_ascii_lines_body(struct term *t, co .orig_y = 0, .orig_rem = rows - 1, .cols = cols, - .def_pen = def_pen, }; while (i < len) { @@ -1165,78 +1160,28 @@ narrow_done: continue; } - int p1 = term_ctz64(mask); - if (p1 > cols) - break; - - int eol1 = 0; - if (buf[i + p1] == '\n' && lnm) { - eol1 = 1; - } else if (buf[i + p1] == '\r' && (p1 + 1 < 64 ? buf[i + p1 + 1] == '\n' : (i + p1 + 1 < len && buf[i + p1 + 1] == '\n'))) { - eol1 = 2; - } else if (p1 < cols) { - goto done; - } else { - break; - } - - if (p1 > 0) - last_char = (unsigned char)buf[i + p1 - 1]; - emit_stream_row_fast(&ctx, buf + i, p1, false); - - int next_start = p1 + eol1; - int last_consumed = next_start; - - if (next_start < 64) { - uint64_t mask2 = mask >> next_start; - if (mask2 != 0) { - int rel_p2 = term_ctz64(mask2); - int p2 = next_start + rel_p2; - if (rel_p2 <= cols) { - int eol2 = 0; - if (buf[i + p2] == '\n' && lnm) { - eol2 = 1; - } else if (buf[i + p2] == '\r' && (p2 + 1 < 64 ? buf[i + p2 + 1] == '\n' : (i + p2 + 1 < len && buf[i + p2 + 1] == '\n'))) { - eol2 = 2; - } - - if (eol2 > 0) { - if (rel_p2 > 0) - last_char = (unsigned char)buf[i + p2 - 1]; - emit_stream_row_fast(&ctx, buf + i + next_start, rel_p2, false); - - int next_start2 = p2 + eol2; - last_consumed = next_start2; - - if (next_start2 < 64) { - uint64_t mask3 = mask >> next_start2; - if (mask3 != 0) { - int rel_p3 = term_ctz64(mask3); - int p3 = next_start2 + rel_p3; - if (rel_p3 <= cols) { - int eol3 = 0; - if (buf[i + p3] == '\n' && lnm) { - eol3 = 1; - } else if (buf[i + p3] == '\r' && (p3 + 1 < 64 ? buf[i + p3 + 1] == '\n' : (i + p3 + 1 < len && buf[i + p3 + 1] == '\n'))) { - eol3 = 2; - } - - if (eol3 > 0) { - if (rel_p3 > 0) - last_char = (unsigned char)buf[i + p3 - 1]; - emit_stream_row_fast(&ctx, buf + i + next_start2, rel_p3, false); - - last_consumed = p3 + eol3; - } - } - } - } - } - } + /* Consume up to three complete lines from this mask. */ + int consumed = 0; + for (int line = 0; line < 3 && consumed < 64; line++) { + uint64_t rest = mask >> consumed; + if (!rest) break; + int run = term_ctz64(rest); + if (run > cols) break; + int pos = consumed + run; + int eol = buf[i + pos] == '\n' && lnm ? 1 : + buf[i + pos] == '\r' && i + pos + 1 < len && + buf[i + pos + 1] == '\n' ? 2 : 0; + if (!eol) { + if (line == 0 && run < cols) goto done; + break; } + if (run > 0) + last_char = (unsigned char)buf[i + pos - 1]; + emit_stream_row_fast(&ctx, buf + i + consumed, run, false); + consumed = pos + eol; } - - i += (size_t)last_consumed; + if (!consumed) break; + i += (size_t)consumed; } if (i >= len) break; } diff --git a/src/kernels.h b/src/kernels.h index 57066c0..5cf0d93 100644 --- a/src/kernels.h +++ b/src/kernels.h @@ -197,23 +197,14 @@ static inline TERM_ALWAYS_INLINE void bytes_copy_small(uint8_t *restrict dst, if (k < n) memcpy(dst + n - 16, src + n - 16, 16); } else if (n >= 8) { - uint64_t a, b; - memcpy(&a, src, 8); - memcpy(&b, src + n - 8, 8); - memcpy(dst, &a, 8); - memcpy(dst + n - 8, &b, 8); + memcpy(dst, src, 8); + memcpy(dst + n - 8, src + n - 8, 8); } else if (n >= 4) { - uint32_t a, b; - memcpy(&a, src, 4); - memcpy(&b, src + n - 4, 4); - memcpy(dst, &a, 4); - memcpy(dst + n - 4, &b, 4); + memcpy(dst, src, 4); + memcpy(dst + n - 4, src + n - 4, 4); } else if (n >= 2) { - uint16_t a, b; - memcpy(&a, src, 2); - memcpy(&b, src + n - 2, 2); - memcpy(dst, &a, 2); - memcpy(dst + n - 2, &b, 2); + memcpy(dst, src, 2); + memcpy(dst + n - 2, src + n - 2, 2); } else if (n == 1) { dst[0] = (uint8_t)src[0]; } @@ -365,6 +356,26 @@ static inline int scan_ascii_lf_tail(const char *buf, int i, int len, uint16_t * } #if defined(TERM_KERNEL_AVX512) +TERM_TARGET_AVX512 +static inline int store_lf_offsets_avx512(uint16_t *ends, uint64_t nl, + __m512i lanes, __m128i block) +{ + unsigned n = (unsigned)term_popcount64(nl); + __m512i packed = _mm512_maskz_compress_epi8(nl, lanes); + __m128i offsets = _mm_add_epi16(_mm_cvtepu8_epi16(_mm512_castsi512_si128(packed)), block); + _mm_storeu_si128((__m128i *)ends, offsets); + if (TERM_EXPECT(n > 8, 0)) { + __m512i base = _mm512_broadcast_i32x4(block); + __m512i wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(packed)), base); + _mm512_storeu_si512(ends, wide); + if (n > 32) { + wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(packed, 1)), base); + _mm512_storeu_si512(ends + 32, wide); + } + } + return (int)n; +} + TERM_TARGET_AVX512 TERM_MAYBE_UNUSED static int scan_ascii_lf_avx512(const char *buf, int len, uint16_t *ends) { @@ -388,20 +399,7 @@ static int scan_ascii_lf_avx512(const char *buf, int len, uint16_t *ends) } if (bad) break; for (int q = 0; q < 4; q++) { - unsigned n = (unsigned)term_popcount64(nl[q]); - __m512i packed = _mm512_maskz_compress_epi8(nl[q], lanes); - __m128i offsets = _mm_add_epi16(_mm_cvtepu8_epi16(_mm512_castsi512_si128(packed)), block); - _mm_storeu_si128((__m128i *)(ends + count + 1), offsets); - if (TERM_EXPECT(n > 8, 0)) { - __m512i base = _mm512_broadcast_i32x4(block); - __m512i wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(packed)), base); - _mm512_storeu_si512(ends + count + 1, wide); - if (n > 32) { - wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(packed, 1)), base); - _mm512_storeu_si512(ends + count + 33, wide); - } - } - count += (int)n; + count += store_lf_offsets_avx512(ends + count + 1, nl[q], lanes, block); block = _mm_add_epi16(block, _mm_set1_epi16(64)); } } @@ -411,20 +409,7 @@ static int scan_ascii_lf_avx512(const char *buf, int len, uint16_t *ends) uint64_t bad = _mm512_cmpge_epu8_mask( _mm512_sub_epi8(v, _mm512_set1_epi8(0x20)), _mm512_set1_epi8(0x5f)) & ~nl; if (bad) break; - unsigned n = (unsigned)term_popcount64(nl); - __m512i packed = _mm512_maskz_compress_epi8(nl, lanes); - __m128i offsets = _mm_add_epi16(_mm_cvtepu8_epi16(_mm512_castsi512_si128(packed)), block); - _mm_storeu_si128((__m128i *)(ends + count + 1), offsets); - if (TERM_EXPECT(n > 8, 0)) { - __m512i base = _mm512_broadcast_i32x4(block); - __m512i wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(packed)), base); - _mm512_storeu_si512(ends + count + 1, wide); - if (n > 32) { - wide = _mm512_add_epi16(_mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(packed, 1)), base); - _mm512_storeu_si512(ends + count + 33, wide); - } - } - count += (int)n; + count += store_lf_offsets_avx512(ends + count + 1, nl, lanes, block); block = _mm_add_epi16(block, _mm_set1_epi16(64)); } return scan_ascii_lf_tail(buf, i, len, ends, count); @@ -672,6 +657,32 @@ static inline void put_ascii_chunk_avx2(struct cell * restrict dst, const char * } #endif +#if defined(__aarch64__) +static inline void store_ascii_quad_neon(struct cell *dst, uint32x4_t cps, + uint32x4_t fg, uint32x4_t bg_flags) +{ + uint64x2_t lo = vreinterpretq_u64_u32(vzip1q_u32(cps, fg)); + uint64x2_t hi = vreinterpretq_u64_u32(vzip2q_u32(cps, fg)); + uint64x2_t tail = vreinterpretq_u64_u32(bg_flags); + uint32x4x4_t cells = {{ + vreinterpretq_u32_u64(vzip1q_u64(lo, tail)), + vreinterpretq_u32_u64(vzip2q_u64(lo, tail)), + vreinterpretq_u32_u64(vzip1q_u64(hi, tail)), + vreinterpretq_u32_u64(vzip2q_u64(hi, tail)), + }}; + vst1q_u32_x4((uint32_t *)(void *)dst, cells); +} + +static inline void put_ascii_quad_neon(struct cell *dst, const char *src, + uint32x4_t fg, uint32x4_t bg_flags) +{ + uint32_t bytes; + memcpy(&bytes, src, sizeof(bytes)); + uint16x4_t cps = vget_low_u16(vmovl_u8(vreinterpret_u8_u32(vdup_n_u32(bytes)))); + store_ascii_quad_neon(dst, vmovl_u16(cps), fg, bg_flags); +} +#endif + TERM_ALWAYS_INLINE static inline void put_ascii_chunk(struct cell * restrict dst, const char * restrict src, int n, uint32_t fg, uint32_t bg, uint32_t flags) @@ -696,55 +707,15 @@ static inline void put_ascii_chunk(struct cell * restrict dst, const char * rest vcreate_u32(((uint64_t)flags << 32) | bg)); for (; k + 8 <= n; k += 8) { uint16x8_t cp16 = vmovl_u8(vld1_u8((const uint8_t *)src + k)); - uint32x4_t cp_lo = vmovl_u16(vget_low_u16(cp16)); - uint32x4_t cp_hi = vmovl_u16(vget_high_u16(cp16)); - - uint32x4_t c_fg_01 = vzip1q_u32(cp_lo, fg4); - uint32x4_t c_fg_23 = vzip2q_u32(cp_lo, fg4); - uint32x4_t c_fg_45 = vzip1q_u32(cp_hi, fg4); - uint32x4_t c_fg_67 = vzip2q_u32(cp_hi, fg4); - - uint32x4_t cell0 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell1 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell2 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4_t cell3 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4_t cell4 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_45, (uint64x2_t)bg_flags); - uint32x4_t cell5 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_45, (uint64x2_t)bg_flags); - uint32x4_t cell6 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_67, (uint64x2_t)bg_flags); - uint32x4_t cell7 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_67, (uint64x2_t)bg_flags); - - uint32x4x4_t q0 = {{ cell0, cell1, cell2, cell3 }}; - uint32x4x4_t q1 = {{ cell4, cell5, cell6, cell7 }}; - vst1q_u32_x4((uint32_t *)(void *)(dst + k + 0), q0); - vst1q_u32_x4((uint32_t *)(void *)(dst + k + 4), q1); - } - for (; k + 4 <= n; k += 4) { - uint32_t bytes; - memcpy(&bytes, src + k, sizeof(bytes)); - uint16x4_t cp16 = vget_low_u16(vmovl_u8(vreinterpret_u8_u32(vdup_n_u32(bytes)))); - uint32x4_t cp_lo = vmovl_u16(cp16); - uint32x4_t c_fg_01 = vzip1q_u32(cp_lo, fg4); - uint32x4_t c_fg_23 = vzip2q_u32(cp_lo, fg4); - uint32x4_t cell0 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell1 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell2 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4_t cell3 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4x4_t q0 = {{ cell0, cell1, cell2, cell3 }}; - vst1q_u32_x4((uint32_t *)(void *)(dst + k + 0), q0); + store_ascii_quad_neon(dst + k, vmovl_u16(vget_low_u16(cp16)), fg4, bg_flags); + store_ascii_quad_neon(dst + k + 4, vmovl_u16(vget_high_u16(cp16)), fg4, bg_flags); + } + if (k + 4 <= n) { + put_ascii_quad_neon(dst + k, src + k, fg4, bg_flags); + k += 4; } if (k < n && n >= 4) { - uint32_t bytes; - memcpy(&bytes, src + n - 4, sizeof(bytes)); - uint16x4_t cp16 = vget_low_u16(vmovl_u8(vreinterpret_u8_u32(vdup_n_u32(bytes)))); - uint32x4_t cp_lo = vmovl_u16(cp16); - uint32x4_t c_fg_01 = vzip1q_u32(cp_lo, fg4); - uint32x4_t c_fg_23 = vzip2q_u32(cp_lo, fg4); - uint32x4_t cell0 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell1 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_01, (uint64x2_t)bg_flags); - uint32x4_t cell2 = (uint32x4_t)vzip1q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4_t cell3 = (uint32x4_t)vzip2q_u64((uint64x2_t)c_fg_23, (uint64x2_t)bg_flags); - uint32x4x4_t q0 = {{ cell0, cell1, cell2, cell3 }}; - vst1q_u32_x4((uint32_t *)(void *)(dst + n - 4), q0); + put_ascii_quad_neon(dst + n - 4, src + n - 4, fg4, bg_flags); k = n; } #elif defined(__x86_64__) diff --git a/src/palette.c b/src/palette.c index cee5bad..610fe52 100644 --- a/src/palette.c +++ b/src/palette.c @@ -133,19 +133,13 @@ static uint32_t *palette_slot(struct term_theme *p, const char *key, const char *digits = key + 5; if (!*digits || (*digits == '0' && digits[1])) return NULL; - int i; - if (!digits[1]) { - if (digits[0] < '0' || digits[0] > '9') + unsigned i = 0; + for (; *digits; digits++) { + if ((unsigned)(*digits - '0') > 9) return NULL; - i = digits[0] - '0'; - } else if (!digits[2]) { - if (digits[0] < '0' || digits[0] > '9' || digits[1] < '0' || digits[1] > '9') - return NULL; - i = (digits[0] - '0') * 10 + (digits[1] - '0'); + i = i * 10 + (unsigned)(*digits - '0'); if (i > 15) return NULL; - } else { - return NULL; } *fallback = builtin_theme.ansi[i]; return &p->ansi[i]; @@ -179,29 +173,17 @@ static bool parse_hex_channel(const char *s, size_t len, uint8_t *out) *out = (uint8_t)((d0 << 4) | d1); return true; } - if (len == 1) { - int d = term_hex_nib(s[0]); + if (len < 1 || len > 4) + return false; + unsigned value = 0; + for (size_t i = 0; i < len; i++) { + int d = term_hex_nib(s[i]); if (d < 0) return false; - *out = (uint8_t)(d * 17); - return true; + value = (value << 4) | (unsigned)d; } - if (len == 4) { - int d0 = term_hex_nib(s[0]), d1 = term_hex_nib(s[1]); - int d2 = term_hex_nib(s[2]), d3 = term_hex_nib(s[3]); - if ((d0 | d1 | d2 | d3) < 0) - return false; - *out = (uint8_t)((((unsigned)d0 << 12) | ((unsigned)d1 << 8) | ((unsigned)d2 << 4) | (unsigned)d3) * 255 / 65535); - return true; - } - if (len == 3) { - int d0 = term_hex_nib(s[0]), d1 = term_hex_nib(s[1]), d2 = term_hex_nib(s[2]); - if ((d0 | d1 | d2) < 0) - return false; - *out = (uint8_t)((((unsigned)d0 << 8) | ((unsigned)d1 << 4) | (unsigned)d2) * 255 / 4095); - return true; - } - return false; + *out = (uint8_t)(value * 255 / ((1u << (4 * len)) - 1)); + return true; } static bool parse_fraction(const char *s, size_t len, uint8_t *out) @@ -306,19 +288,13 @@ bool term_color_parse(const char *input, uint32_t *out) } if (ci != 3) return false; - uint8_t c0, c1, c2; - if (intensity) { - if (!parse_fraction(channels[0], chlens[0], &c0) || - !parse_fraction(channels[1], chlens[1], &c1) || - !parse_fraction(channels[2], chlens[2], &c2)) - return false; - } else { - if (!parse_hex_channel(channels[0], chlens[0], &c0) || - !parse_hex_channel(channels[1], chlens[1], &c1) || - !parse_hex_channel(channels[2], chlens[2], &c2)) - return false; + uint8_t c[3]; + for (int j = 0; j < 3; j++) { + bool ok = intensity ? parse_fraction(channels[j], chlens[j], &c[j]) : + parse_hex_channel(channels[j], chlens[j], &c[j]); + if (!ok) return false; } - *out = term_rgb(c0, c1, c2); + *out = term_rgb(c[0], c[1], c[2]); return true; } diff --git a/src/screen.c b/src/screen.c index d9dfe31..bd65564 100644 --- a/src/screen.c +++ b/src/screen.c @@ -560,6 +560,21 @@ void term_screen_erase(struct term *t, int mode) } } +/* Reset a recycled row's contents and identity; callers choose its version. */ +static inline void reset_scrolled_row(struct term *t, struct srow *row, bool def) +{ + row->kind = ROW_CELLS; + row->uid = ++t->next_row_uid; + if (def) { + row->len = 0; + row->flags = 0; + } else { + fill_cells(row->cells, t->cols, t->cur_fg, t->cur_bg); + row->len = (uint16_t)t->cols; + row->flags = RF_TAIL_PADDED; + } +} + static void rotate_and_clear_srows(struct term *t, struct srow *base, int height, int count, bool down, bool def) { @@ -568,50 +583,18 @@ static void rotate_and_clear_srows(struct term *t, struct srow *base, int height if (!saved) return; - if (!down) { - memcpy(saved, base, (size_t)count * sizeof(struct srow)); - memmove(base, base + count, (size_t)(height - count) * sizeof(struct srow)); - memcpy(base + height - count, saved, (size_t)count * sizeof(struct srow)); - - for (int i = 0; i < height - count; i++) - base[i].version++; - - for (int i = 0; i < count; i++) { - struct srow *dst = &base[height - count + i]; - dst->kind = ROW_CELLS; - dst->uid = ++t->next_row_uid; - dst->version = 1; - if (def) { - dst->len = 0; - dst->flags = 0; - } else { - fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); - dst->len = (uint16_t)t->cols; - dst->flags = RF_TAIL_PADDED; - } - } - } else { - memcpy(saved, base + height - count, (size_t)count * sizeof(struct srow)); - memmove(base + count, base, (size_t)(height - count) * sizeof(struct srow)); - memcpy(base, saved, (size_t)count * sizeof(struct srow)); - - for (int i = 0; i < height - count; i++) - base[count + i].version++; - - for (int i = 0; i < count; i++) { - struct srow *dst = &base[i]; - dst->kind = ROW_CELLS; - dst->uid = ++t->next_row_uid; - dst->version = 1; - if (def) { - dst->len = 0; - dst->flags = 0; - } else { - fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); - dst->len = (uint16_t)t->cols; - dst->flags = RF_TAIL_PADDED; - } - } + int kept = down ? count : 0; + int cleared = down ? 0 : height - count; + memcpy(saved, base + (down ? height - count : 0), (size_t)count * sizeof(struct srow)); + memmove(base + kept, base + (down ? 0 : count), + (size_t)(height - count) * sizeof(struct srow)); + memcpy(base + cleared, saved, (size_t)count * sizeof(struct srow)); + for (int i = 0; i < height - count; i++) + base[kept + i].version++; + for (int i = 0; i < count; i++) { + struct srow *dst = &base[cleared + i]; + reset_scrolled_row(t, dst, def); + dst->version = 1; } if (saved != tmp) free(saved); @@ -659,17 +642,8 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, if (history) { screen_new_row(t, row); } else { - row->kind = ROW_CELLS; - row->uid = ++t->next_row_uid; + reset_scrolled_row(t, row, def); row->version++; - if (def) { - row->len = 0; - row->flags = 0; - } else { - fill_cells(row->cells, t->cols, t->cur_fg, t->cur_bg); - row->len = (uint16_t)t->cols; - row->flags = RF_TAIL_PADDED; - } } } int new_top = orig_top + count; @@ -696,17 +670,8 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, int p = orig_top + (rows - count) + i; if (p >= rows) p -= rows; struct srow *row = &s->slot[p]; - row->kind = ROW_CELLS; - row->uid = ++t->next_row_uid; + reset_scrolled_row(t, row, def); row->version = 1; - if (def) { - row->len = 0; - row->flags = 0; - } else { - fill_cells(row->cells, t->cols, t->cur_fg, t->cur_bg); - row->len = (uint16_t)t->cols; - row->flags = RF_TAIL_PADDED; - } } int new_top = orig_top - count; if (new_top < 0) new_top += rows; @@ -730,36 +695,11 @@ void term_screen_scroll_region(struct term *t, int top, int bottom, int count, if (count == height) { int start = phys_row(t, top); - if (start + height <= s->rows) { - for (int i = 0; i < height; i++) { - struct srow *dst = &s->slot[start + i]; - dst->kind = ROW_CELLS; - dst->uid = ++t->next_row_uid; - dst->version++; - if (def) { - dst->len = 0; - dst->flags = 0; - } else { - fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); - dst->len = (uint16_t)t->cols; - dst->flags = RF_TAIL_PADDED; - } - } - } else { - for (int y = top; y <= bottom; y++) { - struct srow *dst = screen_get_row(s, y); - dst->kind = ROW_CELLS; - dst->uid = ++t->next_row_uid; - dst->version++; - if (def) { - dst->len = 0; - dst->flags = 0; - } else { - fill_cells(dst->cells, t->cols, t->cur_fg, t->cur_bg); - dst->len = (uint16_t)t->cols; - dst->flags = RF_TAIL_PADDED; - } - } + for (int i = 0; i < height; i++) { + int p = start + i; + struct srow *dst = &s->slot[p >= s->rows ? p - s->rows : p]; + reset_scrolled_row(t, dst, def); + dst->version++; } } else { int start = phys_row(t, top); diff --git a/src/text_run.c b/src/text_run.c index bf0e8a9..028b160 100644 --- a/src/text_run.c +++ b/src/text_run.c @@ -53,17 +53,11 @@ static TERM_MAYBE_UNUSED int width_run_find(uint32_t cp) static TERM_MAYBE_UNUSED uint16_t width_cache_covers(const struct width_cache *wc, const uint32_t *cp) { - uint32_t lo0 = wc->lo[0], span0 = wc->span[0]; - uint32_t lo1 = wc->lo[1], span1 = wc->span[1]; - uint32_t lo2 = wc->lo[2], span2 = wc->span[2]; - uint32_t lo3 = wc->lo[3], span3 = wc->span[3]; uint16_t covered = 0; for (int l = 0; l < 16; l++) { - uint32_t c = cp[l]; - bool hit = ((c - lo0 <= span0) | - (c - lo1 <= span1) | - (c - lo2 <= span2) | - (c - lo3 <= span3)); + bool hit = false; + for (int k = 0; k < TERM_WIDTH_SLOTS; k++) + hit |= cp[l] - wc->lo[k] <= wc->span[k]; covered |= (uint16_t)(hit << l); } return covered; @@ -146,24 +140,20 @@ TERM_TARGET_AVX512 static inline struct width_ranges width_ranges_load(const struct width_cache *wc) { struct width_ranges r; - r.lo[0] = _mm512_set1_epi32((int)wc->lo[0]); - r.span[0] = _mm512_set1_epi32((int)wc->span[0]); - r.lo[1] = _mm512_set1_epi32((int)wc->lo[1]); - r.span[1] = _mm512_set1_epi32((int)wc->span[1]); - r.lo[2] = _mm512_set1_epi32((int)wc->lo[2]); - r.span[2] = _mm512_set1_epi32((int)wc->span[2]); - r.lo[3] = _mm512_set1_epi32((int)wc->lo[3]); - r.span[3] = _mm512_set1_epi32((int)wc->span[3]); + for (int k = 0; k < TERM_WIDTH_SLOTS; k++) { + r.lo[k] = _mm512_set1_epi32((int)wc->lo[k]); + r.span[k] = _mm512_set1_epi32((int)wc->span[k]); + } return r; } TERM_TARGET_AVX512 static inline __mmask16 width_ranges_cover(const struct width_ranges *r, __m512i cps) { - return _mm512_cmple_epu32_mask(_mm512_sub_epi32(cps, r->lo[0]), r->span[0]) - | _mm512_cmple_epu32_mask(_mm512_sub_epi32(cps, r->lo[1]), r->span[1]) - | _mm512_cmple_epu32_mask(_mm512_sub_epi32(cps, r->lo[2]), r->span[2]) - | _mm512_cmple_epu32_mask(_mm512_sub_epi32(cps, r->lo[3]), r->span[3]); + __mmask16 covered = 0; + for (int k = 0; k < TERM_WIDTH_SLOTS; k++) + covered |= _mm512_cmple_epu32_mask(_mm512_sub_epi32(cps, r->lo[k]), r->span[k]); + return covered; } TERM_TARGET_AVX512 @@ -189,6 +179,31 @@ static inline void store_narrow_cells(uint64_t * restrict dst, __m512i base, __m #undef TERM_NARROW_QUAD } +/* Both cell and history writers validate and classify the same UTF-8 block. */ +TERM_TARGET_AVX512 +static inline bool classify_3byte_block(__m512i in, struct width_ranges *ranges, + struct width_cache *wc, __m512i *cps) +{ + if (TERM_EXPECT(_mm512_test_epi8_mask( + _mm512_xor_si512(in, _mm512_loadu_si512(utf8_3byte_want)), + _mm512_loadu_si512(utf8_3byte_bits)) & UTF8_3BYTE_SPAN, 0)) + return false; + *cps = utf8_3byte_decode(_mm512_permutexvar_epi8(_mm512_loadu_si512(utf8_3byte_shuffle), in)); + if (_mm512_cmplt_epu32_mask(*cps, _mm512_set1_epi32((int)UTF8_3BYTE_MIN)) || + _mm512_cmple_epu32_mask( + _mm512_sub_epi32(*cps, _mm512_set1_epi32((int)UTF8_SURROGATE_LO)), + _mm512_set1_epi32((int)(UTF8_SURROGATE_HI - UTF8_SURROGATE_LO)))) + return false; + __mmask16 covered = width_ranges_cover(ranges, *cps); + if (TERM_EXPECT(covered != 0xFFFF || wc->klass == 0, 0)) { + uint32_t cp[16]; + _mm512_storeu_si512(cp, *cps); + if (!width_cache_extend(wc, cp, covered)) return false; + *ranges = width_ranges_load(wc); + } + return true; +} + TERM_TARGET_AVX512 static TERM_NOINLINE int decode_3byte_blocks(uint64_t * restrict dst, const char * restrict buf, int len, int cols_left, @@ -197,33 +212,14 @@ decode_3byte_blocks(uint64_t * restrict dst, const char * restrict buf, int len, int * restrict out_cols, uint32_t * restrict last_cp) { struct width_ranges ranges = width_ranges_load(wc); - __m512i shuffle = _mm512_loadu_si512(utf8_3byte_shuffle); - __m512i want = _mm512_loadu_si512(utf8_3byte_want); - __m512i bits = _mm512_loadu_si512(utf8_3byte_bits); uint32_t last = *last_cp; int i = 0, cx = 0; while (i + 48 <= len && cx + 32 <= cols_left && ((unsigned char)buf[i + 45] & UTF8_LEAD3_MASK) == UTF8_LEAD3_TAG) { __m512i in = _mm512_maskz_loadu_epi8(UTF8_3BYTE_SPAN, buf + i); - if (TERM_EXPECT(_mm512_test_epi8_mask(_mm512_xor_si512(in, want), bits) & - UTF8_3BYTE_SPAN, 0)) - break; - - __m512i cps = utf8_3byte_decode(_mm512_permutexvar_epi8(shuffle, in)); - if (_mm512_cmplt_epu32_mask(cps, _mm512_set1_epi32((int)UTF8_3BYTE_MIN)) || - _mm512_cmple_epu32_mask( - _mm512_sub_epi32(cps, _mm512_set1_epi32((int)UTF8_SURROGATE_LO)), - _mm512_set1_epi32((int)(UTF8_SURROGATE_HI - UTF8_SURROGATE_LO)))) - break; - __mmask16 covered = width_ranges_cover(&ranges, cps); - if (TERM_EXPECT(covered != 0xFFFF || wc->klass == 0, 0)) { - uint32_t cp[16]; - _mm512_storeu_si512(cp, cps); - if (!width_cache_extend(wc, cp, covered)) - break; - ranges = width_ranges_load(wc); - } + __m512i cps; + if (!classify_3byte_block(in, &ranges, wc, &cps)) break; if (wc->klass == 2) { store_wide_cells(dst, base_wide_pair, cps); @@ -258,9 +254,6 @@ text_3byte_blocks(uint8_t * restrict dst, const char * restrict buf, int len, in 0, 36, 37, 38, 0, 39, 40, 41, 0, 42, 43, 44, 0, 45, 46, 47, }; struct width_ranges ranges = width_ranges_load(wc); - __m512i shuffle = _mm512_loadu_si512(utf8_3byte_shuffle); - __m512i want = _mm512_loadu_si512(utf8_3byte_want); - __m512i bits = _mm512_loadu_si512(utf8_3byte_bits); __m512i idx = _mm512_loadu_si512(spread); __m512i prefix = _mm512_set1_epi8((char)0xF5); uint32_t last = *last_cp; @@ -269,23 +262,8 @@ text_3byte_blocks(uint8_t * restrict dst, const char * restrict buf, int len, in while (i + 48 <= len && cx + 32 <= cols_left && ((unsigned char)buf[i + 45] & UTF8_LEAD3_MASK) == UTF8_LEAD3_TAG) { __m512i in = _mm512_maskz_loadu_epi8(UTF8_3BYTE_SPAN, buf + i); - if (TERM_EXPECT(_mm512_test_epi8_mask(_mm512_xor_si512(in, want), bits) & - UTF8_3BYTE_SPAN, 0)) - break; - __m512i cps = utf8_3byte_decode(_mm512_permutexvar_epi8(shuffle, in)); - if (_mm512_cmplt_epu32_mask(cps, _mm512_set1_epi32((int)UTF8_3BYTE_MIN)) || - _mm512_cmple_epu32_mask( - _mm512_sub_epi32(cps, _mm512_set1_epi32((int)UTF8_SURROGATE_LO)), - _mm512_set1_epi32((int)(UTF8_SURROGATE_HI - UTF8_SURROGATE_LO)))) - break; - __mmask16 covered = width_ranges_cover(&ranges, cps); - if (TERM_EXPECT(covered != 0xFFFF || wc->klass == 0, 0)) { - uint32_t cp[16]; - _mm512_storeu_si512(cp, cps); - if (!width_cache_extend(wc, cp, covered)) - break; - ranges = width_ranges_load(wc); - } + __m512i cps; + if (!classify_3byte_block(in, &ranges, wc, &cps)) break; if (wc->klass == 2) { __m512i out = _mm512_mask_mov_epi8(_mm512_permutexvar_epi8(idx, in), 0x1111111111111111ull, prefix); diff --git a/tests/osc_test.c b/tests/osc_test.c index 4d0e32b..91b51d6 100644 --- a/tests/osc_test.c +++ b/tests/osc_test.c @@ -397,9 +397,14 @@ static void test_palette_and_theme(void) assert(term_color_parse("123456", &c) && c == 0xFF123456u); assert(term_color_parse("abc", &c) && c == 0xFFAABBCCu); assert(term_color_parse("#123456789abc", &c) && c == 0xFF12569au); + assert(term_color_parse("#123456789", &c) && c == 0xFF124578u); + assert(!term_color_parse("#12g456789", &c)); + assert(!term_color_parse("#123456789abg", &c)); /* rgb: / rgbi: */ assert(term_color_parse("rgb:ff/80/00", &c) && c == 0xFFFF8000u); + assert(term_color_parse("rgb:f/123/4567", &c) && c == 0xFFFF1245u); + assert(!term_color_parse("rgb:fffff/0/0", &c)); assert(term_color_parse("rgbi:1.0/0.0/0.5", &c) && c == 0xFFFF0080u); assert(term_color_parse("rgbi:.25/.5/.75", &c)); @@ -424,6 +429,9 @@ static void test_palette_and_theme(void) assert(term_theme_set_key(&theme, "color0", "") && theme.ansi[0] == 0xFF585B70u); assert(!term_theme_set_key(&theme, "color16", "#000000")); assert(!term_theme_set_key(&theme, "color01", "#000000")); + assert(!term_theme_set_key(&theme, "color", "#000000")); + assert(!term_theme_set_key(&theme, "color1x", "#000000")); + assert(!term_theme_set_key(&theme, "color100000000000000000000", "#000000")); assert(!term_theme_set_key(&theme, "invalid", "#000000")); assert(!term_theme_set_key(NULL, "foreground", "#000000")); assert(!term_theme_set_key(&theme, NULL, "#000000")); diff --git a/tests/term_test.c b/tests/term_test.c index 02c050e..3721720 100644 --- a/tests/term_test.c +++ b/tests/term_test.c @@ -1173,6 +1173,37 @@ static void test_full_width_line_endings(void) term_free(t); } +/* CRLFs at every mask boundary, followed by complete or interrupted lines. */ +static void test_batched_line_masks(void) +{ + static const char *const tails[] = { + "\r\nx\r\ny\r\n", "\r\nx\ny\r\n", "\r\nx\r\ny\t", + "\r\nx\r\ny\rQ", "\r\nx\033[32my\r\n", + }; + for (int offset = 0; offset <= 65; offset++) { + for (size_t k = 0; k < sizeof(tails) / sizeof(tails[0]); k++) { + char input[256]; + memset(input, 'a', (size_t)offset); + size_t n = (size_t)offset + strlen(tails[k]); + memcpy(input + offset, tails[k], strlen(tails[k])); + memset(input + n, 'z', 128); + n += 128; + struct term *bulk = tt_new(80, 3), *bytes = tt_new(80, 3); + /* A nondefault pen exercises the queued bulk path, not streaming. */ + write_both(bulk, bytes, "\033[3;1H\033[31m"); + tt_write(bulk, input, n); + for (size_t i = 0; i < n; i++) tt_write(bytes, input + i, 1); + write_both(bulk, bytes, "\033[b"); + assert_same_screen(bulk, bytes); + term_scroll_to(bulk, INT_MAX); + term_scroll_to(bytes, INT_MAX); + assert_same_screen(bulk, bytes); + term_free(bulk); + term_free(bytes); + } + } +} + /* Compare the screen, cursor, and scrollback for each input mode. * * Input mode | Byte-wise | 64 KiB bulk | 512 KiB single write @@ -1820,6 +1851,7 @@ int main(void) test_invalid_utf8_runs(); test_c0_ends_utf8_sequence(); test_full_width_line_endings(); + test_batched_line_masks(); test_bulk_line_flood(); test_ascii_flood_edits(); test_margin_linefeed_bursts(); |
