diff --git a/kitty/simd-string-impl.h b/kitty/simd-string-impl.h index 7842447ae..eae96fe0d 100644 --- a/kitty/simd-string-impl.h +++ b/kitty/simd-string-impl.h @@ -286,14 +286,15 @@ FUNC(find_either_of_two_bytes)(const uint8_t *haystack, const size_t sz, const u static inline void FUNC(output_plain_ascii)(UTF8Decoder *d, integer_t vec, size_t src_sz) { + utf8_decoder_ensure_capacity(d, src_sz); #if KITTY_SIMD_LEVEL == 128 - for (const uint32_t *limit = d->output + src_sz, *p = d->output; p < limit; p += output_increment) { + for (const uint32_t *p = d->output.storage + d->output.pos, *limit = p + src_sz; p < limit; p += output_increment) { const integer_t unpacked = extract_lower_quarter_as_chars(vec); store_unaligned((integer_t*)p, unpacked); vec = shift_right_by_bytes128(vec, output_increment); } #else - const uint32_t *limit = d->output + src_sz, *p = d->output; + const uint32_t *p = d->output.storage + d->output.pos, *limit = p + src_sz; simde__m128i x = simde_mm256_extracti128_si256(vec, 0); integer_t unpacked = extract_lower_half_as_chars(x); store_unaligned((integer_t*)p, unpacked); p += output_increment; @@ -313,13 +314,14 @@ FUNC(output_plain_ascii)(UTF8Decoder *d, integer_t vec, size_t src_sz) { } } #endif - d->output_sz += src_sz; + d->output.pos += src_sz; } static inline void FUNC(output_unicode)(UTF8Decoder *d, integer_t output1, integer_t output2, integer_t output3, const size_t num_codepoints) { + utf8_decoder_ensure_capacity(d, 64); #if KITTY_SIMD_LEVEL == 128 - for (const uint32_t *limit = d->output + num_codepoints, *p = d->output; p < limit; p += output_increment) { + for (const uint32_t *p = d->output.storage + d->output.pos, *limit = p + num_codepoints; p < limit; p += output_increment) { const integer_t unpacked1 = extract_lower_quarter_as_chars(output1); const integer_t unpacked2 = shift_right_by_one_byte(extract_lower_quarter_as_chars(output2)); const integer_t unpacked3 = shift_right_by_two_bytes(extract_lower_quarter_as_chars(output3)); @@ -330,8 +332,8 @@ FUNC(output_unicode)(UTF8Decoder *d, integer_t output1, integer_t output2, integ output3 = shift_right_by_bytes128(output3, output_increment); } #else - const uint32_t *limit = d->output + num_codepoints; - uint32_t *p = d->output; + uint32_t *p = d->output.storage + d->output.pos; + const uint32_t *limit = p + num_codepoints; simde__m128i x1, x2, x3; #define chunk() { \ const integer_t unpacked1 = extract_lower_half_as_chars(x1); \ @@ -356,7 +358,7 @@ FUNC(output_unicode)(UTF8Decoder *d, integer_t output1, integer_t output2, integ #undef extract #undef shift #endif - d->output_sz += num_codepoints; + d->output.pos += num_codepoints; } #undef output_increment @@ -378,12 +380,12 @@ sum_bytes_128(simde__m128i v) { const uint8_t ch = src[pos++]; \ switch (decode_utf8(&d->state.cur, &d->state.codep, ch)) { \ case UTF8_ACCEPT: \ - d->output[d->output_sz++] = d->state.codep; \ + d->output.storage[d->output.pos++] = d->state.codep; \ break; \ case UTF8_REJECT: { \ const bool prev_was_accept = d->state.prev == UTF8_ACCEPT; \ zero_at_ptr(&d->state); \ - d->output[d->output_sz++] = 0xfffd; \ + d->output.storage[d->output.pos++] = 0xfffd; \ if (!prev_was_accept) { \ pos--; \ continue; /* so that prev is correct */ \ @@ -395,7 +397,8 @@ sum_bytes_128(simde__m128i v) { static inline size_t scalar_decode_to_accept(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { size_t pos = 0; - while (pos < src_sz && d->output_sz < arraysz(d->output) && d->state.cur != UTF8_ACCEPT) { + utf8_decoder_ensure_capacity(d, src_sz); + while (pos < src_sz && d->state.cur != UTF8_ACCEPT) { do_one_byte } return pos; @@ -404,7 +407,8 @@ scalar_decode_to_accept(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { static inline size_t scalar_decode_all(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { size_t pos = 0; - while (pos < src_sz && d->output_sz < arraysz(d->output)) { + utf8_decoder_ensure_capacity(d, src_sz); + while (pos < src_sz) { do_one_byte } return pos; @@ -415,7 +419,7 @@ bool FUNC(utf8_decode_to_esc)(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { // Based on the algorithm described in: https://woboq.com/blog/utf-8-processing-using-simd.html - d->output_sz = 0; d->num_consumed = 0; + d->output.pos = 0; d->num_consumed = 0; if (d->state.cur != UTF8_ACCEPT) { // Finish the trailing sequence only, we will be called again to process the rest allows use of aligned stores since output // is not pre-filled. diff --git a/kitty/simd-string.c b/kitty/simd-string.c index fa714533a..5bdc6630c 100644 --- a/kitty/simd-string.c +++ b/kitty/simd-string.c @@ -31,22 +31,23 @@ find_either_of_two_bytes(const uint8_t *haystack, const size_t sz, const uint8_t static bool utf8_decode_to_esc_scalar(UTF8Decoder *d, const uint8_t *src, const size_t src_sz) { - d->output_sz = 0; d->num_consumed = 0; - while (d->num_consumed < src_sz && d->output_sz < arraysz(d->output)) { + d->output.pos = 0; d->num_consumed = 0; + utf8_decoder_ensure_capacity(d, src_sz); + while (d->num_consumed < src_sz) { const uint8_t ch = src[d->num_consumed++]; if (ch == 0x1b) { - if (d->state.cur != UTF8_ACCEPT) d->output[d->output_sz++] = 0xfffd; + if (d->state.cur != UTF8_ACCEPT) d->output.storage[d->output.pos++] = 0xfffd; zero_at_ptr(&d->state); return true; } else { switch(decode_utf8(&d->state.cur, &d->state.codep, ch)) { case UTF8_ACCEPT: - d->output[d->output_sz++] = d->state.codep; + d->output.storage[d->output.pos++] = d->state.codep; break; case UTF8_REJECT: { const bool prev_was_accept = d->state.prev == UTF8_ACCEPT; zero_at_ptr(&d->state); - d->output[d->output_sz++] = 0xfffd; + d->output.storage[d->output.pos++] = 0xfffd; if (!prev_was_accept && d->num_consumed) { d->num_consumed--; continue; // so that prev is correct @@ -92,13 +93,14 @@ test_utf8_decode_to_sentinel(PyObject *self UNUSED, PyObject *args) { while (p < src_sz && !found_sentinel) { found_sentinel = func(&d, src + p, src_sz - p); p += d.num_consumed; - if (d.output_sz) { - RAII_PyObject(temp, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, d.output, d.output_sz)); + if (d.output.pos) { + RAII_PyObject(temp, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, d.output.storage, d.output.pos)); PyObject *t = PyUnicode_Concat(ans, temp); Py_DECREF(ans); ans = t; } } + utf8_decoder_free(&d); return Py_BuildValue("OO", found_sentinel ? Py_True : Py_False, ans); } // }}} diff --git a/kitty/simd-string.h b/kitty/simd-string.h index 761205c4b..4a9140d33 100644 --- a/kitty/simd-string.h +++ b/kitty/simd-string.h @@ -14,13 +14,24 @@ typedef void (*control_byte_callback)(void *data, uint8_t ch); typedef void (*output_chars_callback)(void *data, const uint32_t *chars, unsigned count); typedef struct UTF8Decoder { - alignas(512/8) uint32_t output[512/8]; // we can process at most 512 bits of input (AVX512) so we get at most 64 chars of output - unsigned output_sz, num_consumed; - + struct { uint32_t *storage; unsigned pos, capacity; } output; struct { uint32_t cur, prev, codep; } state; + unsigned num_consumed; } UTF8Decoder; static inline void utf8_decoder_reset(UTF8Decoder *self) { zero_at_ptr(&self->state); } bool utf8_decode_to_esc(UTF8Decoder *d, const uint8_t *src, size_t src_sz); +static inline void utf8_decoder_ensure_capacity(UTF8Decoder *d, unsigned sz) { + if (d->output.capacity + d->output.pos < sz) { + d->output.capacity = d->output.pos + sz + 4096; + d->output.storage = realloc(d->output.storage, d->output.capacity * sizeof(d->output.storage[0]) + 64); // allow for overwrite of upto 64 bytes + if (!d->output.storage) fatal("Output of memory for UTF8Decoder output buffer at capacity: %u", d->output.capacity); + } +} +static inline void utf8_decoder_free(UTF8Decoder *d) { + free(d->output.storage); + zero_at_ptr(&(d->output)); +} + // Pass a PyModule PyObject* as the argument. Must be called once at application startup bool init_simd(void* module); diff --git a/kitty/vt-parser.c b/kitty/vt-parser.c index bbfac77e8..3d8a37a4a 100644 --- a/kitty/vt-parser.c +++ b/kitty/vt-parser.c @@ -188,7 +188,8 @@ typedef struct ParsedCSI { } ParsedCSI; typedef struct PS { - alignas(BUF_EXTRA) UTF8Decoder utf8_decoder; + alignas(BUF_EXTRA) uint8_t buf[BUF_SZ + BUF_EXTRA]; + UTF8Decoder utf8_decoder; id_type window_id; @@ -204,7 +205,6 @@ typedef struct PS { // The buffer struct { size_t consumed, pos, sz; } read; struct { size_t offset, sz, pending; } write; - alignas(BUF_EXTRA) uint8_t buf[BUF_SZ + BUF_EXTRA]; } PS; static void @@ -228,9 +228,9 @@ consume_normal(PS *self) { do { const bool sentinel_found = utf8_decode_to_esc(&self->utf8_decoder, self->buf + self->read.pos, self->read.sz - self->read.pos); self->read.pos += self->utf8_decoder.num_consumed; - if (self->utf8_decoder.output_sz) { - REPORT_DRAW(self->utf8_decoder.output, self->utf8_decoder.output_sz); - screen_draw_text(self->screen, self->utf8_decoder.output, self->utf8_decoder.output_sz); + if (self->utf8_decoder.output.pos) { + REPORT_DRAW(self->utf8_decoder.output.storage, self->utf8_decoder.output.pos); + screen_draw_text(self->screen, self->utf8_decoder.output.storage, self->utf8_decoder.output.pos); } if (sentinel_found) { SET_STATE(ESC); break; } } while (self->read.pos < self->read.sz); @@ -1480,6 +1480,7 @@ void free_vt_parser(Parser* self) { if (self->state) { PS *s = (PS*)self->state; + utf8_decoder_free(&s->utf8_decoder); pthread_mutex_destroy(&s->lock); free(self->state); self->state = NULL; } @@ -1540,10 +1541,6 @@ alloc_vt_parser(id_type window_id) { } memset(self->state, 0, sizeof(PS)); PS *state = (PS*)self->state; - if ((intptr_t)state->utf8_decoder.output % BUF_EXTRA != 0) { - Py_CLEAR(self); PyErr_SetString(PyExc_TypeError, "UTF8Decoder is not aligned"); - return NULL; - } if ((intptr_t)state->buf % BUF_EXTRA != 0) { Py_CLEAR(self); PyErr_SetString(PyExc_TypeError, "PS->buf is not aligned"); return NULL;