mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-09 21:55:29 +02:00
Allow unbounded output in UTF8Decoder
This will allow us to eventually decode more than a single vector's worth in a fast inner loop
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user