simdutf 9.0.0
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 uint16_t high_surrogate = uint16_t(0xD800 + (code_point >> 10));
122 uint16_t low_surrogate = uint16_t(0xDC00 + (code_point & 0x3FF));
123 if constexpr (!match_system(big_endian)) {
124 high_surrogate = u16_swap_bytes(high_surrogate);
125 low_surrogate = u16_swap_bytes(low_surrogate);
126 }
127 *utf16_output++ = char16_t(high_surrogate);
128 *utf16_output++ = char16_t(low_surrogate);
129 pos += 4;
130 } else {
131 return 0;
132 }
133 }
134 return utf16_output - start;
135}
136
137template <endianness big_endian, typename InputPtr>
138#if SIMDUTF_CPLUSPLUS20
139 requires simdutf::detail::indexes_into_byte_like<InputPtr>
140#endif
141simdutf_constexpr23 result convert_with_errors(InputPtr data, size_t len,
142 char16_t *utf16_output) {
143 size_t pos = 0;
144 char16_t *start{utf16_output};
145 while (pos < len) {
146#if SIMDUTF_CPLUSPLUS23
147 if !consteval
148#endif
149 {
150 // try to convert the next block of 16 ASCII bytes
151 if (pos + 16 <= len) { // if it is safe to read 16 more bytes, check that
152 // they are ascii
153 uint64_t v1;
154 ::memcpy(&v1, data + pos, sizeof(uint64_t));
155 uint64_t v2;
156 ::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
157 uint64_t v{v1 | v2};
158 if ((v & 0x8080808080808080) == 0) {
159 size_t final_pos = pos + 16;
160 while (pos < final_pos) {
161 const char16_t byte = uint8_t(data[pos]);
162 *utf16_output++ =
163 !match_system(big_endian) ? u16_swap_bytes(byte) : byte;
164 pos++;
165 }
166 continue;
167 }
168 }
169 }
170
171 auto leading_byte = uint8_t(data[pos]); // leading byte
172 if (leading_byte < 0b10000000) {
173 // converting one ASCII byte !!!
174 *utf16_output++ = !match_system(big_endian)
175 ? char16_t(u16_swap_bytes(leading_byte))
176 : char16_t(leading_byte);
177 pos++;
178 } else if ((leading_byte & 0b11100000) == 0b11000000) {
179 // We have a two-byte UTF-8, it should become
180 // a single UTF-16 word.
181 if (pos + 1 >= len) {
182 return result(error_code::TOO_SHORT, pos);
183 } // minimal bound checking
184 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
185 return result(error_code::TOO_SHORT, pos);
186 }
187 // range check
188 uint32_t code_point = (leading_byte & 0b00011111) << 6 |
189 (uint8_t(data[pos + 1]) & 0b00111111);
190 if (code_point < 0x80) {
191 return result(error_code::OVERLONG, pos);
192 }
193 if constexpr (!match_system(big_endian)) {
194 code_point = uint32_t(u16_swap_bytes(uint16_t(code_point)));
195 }
196 *utf16_output++ = char16_t(code_point);
197 pos += 2;
198 } else if ((leading_byte & 0b11110000) == 0b11100000) {
199 // We have a three-byte UTF-8, it should become
200 // a single UTF-16 word.
201 if (pos + 2 >= len) {
202 return result(error_code::TOO_SHORT, pos);
203 } // minimal bound checking
204
205 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
206 return result(error_code::TOO_SHORT, pos);
207 }
208 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
209 return result(error_code::TOO_SHORT, pos);
210 }
211 // range check
212 uint32_t code_point = (leading_byte & 0b00001111) << 12 |
213 (uint8_t(data[pos + 1]) & 0b00111111) << 6 |
214 (uint8_t(data[pos + 2]) & 0b00111111);
215 if (code_point < 0x800) {
216 return result(error_code::OVERLONG, pos);
217 }
218 if (0xd7ff < code_point && code_point < 0xe000) {
219 return result(error_code::SURROGATE, pos);
220 }
221 if constexpr (!match_system(big_endian)) {
222 code_point = uint32_t(u16_swap_bytes(uint16_t(code_point)));
223 }
224 *utf16_output++ = char16_t(code_point);
225 pos += 3;
226 } else if ((leading_byte & 0b11111000) == 0b11110000) { // 0b11110000
227 // we have a 4-byte UTF-8 word.
228 if (pos + 3 >= len) {
229 return result(error_code::TOO_SHORT, pos);
230 } // minimal bound checking
231 if ((uint8_t(data[pos + 1]) & 0b11000000) != 0b10000000) {
232 return result(error_code::TOO_SHORT, pos);
233 }
234 if ((uint8_t(data[pos + 2]) & 0b11000000) != 0b10000000) {
235 return result(error_code::TOO_SHORT, pos);
236 }
237 if ((uint8_t(data[pos + 3]) & 0b11000000) != 0b10000000) {
238 return result(error_code::TOO_SHORT, pos);
239 }
240
241 // range check
242 uint32_t code_point = (leading_byte & 0b00000111) << 18 |
243 (uint8_t(data[pos + 1]) & 0b00111111) << 12 |
244 (uint8_t(data[pos + 2]) & 0b00111111) << 6 |
245 (uint8_t(data[pos + 3]) & 0b00111111);
246 if (code_point <= 0xffff) {
247 return result(error_code::OVERLONG, pos);
248 }
249 if (0x10ffff < code_point) {
250 return result(error_code::TOO_LARGE, pos);
251 }
252 code_point -= 0x10000;
253 uint16_t high_surrogate = uint16_t(0xD800 + (code_point >> 10));
254 uint16_t low_surrogate = uint16_t(0xDC00 + (code_point & 0x3FF));
255 if constexpr (!match_system(big_endian)) {
256 high_surrogate = u16_swap_bytes(high_surrogate);
257 low_surrogate = u16_swap_bytes(low_surrogate);
258 }
259 *utf16_output++ = char16_t(high_surrogate);
260 *utf16_output++ = char16_t(low_surrogate);
261 pos += 4;
262 } else {
263 // we either have too many continuation bytes or an invalid leading byte
264 if ((leading_byte & 0b11000000) == 0b10000000) {
265 return result(error_code::TOO_LONG, pos);
266 } else {
267 return result(error_code::HEADER_BITS, pos);
268 }
269 }
270 }
271 return result(error_code::SUCCESS, utf16_output - start);
272}
273
274/**
275 * When rewind_and_convert_with_errors is called, we are pointing at 'buf' and
276 * we have up to len input bytes left, and we encountered some error. It is
277 * possible that the error is at 'buf' exactly, but it could also be in the
278 * previous bytes (up to 3 bytes back).
279 *
280 * prior_bytes indicates how many bytes, prior to 'buf' may belong to the
281 * current memory section and can be safely accessed. We prior_bytes to access
282 * safely up to three bytes before 'buf'.
283 *
284 * The caller is responsible to ensure that len > 0.
285 *
286 * If the error is believed to have occurred prior to 'buf', the count value
287 * contain in the result will be SIZE_T - 1, SIZE_T - 2, or SIZE_T - 3.
288 */
289template <endianness endian>
290inline result rewind_and_convert_with_errors(size_t prior_bytes,
291 const char *buf, size_t len,
292 char16_t *utf16_output) {
293 size_t extra_len{0};
294 // We potentially need to go back in time and find a leading byte.
295 // In theory '3' would be sufficient, but sometimes the error can go back
296 // quite far.
297 size_t how_far_back = prior_bytes;
298 // size_t how_far_back = 3; // 3 bytes in the past + current position
299 // if(how_far_back >= prior_bytes) { how_far_back = prior_bytes; }
300 bool found_leading_bytes{false};
301 // important: it is i <= how_far_back and not 'i < how_far_back'.
302 for (size_t i = 0; i <= how_far_back; i++) {
303 unsigned char byte = buf[-static_cast<std::ptrdiff_t>(i)];
304 found_leading_bytes = ((byte & 0b11000000) != 0b10000000);
305 if (found_leading_bytes) {
306 if (i > 0 && byte < 128) {
307 // If we had to go back and the leading byte is ascii
308 // then we can stop right away.
309 return result(error_code::TOO_LONG, 0 - i + 1);
310 }
311 buf -= i;
312 extra_len = i;
313 break;
314 }
315 }
316 //
317 // It is possible for this function to return a negative count in its result.
318 // C++ Standard Section 18.1 defines size_t is in <cstddef> which is described
319 // in C Standard as <stddef.h>. C Standard Section 4.1.5 defines size_t as an
320 // unsigned integral type of the result of the sizeof operator
321 //
322 // An unsigned type will simply wrap round arithmetically (well defined).
323 //
324 if (!found_leading_bytes) {
325 // If how_far_back == 3, we may have four consecutive continuation bytes!!!
326 // [....] [continuation] [continuation] [continuation] | [buf is
327 // continuation] Or we possibly have a stream that does not start with a
328 // leading byte.
329 return result(error_code::TOO_LONG, 0 - how_far_back);
330 }
331 result res = convert_with_errors<endian>(buf, len + extra_len, utf16_output);
332 if (res.error) {
333 res.count -= extra_len;
334 }
335 return res;
336}
337
338} // namespace utf8_to_utf16
339} // unnamed namespace
340} // namespace scalar
341} // namespace simdutf
342
343#endif
helpers placed in namespace detail are not a part of the public API