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