1#[allow(dead_code)]
13pub const ASCII_MASK: usize = 0x8080_8080_8080_8080u64 as usize;
14
15cfg_if! {
16 if #[cfg(all(feature = "simd-accel", target_endian = "little"))] {
17 pub(crate) use crate::simd_funcs::ascii_to_ascii_stride;
18 pub(crate) use crate::simd_funcs::ascii_to_basic_latin_stride;
19 pub(crate) use crate::simd_funcs::basic_latin_to_ascii_stride;
20 #[allow(unused_imports)]
21 pub(crate) use crate::simd_funcs::validate_ascii_stride;
22 #[allow(unused_imports)]
23 pub(crate) use crate::simd_funcs::ascii_to_ascii_double_stride;
24 #[allow(unused_imports)]
25 pub(crate) use crate::simd_funcs::ascii_to_basic_latin_double_stride;
26 pub(crate) use crate::simd_funcs::basic_latin_to_ascii_double_stride;
27 #[allow(unused_imports)]
28 pub(crate) use crate::simd_funcs::validate_ascii_double_stride;
29 } else {
30
31 #[inline(never)]
36 pub(crate) fn is_ascii(s: &[u8; STRIDE]) -> bool {
37 s.iter().all(|b| *b < 0x80)
38 }
39
40 #[inline(never)]
41 pub(crate) fn is_basic_latin(s: &[u16; STRIDE]) -> bool {
42 s.iter().all(|b| *b < 0x80)
43 }
44
45 #[inline(never)]
46 pub(crate) fn is_utf16_latin1(s: &[u16; STRIDE]) -> bool {
47 s.iter().all(|b| *b < 0x100)
48 }
49
50 #[inline(never)]
51 pub(crate) fn copy_stride(src_stride: &[u8; STRIDE], dst_stride: &mut [u8; STRIDE]) {
52 *dst_stride = *src_stride;
53 }
54
55 #[inline(never)]
56 pub(crate) fn unpack_stride(src_stride: &[u8; STRIDE], dst_stride: &mut [u16; STRIDE]) {
57 src_stride.iter().zip(dst_stride.iter_mut()).for_each(|(s, d)| *d = *s as u16);
58 }
59
60 #[inline(never)]
61 pub(crate) fn pack_stride(src_stride: &[u16; STRIDE], dst_stride: &mut [u8; STRIDE]) {
62 src_stride.iter().zip(dst_stride.iter_mut()).for_each(|(s, d)| *d = *s as u8);
63 }
64
65 #[inline(never)]
66 fn copy_stride_tail(src_stride: &[u8; 16], dst_stride: &mut [u8; 16]) -> (u8, usize) {
67 for (i, (s, d)) in src_stride.iter().zip(dst_stride.iter_mut()).enumerate() {
68 let c = *s;
69 if c >= 0x80 {
70 return (c, i);
71 }
72 *d = c;
73 }
74 debug_assert!(false);
75 (0, 0)
76 }
77
78 #[inline(never)]
79 fn unpack_stride_tail(src_stride: &[u8; 16], dst_stride: &mut [u16; 16]) -> (u8, usize) {
80 for (i, (s, d)) in src_stride.iter().zip(dst_stride.iter_mut()).enumerate() {
81 let c = *s;
82 if c >= 0x80 {
83 return (c, i);
84 }
85 *d = c as u16;
86 }
87 debug_assert!(false);
88 (0, 0)
89 }
90
91 #[inline(never)]
92 fn pack_stride_tail(src_stride: &[u16; 16], dst_stride: &mut [u8; 16]) -> (u16, usize) {
93 for (i, (s, d)) in src_stride.iter().zip(dst_stride.iter_mut()).enumerate() {
94 let c = *s;
95 if c >= 0x80 {
96 return (c, i);
97 }
98 *d = c as u8;
99 }
100 debug_assert!(false);
101 (0, 0)
102 }
103
104 #[allow(dead_code)]
105 #[inline(never)]
106 fn validate_ascii_stride_tail(stride: &[u8; 16]) -> (u8, usize) {
107 for (i, s) in stride.iter().enumerate() {
108 let b = *s;
109 if b >= 0x80 {
110 return (b, i);
111 }
112 }
113 debug_assert!(false);
114 (0, 0)
115 }
116
117 #[allow(dead_code)]
118 #[inline(never)]
119 fn validate_basic_latin_stride_tail(stride: &[u16; 16]) -> usize {
120 for (i, s) in stride.iter().enumerate() {
121 if *s >= 0x80 {
122 return i;
123 }
124 }
125 debug_assert!(false);
126 0
127 }
128
129 #[inline(always)]
130 fn ascii_to_ascii_stride(
131 src_stride: &[u8; STRIDE],
132 dst_stride: &mut [u8; STRIDE],
133 ) -> Option<(u8, usize)> {
134 if is_ascii(src_stride) {
135 copy_stride(src_stride, dst_stride);
136 return None;
137 }
138 Some(copy_stride_tail(src_stride, dst_stride))
139 }
140
141 #[inline(always)]
142 fn ascii_to_basic_latin_stride(
143 src_stride: &[u8; STRIDE],
144 dst_stride: &mut [u16; STRIDE],
145 ) -> Option<(u8, usize)> {
146 if is_ascii(src_stride) {
147 unpack_stride(src_stride, dst_stride);
148 return None;
149 }
150 Some(unpack_stride_tail(src_stride, dst_stride))
151 }
152
153 #[inline(always)]
154 fn basic_latin_to_ascii_stride(
155 src_stride: &[u16; STRIDE],
156 dst_stride: &mut [u8; STRIDE],
157 ) -> Option<(u16, usize)> {
158 if is_basic_latin(src_stride) {
159 pack_stride(src_stride, dst_stride);
160 return None;
161 }
162 Some(pack_stride_tail(src_stride, dst_stride))
163 }
164
165 #[allow(dead_code)]
166 #[inline(always)]
167 fn validate_ascii_stride(
168 stride: &[u8; STRIDE],
169 ) -> Option<(u8, usize)> {
170 if is_ascii(stride) {
171 return None;
172 }
173 Some(validate_ascii_stride_tail(stride))
174 }
175
176 #[allow(dead_code)]
177 #[inline(always)]
178 fn validate_basic_latin_stride(
179 stride: &[u16; STRIDE],
180 ) -> Option<usize> {
181 if is_basic_latin(stride) {
182 return None;
183 }
184 Some(validate_basic_latin_stride_tail(stride))
185 }
186 }
187}
188
189pub(crate) const STRIDE: usize = 16;
190
191pub(crate) const MAX_STRIDE_SIZE: usize = STRIDE;
192
193#[allow(unused_macros)]
194macro_rules! ascii_copy_impl_double {
195 ($name:ident, $stride:ident, $double_stride:ident, $src_unit:ty, $dst_unit:ty) => {
196 #[inline(always)]
197 pub(crate) fn $name(
198 src: &[$src_unit],
199 dst: &mut [$dst_unit],
200 ) -> Option<($src_unit, usize)> {
201 let len = core::cmp::min(src.len(), dst.len());
203 let mut consumed = 0usize;
204 let (src_strides, src_tail) = src[..len].as_chunks::<STRIDE>();
205 let (dst_strides, dst_tail) = dst[..len].as_chunks_mut::<STRIDE>();
206 if let Some((src_first_stride, src_strides_tail)) = src_strides.split_first() {
207 if let Some((dst_first_stride, dst_strides_tail)) = dst_strides.split_first_mut() {
208 if let Some(pos) = $stride(src_first_stride, dst_first_stride) {
209 return Some(pos);
210 }
211 consumed = STRIDE;
212
213 let (src_double_strides, src_single_stride) = src_strides_tail.as_chunks::<2>();
214 let (dst_double_strides, dst_single_stride) =
215 dst_strides_tail.as_chunks_mut::<2>();
216 for (src_double_stride, dst_double_stride) in
217 src_double_strides.iter().zip(dst_double_strides.iter_mut())
218 {
219 if let Some((c, pos)) = $double_stride(src_double_stride, dst_double_stride)
220 {
221 return Some((c, consumed + pos));
222 }
223 consumed += STRIDE * 2;
224 }
225 for (src_stride, dst_stride) in
226 src_single_stride.iter().zip(dst_single_stride.iter_mut())
227 {
228 if let Some((c, pos)) = $stride(src_stride, dst_stride) {
229 return Some((c, consumed + pos));
230 }
231 consumed += STRIDE;
232 }
233 } else {
234 debug_assert!(false);
235 }
236 }
237 for (src_slot, dst_slot) in src_tail.iter().zip(dst_tail.iter_mut()) {
238 let c = *src_slot;
239 if c >= 0x80 {
240 return Some((c, consumed));
241 }
242 *dst_slot = c as $dst_unit;
243 consumed += 1;
244 }
245 None
246 }
247 };
248}
249
250#[allow(unused_macros)]
251macro_rules! ascii_copy_impl_single {
252 ($name:ident, $stride:ident, $double_stride:ident, $src_unit:ty, $dst_unit:ty) => {
253 #[inline(always)]
254 pub fn $name(src: &[$src_unit], dst: &mut [$dst_unit]) -> Option<($src_unit, usize)> {
255 let len = core::cmp::min(src.len(), dst.len());
257 let mut consumed = 0usize;
258 let (src_strides, src_tail) = src[..len].as_chunks::<STRIDE>();
259 let (dst_strides, dst_tail) = dst[..len].as_chunks_mut::<STRIDE>();
260 for (src_stride, dst_stride) in src_strides.iter().zip(dst_strides.iter_mut()) {
261 if let Some((c, pos)) = $stride(src_stride, dst_stride) {
262 return Some((c, consumed + pos));
263 }
264 consumed += STRIDE;
265 }
266 for (src_slot, dst_slot) in src_tail.iter().zip(dst_tail.iter_mut()) {
267 let c = *src_slot;
268 if c >= 0x80 {
269 return Some((c, consumed));
270 }
271 *dst_slot = c as $dst_unit;
272 consumed += 1;
273 }
274 None
275 }
276 };
277}
278
279cfg_if! {
280 if #[cfg(target_arch = "arm")] {
281
282 #[inline(always)]
283 fn ascii_valid_impl(bytes: &[u8]) -> Option<(u8, usize)> {
284 let mut consumed = 0usize;
285 #[allow(clippy::never_loop)]
286 'outer: loop {
287 let (strides, tail) = bytes.as_chunks::<{core::mem::size_of::<usize>()}>();
288 if let Some((first_stride, strides_tail)) = strides.split_first() {
289 let first_usize = usize::from_ne_bytes(*first_stride);
290 if first_usize & ASCII_MASK != 0 {
291 break 'outer;
292 }
293 consumed = core::mem::size_of::<usize>();
294
295 let (double_strides, single_stride) = strides_tail.as_chunks::<2>();
296 for double_stride in double_strides.iter() {
297 let first_usize = usize::from_ne_bytes(double_stride[0]);
298 let second_usize = usize::from_ne_bytes(double_stride[1]);
299 if (first_usize | second_usize) & ASCII_MASK != 0 {
300 break 'outer;
301 }
302 consumed += core::mem::size_of::<usize>() * 2;
303 }
304 for stride in single_stride.iter() {
305 let last_usize = usize::from_ne_bytes(*stride);
306 if last_usize & ASCII_MASK != 0 {
307 break 'outer;
308 }
309 consumed += core::mem::size_of::<usize>();
310 }
311 }
312 for slot in tail.iter() {
313 let c = *slot;
314 if c >= 0x80 {
315 return Some((c, consumed));
316 }
317 consumed += 1;
318 }
319 return None;
320 }
321 let tail = &bytes[consumed..];
322 for slot in tail.iter() {
323 let c = *slot;
324 if c >= 0x80 {
325 return Some((c, consumed));
326 }
327 consumed += 1;
328 }
329 debug_assert!(false);
330 None
331 }
332
333 } else if #[cfg(all(feature = "simd-accel", target_endian = "little"))] {
334
335 #[inline(always)]
336 fn ascii_valid_impl(bytes: &[u8]) -> Option<(u8, usize)> {
337 let mut consumed = 0usize;
338 let (strides, tail) = bytes.as_chunks::<STRIDE>();
339 if let Some((first_stride, strides_tail)) = strides.split_first() {
340 if let Some((c, pos)) = validate_ascii_stride(first_stride) {
341 return Some((c, pos));
342 }
343 consumed = STRIDE;
344
345 let (double_strides, single_stride) = strides_tail.as_chunks::<2>();
346 for double_stride in double_strides.iter() {
347 if let Some((c, pos)) = validate_ascii_double_stride(double_stride) {
348 return Some((c, consumed + pos));
349 }
350 consumed += STRIDE * 2;
351 }
352 for stride in single_stride.iter() {
353 if let Some((c, pos)) = validate_ascii_stride(stride) {
354 return Some((c, consumed + pos));
355 }
356 consumed += STRIDE;
357 }
358 }
359 for slot in tail.iter() {
360 let c = *slot;
361 if c >= 0x80 {
362 return Some((c, consumed));
363 }
364 consumed += 1;
365 }
366 None
367 }
368
369 } else {
370
371 #[inline(always)]
372 fn ascii_valid_impl(bytes: &[u8]) -> Option<(u8, usize)> {
373 let mut consumed = 0usize;
374 let (strides, tail) = bytes.as_chunks::<STRIDE>();
375 for stride in strides.iter() {
376 if let Some((b, pos)) = validate_ascii_stride(stride) {
377 return Some((b, consumed + pos));
378 }
379 consumed += STRIDE;
380 }
381 for slot in tail.iter() {
382 let b = *slot;
383 if b >= 0x80 {
384 return Some((b, consumed));
385 }
386 consumed += 1;
387 }
388 None
389 }
390
391 }
392}
393
394cfg_if! {
395 if #[cfg(all(feature = "simd-accel", target_endian = "little", not(target_arch = "arm")))] {
396 ascii_copy_impl_double!(
397 ascii_to_ascii_impl,
398 ascii_to_ascii_stride,
399 ascii_to_ascii_double_stride,
400 u8,
401 u8
402 );
403 ascii_copy_impl_double!(
404 ascii_to_basic_latin_impl,
405 ascii_to_basic_latin_stride,
406 ascii_to_basic_latin_double_stride,
407 u8,
408 u16
409 );
410 } else {
411 ascii_copy_impl_single!(
412 ascii_to_ascii_impl,
413 ascii_to_ascii_stride,
414 ascii_to_ascii_double_stride,
415 u8,
416 u8
417 );
418 ascii_copy_impl_single!(
419 ascii_to_basic_latin_impl,
420 ascii_to_basic_latin_stride,
421 ascii_to_basic_latin_double_stride,
422 u8,
423 u16
424 );
425 }
426}
427
428cfg_if! {
429 if #[cfg(all(feature = "simd-accel", target_endian = "little"))] {
430 ascii_copy_impl_double!(
431 basic_latin_to_ascii_impl,
432 basic_latin_to_ascii_stride,
433 basic_latin_to_ascii_double_stride,
434 u16,
435 u8
436 );
437 } else {
438 ascii_copy_impl_single!(
439 basic_latin_to_ascii_impl,
440 basic_latin_to_ascii_stride,
441 basic_latin_to_ascii_double_stride,
442 u16,
443 u8
444 );
445 }
446}
447
448macro_rules! ascii_copy {
449 ($name:ident, $impl:ident, $src_unit:ty, $dst_unit:ty) => {
450 #[inline(always)]
451 pub(crate) fn $name(
452 src: &[$src_unit],
453 dst: &mut [$dst_unit],
454 ) -> Option<($src_unit, usize)> {
455 $impl(src, dst)
456 }
457 };
458}
459
460ascii_copy!(ascii_to_ascii, ascii_to_ascii_impl, u8, u8);
461ascii_copy!(ascii_to_basic_latin, ascii_to_basic_latin_impl, u8, u16);
462ascii_copy!(basic_latin_to_ascii, basic_latin_to_ascii_impl, u16, u8);
463
464#[inline(always)]
465pub(crate) fn validate_ascii(bytes: &[u8]) -> Option<(u8, usize)> {
466 ascii_valid_impl(bytes)
467}
468
469#[inline(always)]
470pub(crate) fn ascii_valid_up_to(bytes: &[u8]) -> usize {
471 ascii_valid_impl(bytes)
472 .map(|(_, pos)| pos)
473 .unwrap_or(bytes.len())
474}
475
476pub(crate) fn iso_2022_jp_ascii_valid_up_to(bytes: &[u8]) -> usize {
477 for (i, b_ref) in bytes.iter().enumerate() {
478 let b = *b_ref;
479 if b >= 0x80 || b == 0x1B || b == 0x0E || b == 0x0F {
480 return i;
481 }
482 }
483 bytes.len()
484}
485
486#[cfg(all(test, feature = "alloc"))]
490mod tests {
491 use super::*;
492 use alloc::vec::Vec;
493
494 macro_rules! test_ascii {
495 ($test_name:ident, $fn_tested:ident, $src_unit:ty, $dst_unit:ty) => {
496 #[test]
497 fn $test_name() {
498 let mut src: Vec<$src_unit> = Vec::with_capacity(32);
499 let mut dst: Vec<$dst_unit> = Vec::with_capacity(32);
500 for i in 0..32 {
501 src.clear();
502 dst.clear();
503 dst.resize(32, 0);
504 for j in 0..32 {
505 let c = if i == j { 0xAA } else { j + 0x40 };
506 src.push(c as $src_unit);
507 }
508 match { $fn_tested(&src, &mut dst) } {
509 None => unreachable!("Should always find non-ASCII"),
510 Some((non_ascii, num_ascii)) => {
511 assert_eq!(non_ascii, 0xAA);
512 assert_eq!(num_ascii, i);
513 for j in 0..i {
514 assert_eq!(dst[j], (j + 0x40) as $dst_unit);
515 }
516 }
517 }
518 }
519 }
520 };
521 }
522
523 test_ascii!(test_ascii_to_ascii, ascii_to_ascii, u8, u8);
524 test_ascii!(test_ascii_to_basic_latin, ascii_to_basic_latin, u8, u16);
525 test_ascii!(test_basic_latin_to_ascii, basic_latin_to_ascii, u16, u8);
526}