simdutf 9.3.3
Unicode at GB/s.
Loading...
Searching...
No Matches
utf8.h
1#ifndef SIMDUTF_UTF8_H
2#define SIMDUTF_UTF8_H
3
4#include <cstring>
5
6namespace simdutf {
7namespace scalar {
8namespace {
9namespace utf8 {
10
11// credit: based on code from Google Fuchsia (Apache Licensed)
12template <class BytePtr>
13simdutf_constexpr23 simdutf_warn_unused bool validate(BytePtr data,
14 size_t len) noexcept {
15 static_assert(
16 std::is_same<typename std::decay<decltype(*data)>::type, uint8_t>::value,
17 "dereferencing the data pointer must result in a uint8_t");
18 uint64_t pos = 0;
19 uint32_t code_point = 0;
20 while (pos < len) {
21 uint64_t next_pos;
22#if SIMDUTF_CPLUSPLUS23
23 if !consteval
24#endif
25 { // check if the next 16 bytes are ascii.
26 next_pos = pos + 16;
27 if (next_pos <= len) { // if it is safe to read 16 more bytes, check
28 // that they are ascii
29 uint64_t v1{};
30 std::memcpy(&v1, data + pos, sizeof(uint64_t));
31 uint64_t v2{};
32 std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
33 uint64_t v{v1 | v2};
34 if ((v & 0x8080808080808080) == 0) {
35 pos = next_pos;
36 continue;
37 }
38 }
39 }
40
41 unsigned char byte = data[pos];
42
43 while (byte < 0b10000000) {
44 if (++pos == len) {
45 return true;
46 }
47 byte = data[pos];
48 }
49
50 if ((byte & 0b11100000) == 0b11000000) {
51 next_pos = pos + 2;
52 if (next_pos > len) {
53 return false;
54 }
55 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
56 return false;
57 }
58 // range check
59 code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
60 if (code_point < 0x80) {
61 return false;
62 }
63 } else if ((byte & 0b11110000) == 0b11100000) {
64 next_pos = pos + 3;
65 if (next_pos > len) {
66 return false;
67 }
68 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
69 return false;
70 }
71 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
72 return false;
73 }
74 // range check
75 code_point = (byte & 0b00001111) << 12 |
76 (data[pos + 1] & 0b00111111) << 6 |
77 (data[pos + 2] & 0b00111111);
78 if ((code_point < 0x800) ||
79 (0xd7ff < code_point && code_point < 0xe000)) {
80 return false;
81 }
82 } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000
83 next_pos = pos + 4;
84 if (next_pos > len) {
85 return false;
86 }
87 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
88 return false;
89 }
90 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
91 return false;
92 }
93 if ((data[pos + 3] & 0b11000000) != 0b10000000) {
94 return false;
95 }
96 // range check
97 code_point =
98 (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
99 (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
100 if (code_point <= 0xffff || 0x10ffff < code_point) {
101 return false;
102 }
103 } else {
104 // we may have a continuation
105 return false;
106 }
107 pos = next_pos;
108 }
109 return true;
110}
111
112simdutf_really_inline simdutf_warn_unused bool validate(const char *buf,
113 size_t len) noexcept {
114 return validate(reinterpret_cast<const uint8_t *>(buf), len);
115}
116
117template <class BytePtr>
118simdutf_constexpr23 simdutf_warn_unused result
119validate_with_errors(BytePtr data, size_t len) noexcept {
120 static_assert(
121 std::is_same<typename std::decay<decltype(*data)>::type, uint8_t>::value,
122 "dereferencing the data pointer must result in a uint8_t");
123 size_t pos = 0;
124 uint32_t code_point = 0;
125 while (pos < len) {
126 // check of the next 16 bytes are ascii.
127 size_t next_pos = pos + 16;
128 if (next_pos <=
129 len) { // if it is safe to read 16 more bytes, check that they are ascii
130 uint64_t v1;
131 std::memcpy(&v1, data + pos, sizeof(uint64_t));
132 uint64_t v2;
133 std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
134 uint64_t v{v1 | v2};
135 if ((v & 0x8080808080808080) == 0) {
136 pos = next_pos;
137 continue;
138 }
139 }
140 unsigned char byte = data[pos];
141
142 while (byte < 0b10000000) {
143 if (++pos == len) {
144 return result(error_code::SUCCESS, len);
145 }
146 byte = data[pos];
147 }
148
149 if ((byte & 0b11100000) == 0b11000000) {
150 next_pos = pos + 2;
151 if (next_pos > len) {
152 return result(error_code::TOO_SHORT, pos);
153 }
154 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
155 return result(error_code::TOO_SHORT, pos);
156 }
157 // range check
158 code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
159 if (code_point < 0x80) {
160 return result(error_code::OVERLONG, pos);
161 }
162 } else if ((byte & 0b11110000) == 0b11100000) {
163 next_pos = pos + 3;
164 if (next_pos > len) {
165 return result(error_code::TOO_SHORT, pos);
166 }
167 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
168 return result(error_code::TOO_SHORT, pos);
169 }
170 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
171 return result(error_code::TOO_SHORT, pos);
172 }
173 // range check
174 code_point = (byte & 0b00001111) << 12 |
175 (data[pos + 1] & 0b00111111) << 6 |
176 (data[pos + 2] & 0b00111111);
177 if (code_point < 0x800) {
178 return result(error_code::OVERLONG, pos);
179 }
180 if (0xd7ff < code_point && code_point < 0xe000) {
181 return result(error_code::SURROGATE, pos);
182 }
183 } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000
184 next_pos = pos + 4;
185 if (next_pos > len) {
186 return result(error_code::TOO_SHORT, pos);
187 }
188 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
189 return result(error_code::TOO_SHORT, pos);
190 }
191 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
192 return result(error_code::TOO_SHORT, pos);
193 }
194 if ((data[pos + 3] & 0b11000000) != 0b10000000) {
195 return result(error_code::TOO_SHORT, pos);
196 }
197 // range check
198 code_point =
199 (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
200 (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
201 if (code_point <= 0xffff) {
202 return result(error_code::OVERLONG, pos);
203 }
204 if (0x10ffff < code_point) {
205 return result(error_code::TOO_LARGE, pos);
206 }
207 } else {
208 // we either have too many continuation bytes or an invalid leading byte
209 if ((byte & 0b11000000) == 0b10000000) {
210 return result(error_code::TOO_LONG, pos);
211 } else {
212 return result(error_code::HEADER_BITS, pos);
213 }
214 }
215 pos = next_pos;
216 }
217 return result(error_code::SUCCESS, len);
218}
219
220simdutf_really_inline simdutf_warn_unused result
221validate_with_errors(const char *buf, size_t len) noexcept {
222 return validate_with_errors(reinterpret_cast<const uint8_t *>(buf), len);
223}
224
225// Finds the previous leading byte starting backward from buf and validates with
226// errors from there Used to pinpoint the location of an error when an invalid
227// chunk is detected We assume that the stream starts with a leading byte, and
228// to check that it is the case, we ask that you pass a pointer to the start of
229// the stream (start). Note that the resulting count is underflowed if an error
230// is encountered in the rewinded segment.
231inline simdutf_warn_unused result rewind_and_validate_with_errors(
232 const char *start, const char *buf, size_t len) noexcept {
233 // First check that we start with a leading byte
234 if ((*start & 0b11000000) == 0b10000000) {
235 return result(error_code::TOO_LONG, 0);
236 }
237 size_t extra_len{0};
238 // A leading byte cannot be further than 4 bytes away
239 for (int i = 0; i < 5; i++) {
240 unsigned char byte = *buf;
241 if ((byte & 0b11000000) != 0b10000000) {
242 break;
243 } else {
244 buf--;
245 extra_len++;
246 }
247 }
248
249 result res = validate_with_errors(buf, len + extra_len);
250 res.count -= extra_len; // Might underflow
251 return res;
252}
253
254// credit: based on code from Google Fuchsia (Apache Licensed)
255template <class BytePtr>
256simdutf_constexpr23 simdutf_warn_unused utf8_result
257validate_with_counts(BytePtr data, size_t len) noexcept {
258 static_assert(
259 std::is_same<typename std::decay<decltype(*data)>::type, uint8_t>::value,
260 "dereferencing the data pointer must result in a uint8_t");
261 size_t pos = 0;
262 uint32_t code_point = 0;
263 size_t continuations = 0;
264 size_t four_byte = 0;
265 while (pos < len) {
266 // std::memcpy is not usable during constant evaluation, so the block-wise
267 // ASCII fast path is restricted to run time, like in validate() above.
268#if SIMDUTF_CPLUSPLUS23
269 if !consteval
270#endif
271 { // check if the next 16 bytes are ascii.
272 const size_t next_ascii_pos = pos + 16;
273 if (next_ascii_pos <= len) { // if it is safe to read 16 more bytes, check
274 // that they are ascii
275 uint64_t v1{};
276 std::memcpy(&v1, data + pos, sizeof(uint64_t));
277 uint64_t v2{};
278 std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
279 uint64_t v{v1 | v2};
280 if ((v & 0x8080808080808080) == 0) {
281 pos = next_ascii_pos;
282 continue;
283 }
284 }
285 }
286
287 size_t next_pos;
288 unsigned char byte = data[pos];
289
290 while (byte < 0b10000000) {
291 if (++pos == len) {
292 return utf8_result(error_code::SUCCESS, pos, continuations, four_byte);
293 }
294 byte = data[pos];
295 }
296
297 if ((byte & 0b11100000) == 0b11000000) {
298 next_pos = pos + 2;
299 if (next_pos > len) {
300 return utf8_result(error_code::TOO_SHORT, pos, continuations,
301 four_byte);
302 }
303 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
304 return utf8_result(error_code::TOO_SHORT, pos, continuations,
305 four_byte);
306 }
307 // range check
308 code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
309 if (code_point < 0x80) {
310 return utf8_result(error_code::OVERLONG, pos, continuations, four_byte);
311 }
312 continuations += 1;
313 } else if ((byte & 0b11110000) == 0b11100000) {
314 next_pos = pos + 3;
315 if (next_pos > len) {
316 return utf8_result(error_code::TOO_SHORT, pos, continuations,
317 four_byte);
318 }
319 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
320 return utf8_result(error_code::TOO_SHORT, pos, continuations,
321 four_byte);
322 }
323 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
324 return utf8_result(error_code::TOO_SHORT, pos, continuations,
325 four_byte);
326 }
327 // range check
328 code_point = (byte & 0b00001111) << 12 |
329 (data[pos + 1] & 0b00111111) << 6 |
330 (data[pos + 2] & 0b00111111);
331 if (code_point < 0x800) {
332 return utf8_result(error_code::OVERLONG, pos, continuations, four_byte);
333 } else if (0xd7ff < code_point && code_point < 0xe000) {
334 return utf8_result(error_code::SURROGATE, pos, continuations,
335 four_byte);
336 }
337 continuations += 2;
338 } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000
339 next_pos = pos + 4;
340 if (next_pos > len) {
341 return utf8_result(error_code::TOO_SHORT, pos, continuations,
342 four_byte);
343 }
344 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
345 return utf8_result(error_code::TOO_SHORT, pos, continuations,
346 four_byte);
347 }
348 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
349 return utf8_result(error_code::TOO_SHORT, pos, continuations,
350 four_byte);
351 }
352 if ((data[pos + 3] & 0b11000000) != 0b10000000) {
353 return utf8_result(error_code::TOO_SHORT, pos, continuations,
354 four_byte);
355 }
356 // range check
357 code_point =
358 (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
359 (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
360 if (code_point <= 0xffff) {
361 return utf8_result(error_code::OVERLONG, pos, continuations, four_byte);
362 } else if (0x10ffff < code_point) {
363 return utf8_result(error_code::TOO_LARGE, pos, continuations,
364 four_byte);
365 }
366 continuations += 3;
367 four_byte += 1;
368 } else {
369 // Continuation byte or invalid header byte
370 if ((byte & 0b11000000) == 0b10000000) {
371 return utf8_result(error_code::TOO_LONG, pos, continuations, four_byte);
372 } else {
373 return utf8_result(error_code::HEADER_BITS, pos, continuations,
374 four_byte);
375 }
376 }
377 pos = next_pos;
378 }
379 return utf8_result(error_code::SUCCESS, pos, continuations, four_byte);
380}
381
382simdutf_really_inline simdutf_warn_unused utf8_result
383validate_with_counts(const char *buf, size_t len) noexcept {
384 return validate_with_counts(reinterpret_cast<const uint8_t *>(buf), len);
385}
386
387// Finds the previous leading byte starting backward from buf and validates from
388// there on out while counting continuation bytes and 4-byte leads. Used to
389// pinpoint the location of an error when an invalid chunk is detected. We check
390// that the stream starts with a leading byte with the passed pointer to the
391// start of the stream (start).
392//
393// All three fields of the result are relative to buf. The bytes we rewind over
394// sit in a block the caller has already counted, so their contribution is
395// subtracted back out at the end. When the error lies inside the rewound
396// region, those three subtractions underflow. That is intended, not an
397// oversight: the caller adds the offset of buf and the counts of the preceding
398// blocks, which include the rewound bytes, and the unsigned wraparound cancels
399// exactly, leaving the correct absolute values.
400inline simdutf_warn_unused utf8_result rewind_and_validate_with_counts(
401 const char *start, const char *buf, size_t len) noexcept {
402 // First check that we start with a leading byte
403 if ((*start & 0b11000000) == 0b10000000) {
404 return utf8_result(error_code::TOO_LONG, 0, 0, 0);
405 }
406 // If this is the start of the buffer, we don't need to rewind to check the
407 // previous chunk.
408 if (start == buf) {
409 return validate_with_counts(buf, len);
410 }
411 // As this isn't the start of the buffer, the error might be in the previous
412 // chunk. So we backtrack and also need to avoid double counting.
413 size_t extra_len{0};
414 size_t extra_continuations{0};
415 size_t extra_four_bytes{0};
416 // Four steps are enough. Everything before buf has already validated, so at
417 // most three continuation bytes can precede it, from a four-byte sequence
418 // ending exactly on the boundary; the fourth step then lands on its leading
419 // byte. Note that the sibling rewind_and_validate_with_errors needs five
420 // steps only because it tests the byte before stepping back, whereas we step
421 // back first. Callers pass a block boundary at least 64 bytes into the
422 // stream, so stepping back four bytes stays in bounds.
423 for (int i = 0; i < 4; i++) {
424 // We rewind at least one byte to find the actual previous leading byte.
425 // We do so to backtrack into the last chunk in case the utf8 error happened
426 // there already.
427 buf--;
428 unsigned char byte = *buf;
429 extra_len++;
430
431 if (byte >= 0b10000000) {
432 if (byte >= 0b11110000) {
433 extra_four_bytes += 1;
434 } else if ((int8_t)byte < -65 + 1) {
435 extra_continuations += 1;
436 }
437 }
438 if ((byte & 0b11000000) != 0b10000000) {
439 break;
440 }
441 }
442
443 utf8_result res = validate_with_counts(buf, len + extra_len);
444 res.input_count -= extra_len;
445 res.continuation_count -= extra_continuations;
446 res.four_byte_count -= extra_four_bytes;
447 return res;
448}
449
450template <typename InputPtr>
451#if SIMDUTF_CPLUSPLUS20
452 requires simdutf::detail::indexes_into_byte_like<InputPtr>
453#endif
454simdutf_constexpr23 size_t count_code_points(InputPtr data, size_t len) {
455 size_t counter{0};
456 for (size_t i = 0; i < len; i++) {
457 // -65 is 0b10111111, anything larger in two-complement's should start a new
458 // code point.
459 if (int8_t(data[i]) > -65) {
460 counter++;
461 }
462 }
463 return counter;
464}
465
466template <typename InputPtr>
467#if SIMDUTF_CPLUSPLUS20
468 requires simdutf::detail::indexes_into_byte_like<InputPtr>
469#endif
470simdutf_constexpr23 size_t utf16_length_from_utf8(InputPtr data, size_t len) {
471 size_t counter{0};
472 for (size_t i = 0; i < len; i++) {
473 if (int8_t(data[i]) > -65) {
474 counter++;
475 }
476 if (uint8_t(data[i]) >= 240) {
477 counter++;
478 }
479 }
480 return counter;
481}
482
483template <typename InputPtr>
484#if SIMDUTF_CPLUSPLUS20
485 requires simdutf::detail::indexes_into_byte_like<InputPtr>
486#endif
487simdutf_warn_unused simdutf_constexpr23 size_t
488trim_partial_utf8(InputPtr input, size_t length) {
489 if (length < 3) {
490 switch (length) {
491 case 2:
492 if (uint8_t(input[length - 1]) >= 0xc0) {
493 return length - 1;
494 } // 2-, 3- and 4-byte characters with only 1 byte left
495 if (uint8_t(input[length - 2]) >= 0xe0) {
496 return length - 2;
497 } // 3- and 4-byte characters with only 2 bytes left
498 return length;
499 case 1:
500 if (uint8_t(input[length - 1]) >= 0xc0) {
501 return length - 1;
502 } // 2-, 3- and 4-byte characters with only 1 byte left
503 return length;
504 case 0:
505 return length;
506 }
507 }
508 if (uint8_t(input[length - 1]) >= 0xc0) {
509 return length - 1;
510 } // 2-, 3- and 4-byte characters with only 1 byte left
511 if (uint8_t(input[length - 2]) >= 0xe0) {
512 return length - 2;
513 } // 3- and 4-byte characters with only 1 byte left
514 if (uint8_t(input[length - 3]) >= 0xf0) {
515 return length - 3;
516 } // 4-byte characters with only 3 bytes left
517 return length;
518}
519
520} // namespace utf8
521} // unnamed namespace
522} // namespace scalar
523} // namespace simdutf
524
525#endif
helpers placed in namespace detail are not a part of the public API
simdutf_warn_unused size_t utf16_length_from_utf8(const char *input, size_t length) noexcept
Compute the number of 2-byte code units that this UTF-8 string would require in UTF-16LE format.
simdutf_warn_unused size_t trim_partial_utf8(const char *input, size_t length)
Given a valid UTF-8 string having a possibly truncated last character, this function checks the end o...