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:
Kovid Goyal
2024-02-01 12:15:37 +05:30
parent d987ffe49a
commit 9cb9373274
4 changed files with 45 additions and 31 deletions

View File

@@ -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.

View File

@@ -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);
}
// }}}

View File

@@ -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);

View File

@@ -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;