summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-14 17:36:22 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-14 17:36:22 +0300
commit13bf7edad251df9943bd75665a3510535849b0ac (patch)
tree4597aae71940edac0b7d8b416c4803cd72ac5345
parent513bf031a589d9e2a1a2730adf56f95e5e316a06 (diff)
Deduplicate scroll and SIMD paths while preserving performance
-rw-r--r--src/core.c95
-rw-r--r--src/kernels.h151
-rw-r--r--src/palette.c60
-rw-r--r--src/screen.c128
-rw-r--r--src/text_run.c102
-rw-r--r--tests/osc_test.c8
-rw-r--r--tests/term_test.c32
7 files changed, 213 insertions, 363 deletions
diff --git a/src/core.c b/src/core.c
index 7bca76e..b352be1 100644
--- a/src/core.c
+++ b/src/core.c
@@ -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();