Skip to main content

encoding_rs/
ascii.rs

1// Copyright Mozilla Foundation. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10// `as` truncates, so works on 32-bit, too.
11// Safety invariant: `data & ASCII_MASK == 0` for valid ASCII
12#[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        // These are `inline(never)`, because the autovectorizer can vectorize the
32        // full-stride operations in isolation but not when combining the ASCII/Basic Latin
33        // check and the pack/unpack!
34
35        #[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            // Make both the same length here to have the chunks and tail match.
202            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            // Make both the same length here to have the chunks and tail match.
256            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// Any copyright to the test code below this comment is dedicated to the
487// Public Domain. http://creativecommons.org/publicdomain/zero/1.0/
488
489#[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}