simdutf 9.0.0
Unicode at GB/s.
Loading...
Searching...
No Matches
utf8_to_utf32.h
1#ifndef SIMDUTF_UTF8_TO_UTF32_H
2#define SIMDUTF_UTF8_TO_UTF32_H
3
4#include <cstring>
5
6namespace simdutf {
7namespace scalar {
8namespace {
9namespace utf8_to_utf32 {
10
11template <typename InputPtr>
12#if SIMDUTF_CPLUSPLUS20
13 requires simdutf::detail::indexes_into_byte_like<InputPtr>
14#endif
15simdutf_constexpr23 size_t convert(InputPtr data, size_t len,
16 char32_t *utf32_output) {
17 size_t pos = 0;
18 char32_t *start{utf32_output};
19 while (pos < len) {
20#if SIMDUTF_CPLUSPLUS23
21 if !consteval
22#endif
23 {
24 // try to convert the next block of 16 ASCII bytes
25 if (pos + 16 <= len) { // if it is safe to read 16 more bytes, check that
26 // they are ascii
27 uint64_t v1;
28 ::memcpy(&v1, data + pos, sizeof(uint64_t));
29 uint64_t v2;
30 ::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
31 uint64_t v{v1 | v2};
32 if ((v & 0x8080808080808080) == 0) {
33 size_t final_pos = pos + 16;
34 while (pos < final_pos) {
35 *utf32_output++ = uint8_t(data[pos]);
36 pos++;
37 }
38 continue;
39 }
40 }
41 }
42 auto leading_byte = uint8_t(data[pos]); // leading byte
43 if (leading_byte < 0b10000000) {
44 // converting one ASCII byte !!!
45 *utf32_output++ = char32_t(leading_byte);
46 pos++;
47 } else if ((leading_byte & 0b11100000) == 0b11000000) {
48 // We have a two-byte UTF-8
49 if (pos + 1 >= len) {
50 return 0;
51 } // minimal bound checking
52 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
53 return 0;
54 }
55 // range check
56 uint32_t code_point = (leading_byte & 0b00011111) << 6 |
57 (uint8_t(data[pos + 1]) & 0b00111111);
58 if (code_point < 0x80) {
59 return 0;
60 }
61 *utf32_output++ = char32_t(code_point);
62 pos += 2;
63 } else if ((leading_byte & 0b11110000) == 0b11100000) {
64 // We have a three-byte UTF-8
65 if (pos + 2 >= len) {
66 return 0;
67 } // minimal bound checking
68
69 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
70 return 0;
71 }
72 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
73 return 0;
74 }
75 // range check
76 uint32_t code_point = (leading_byte & 0b00001111) << 12 |
77 (uint8_t(data[pos + 1]) & 0b00111111) << 6 |
78 (uint8_t(data[pos + 2]) & 0b00111111);
79 if (code_point < 0x800 || (0xd7ff < code_point && code_point < 0xe000)) {
80 return 0;
81 }
82 *utf32_output++ = char32_t(code_point);
83 pos += 3;
84 } else if ((leading_byte & 0b11111000) == 0b11110000) { // 0b11110000
85 // we have a 4-byte UTF-8 word.
86 if (pos + 3 >= len) {
87 return 0;
88 } // minimal bound checking
89 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
90 return 0;
91 }
92 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
93 return 0;
94 }
95 if ((uint8_t(data[pos + 3]) & 0b11000000) != 0b10000000) {
96 return 0;
97 }
98
99 // range check
100 uint32_t code_point = (leading_byte & 0b00000111) << 18 |
101 (uint8_t(data[pos + 1]) & 0b00111111) << 12 |
102 (uint8_t(data[pos + 2]) & 0b00111111) << 6 |
103 (uint8_t(data[pos + 3]) & 0b00111111);
104 if (code_point <= 0xffff || 0x10ffff < code_point) {
105 return 0;
106 }
107 *utf32_output++ = char32_t(code_point);
108 pos += 4;
109 } else {
110 return 0;
111 }
112 }
113 return utf32_output - start;
114}
115
116template <typename InputPtr>
117#if SIMDUTF_CPLUSPLUS20
118 requires simdutf::detail::indexes_into_byte_like<InputPtr>
119#endif
120simdutf_constexpr23 result convert_with_errors(InputPtr data, size_t len,
121 char32_t *utf32_output) {
122 size_t pos = 0;
123 char32_t *start{utf32_output};
124 while (pos < len) {
125#if SIMDUTF_CPLUSPLUS23
126 if !consteval
127#endif
128 {
129 // try to convert the next block of 16 ASCII bytes
130 if (pos + 16 <= len) { // if it is safe to read 16 more bytes, check that
131 // they are ascii
132 uint64_t v1;
133 ::memcpy(&v1, data + pos, sizeof(uint64_t));
134 uint64_t v2;
135 ::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
136 uint64_t v{v1 | v2};
137 if ((v & 0x8080808080808080) == 0) {
138 size_t final_pos = pos + 16;
139 while (pos < final_pos) {
140 *utf32_output++ = uint8_t(data[pos]);
141 pos++;
142 }
143 continue;
144 }
145 }
146 }
147 auto leading_byte = uint8_t(data[pos]); // leading byte
148 if (leading_byte < 0b10000000) {
149 // converting one ASCII byte !!!
150 *utf32_output++ = char32_t(leading_byte);
151 pos++;
152 } else if ((leading_byte & 0b11100000) == 0b11000000) {
153 // We have a two-byte UTF-8
154 if (pos + 1 >= len) {
155 return result(error_code::TOO_SHORT, pos);
156 } // minimal bound checking
157 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
158 return result(error_code::TOO_SHORT, pos);
159 }
160 // range check
161 uint32_t code_point = (leading_byte & 0b00011111) << 6 |
162 (uint8_t(data[pos + 1]) & 0b00111111);
163 if (code_point < 0x80) {
164 return result(error_code::OVERLONG, pos);
165 }
166 *utf32_output++ = char32_t(code_point);
167 pos += 2;
168 } else if ((leading_byte & 0b11110000) == 0b11100000) {
169 // We have a three-byte UTF-8
170 if (pos + 2 >= len) {
171 return result(error_code::TOO_SHORT, pos);
172 } // minimal bound checking
173
174 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
175 return result(error_code::TOO_SHORT, pos);
176 }
177 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
178 return result(error_code::TOO_SHORT, pos);
179 }
180 // range check
181 uint32_t code_point = (leading_byte & 0b00001111) << 12 |
182 (uint8_t(data[pos + 1]) & 0b00111111) << 6 |
183 (uint8_t(data[pos + 2]) & 0b00111111);
184 if (code_point < 0x800) {
185 return result(error_code::OVERLONG, pos);
186 }
187 if (0xd7ff < code_point && code_point < 0xe000) {
188 return result(error_code::SURROGATE, pos);
189 }
190 *utf32_output++ = char32_t(code_point);
191 pos += 3;
192 } else if ((leading_byte & 0b11111000) == 0b11110000) { // 0b11110000
193 // we have a 4-byte UTF-8 word.
194 if (pos + 3 >= len) {
195 return result(error_code::TOO_SHORT, pos);
196 } // minimal bound checking
197 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
198 return result(error_code::TOO_SHORT, pos);
199 }
200 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
201 return result(error_code::TOO_SHORT, pos);
202 }
203 if ((uint8_t(data[pos + 3]) & 0b11000000) != 0b10000000) {
204 return result(error_code::TOO_SHORT, pos);
205 }
206
207 // range check
208 uint32_t code_point = (leading_byte & 0b00000111) << 18 |
209 (uint8_t(data[pos + 1]) & 0b00111111) << 12 |
210 (uint8_t(data[pos + 2]) & 0b00111111) << 6 |
211 (uint8_t(data[pos + 3]) & 0b00111111);
212 if (code_point <= 0xffff) {
213 return result(error_code::OVERLONG, pos);
214 }
215 if (0x10ffff < code_point) {
216 return result(error_code::TOO_LARGE, pos);
217 }
218 *utf32_output++ = char32_t(code_point);
219 pos += 4;
220 } else {
221 // we either have too many continuation bytes or an invalid leading byte
222 if ((leading_byte & 0b11000000) == 0b10000000) {
223 return result(error_code::TOO_LONG, pos);
224 } else {
225 return result(error_code::HEADER_BITS, pos);
226 }
227 }
228 }
229 return result(error_code::SUCCESS, utf32_output - start);
230}
231
232/**
233 * When rewind_and_convert_with_errors is called, we are pointing at 'buf' and
234 * we have up to len input bytes left, and we encountered some error. It is
235 * possible that the error is at 'buf' exactly, but it could also be in the
236 * previous bytes location (up to 3 bytes back).
237 *
238 * prior_bytes indicates how many bytes, prior to 'buf' may belong to the
239 * current memory section and can be safely accessed. We prior_bytes to access
240 * safely up to three bytes before 'buf'.
241 *
242 * The caller is responsible to ensure that len > 0.
243 *
244 * If the error is believed to have occurred prior to 'buf', the count value
245 * contain in the result will be SIZE_T - 1, SIZE_T - 2, or SIZE_T - 3.
246 */
247inline result rewind_and_convert_with_errors(size_t prior_bytes,
248 const char *buf, size_t len,
249 char32_t *utf32_output) {
250 size_t extra_len{0};
251 // We potentially need to go back in time and find a leading byte.
252 size_t how_far_back = 3; // 3 bytes in the past + current position
253 if (how_far_back > prior_bytes) {
254 how_far_back = prior_bytes;
255 }
256 bool found_leading_bytes{false};
257 // important: it is i <= how_far_back and not 'i < how_far_back'.
258 for (size_t i = 0; i <= how_far_back; i++) {
259 unsigned char byte = buf[-static_cast<std::ptrdiff_t>(i)];
260 found_leading_bytes = ((byte & 0b11000000) != 0b10000000);
261 if (found_leading_bytes) {
262 if (i > 0 && byte < 128) {
263 // If we had to go back and the leading byte is ascii
264 // then we can stop right away.
265 return result(error_code::TOO_LONG, 0 - i + 1);
266 }
267 buf -= i;
268 extra_len = i;
269 break;
270 }
271 }
272 //
273 // It is possible for this function to return a negative count in its result.
274 // C++ Standard Section 18.1 defines size_t is in <cstddef> which is described
275 // in C Standard as <stddef.h>. C Standard Section 4.1.5 defines size_t as an
276 // unsigned integral type of the result of the sizeof operator
277 //
278 // An unsigned type will simply wrap round arithmetically (well defined).
279 //
280 if (!found_leading_bytes) {
281 // If how_far_back == 3, we may have four consecutive continuation bytes!!!
282 // [....] [continuation] [continuation] [continuation] | [buf is
283 // continuation] Or we possibly have a stream that does not start with a
284 // leading byte.
285 return result(error_code::TOO_LONG, 0 - how_far_back);
286 }
287
288 result res = convert_with_errors(buf, len + extra_len, utf32_output);
289 if (res.error) {
290 res.count -= extra_len;
291 }
292 return res;
293}
294
295} // namespace utf8_to_utf32
296} // unnamed namespace
297} // namespace scalar
298} // namespace simdutf
299
300#endif
helpers placed in namespace detail are not a part of the public API