Skip to main content

faster_hex_thiserror/
lib.rs

1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6mod decode;
7mod encode;
8mod error;
9
10#[cfg(feature = "serde")]
11mod serde;
12
13pub use crate::decode::{
14    hex_check, hex_check_fallback, hex_check_with_case, hex_decode, hex_decode_fallback,
15    hex_decode_unchecked,
16};
17pub use crate::encode::{
18    hex_encode, hex_encode_fallback, hex_encode_upper, hex_encode_upper_fallback, hex_string,
19    hex_string_upper,
20};
21
22pub use crate::error::Error;
23
24#[cfg(feature = "serde")]
25pub use crate::serde::{
26    deserialize, nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase, option_nopfx_ignorecase,
27    option_nopfx_lowercase, option_nopfx_uppercase, option_withpfx_ignorecase,
28    option_withpfx_lowercase, option_withpfx_uppercase, serialize, withpfx_ignorecase,
29    withpfx_lowercase, withpfx_uppercase,
30};
31
32#[allow(deprecated)]
33pub use crate::encode::hex_to;
34
35#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
36pub use crate::decode::{hex_check_sse, hex_check_sse_with_case};
37
38#[cfg(target_arch = "aarch64")]
39pub use crate::decode::{hex_check_neon, hex_check_neon_with_case};
40
41#[derive(Copy, Clone, PartialEq, Eq, Debug)]
42#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
43pub(crate) enum Vectorization {
44    None = 0,
45    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
46    SSE41 = 1,
47    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
48    AVX2 = 2,
49    #[cfg(target_arch = "aarch64")]
50    Neon = 3,
51}
52
53#[inline(always)]
54pub(crate) fn vectorization_support() -> Vectorization {
55    #[cfg(all(
56        any(target_arch = "x86", target_arch = "x86_64"),
57        target_feature = "sse"
58    ))]
59    {
60        use core::sync::atomic::{AtomicU8, Ordering};
61        static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
62
63        // We're OK with relaxed, worst case scenario multiple threads checked the CPUID.
64        let current_flags = FLAGS.load(Ordering::Relaxed);
65        // u8::MAX means uninitialized.
66        if current_flags != u8::MAX {
67            return match current_flags {
68                0 => Vectorization::None,
69                1 => Vectorization::SSE41,
70                2 => Vectorization::AVX2,
71                _ => unreachable!(),
72            };
73        }
74
75        let val = vectorization_support_no_cache_x86();
76
77        FLAGS.store(val as u8, Ordering::Relaxed);
78        return val;
79    }
80
81    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
82    {
83        // reuse flag code from x86 impl
84        use core::sync::atomic::{AtomicU8, Ordering};
85        static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
86
87        let current_flags = FLAGS.load(Ordering::Relaxed);
88        if current_flags != u8::MAX {
89            return match current_flags {
90                0 => Vectorization::None,
91                3 => Vectorization::Neon,
92                _ => unreachable!(),
93            };
94        }
95
96        let val = vectorization_support_no_cache_arm();
97        FLAGS.store(val as u8, Ordering::Relaxed);
98        return val;
99    }
100
101    #[allow(unreachable_code)]
102    Vectorization::None
103}
104
105#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
106#[cold]
107fn vectorization_support_no_cache_x86() -> Vectorization {
108    #[cfg(target_arch = "x86")]
109    use core::arch::x86::__cpuid_count;
110    #[cfg(target_arch = "x86_64")]
111    use core::arch::x86_64::__cpuid_count;
112
113    // SGX doesn't support CPUID,
114    // If there's no SSE there might not be CPUID and there's no SSE4.1/AVX2
115    if cfg!(target_env = "sgx") || !cfg!(target_feature = "sse") {
116        return Vectorization::None;
117    }
118
119    let proc_info_ecx = unsafe { __cpuid_count(1, 0) }.ecx;
120    let have_sse4 = (proc_info_ecx >> 19) & 1 == 1;
121    // If there's no SSE4 there can't be AVX2.
122    if !have_sse4 {
123        return Vectorization::None;
124    }
125
126    let have_xsave = (proc_info_ecx >> 26) & 1 == 1;
127    let have_osxsave = (proc_info_ecx >> 27) & 1 == 1;
128    let have_avx = (proc_info_ecx >> 28) & 1 == 1;
129    if have_xsave && have_osxsave && have_avx {
130        // # Safety: We checked that the processor supports xsave
131        if unsafe { avx2_support_no_cache_x86() } {
132            return Vectorization::AVX2;
133        }
134    }
135    Vectorization::SSE41
136}
137
138// We enable xsave so it can inline the _xgetbv call.
139// # Safety: Safe as long it's only called when xsave is supported
140#[target_feature(enable = "xsave")]
141#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
142#[cold]
143unsafe fn avx2_support_no_cache_x86() -> bool {
144    #[cfg(target_arch = "x86")]
145    use core::arch::x86::{__cpuid_count, _xgetbv};
146    #[cfg(target_arch = "x86_64")]
147    use core::arch::x86_64::{__cpuid_count, _xgetbv};
148
149    let xcr0 = _xgetbv(0);
150    let os_avx_support = xcr0 & 6 == 6;
151    if os_avx_support {
152        let extended_features_ebx = __cpuid_count(7, 0).ebx;
153        let have_avx2 = (extended_features_ebx >> 5) & 1 == 1;
154        if have_avx2 {
155            return true;
156        }
157    }
158    false
159}
160
161#[cfg(target_arch = "aarch64")]
162#[cold]
163fn vectorization_support_no_cache_arm() -> Vectorization {
164    #[cfg(feature = "std")]
165    if std::arch::is_aarch64_feature_detected!("neon") {
166        return Vectorization::Neon;
167    }
168    #[cfg(target_feature = "neon")]
169    return Vectorization::Neon;
170
171    #[allow(unreachable_code)]
172    Vectorization::None
173}
174
175#[cfg(test)]
176mod tests {
177    use crate::decode::{hex_decode, hex_decode_with_case, CheckCase};
178    use crate::encode::{hex_encode, hex_string};
179    use crate::{hex_encode_upper, hex_string_upper, vectorization_support, Vectorization};
180    use proptest::proptest;
181
182    #[cfg(not(feature = "alloc"))]
183    const CAPACITY: usize = 128;
184
185    #[test]
186    fn test_feature_detection() {
187        let vector_support = vectorization_support();
188        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
189        {
190            match vector_support {
191                Vectorization::AVX2 => assert!(is_x86_feature_detected!("avx2")),
192                Vectorization::SSE41 => assert!(is_x86_feature_detected!("sse4.1")),
193                Vectorization::None => assert!(
194                    !cfg!(target_feature = "sse")
195                        || !is_x86_feature_detected!("avx2") && !is_x86_feature_detected!("sse4.1")
196                ),
197            }
198        }
199
200        #[cfg(target_arch = "aarch64")]
201        match vector_support {
202            Vectorization::Neon => assert!(std::arch::is_aarch64_feature_detected!("neon")),
203            Vectorization::None => assert!(
204                !cfg!(target_feature = "neon") || !std::arch::is_aarch64_feature_detected!("neon")
205            ),
206        }
207
208        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
209        assert_eq!(vector_support, Vectorization::None);
210    }
211
212    fn _test_hex_encode(s: &String) {
213        let mut buffer = vec![0; s.as_bytes().len() * 2];
214        {
215            let encode = &*hex_encode(s.as_bytes(), &mut buffer).unwrap();
216
217            #[cfg(feature = "alloc")]
218            let hex_string = hex_string(s.as_bytes());
219            #[cfg(not(feature = "alloc"))]
220            let hex_string = hex_string::<CAPACITY>(s.as_bytes());
221
222            assert_eq!(encode, hex::encode(s));
223            assert_eq!(hex_string.as_str(), hex::encode(s));
224        }
225
226        {
227            let encode_upper = &*hex_encode_upper(s.as_bytes(), &mut buffer).unwrap();
228
229            #[cfg(feature = "alloc")]
230            let hex_string_upper = hex_string_upper(s.as_bytes());
231            #[cfg(not(feature = "alloc"))]
232            let hex_string_upper = hex_string_upper::<CAPACITY>(s.as_bytes());
233
234            assert_eq!(encode_upper, hex::encode_upper(s));
235            assert_eq!(hex_string_upper.as_str(), hex::encode_upper(s));
236        }
237    }
238
239    #[cfg(feature = "alloc")]
240    proptest! {
241        #[test]
242        fn test_hex_encode(ref s in ".*") {
243            _test_hex_encode(s);
244        }
245    }
246
247    #[cfg(not(feature = "alloc"))]
248    proptest! {
249        #[test]
250        fn test_hex_encode(ref s in ".{0,16}") {
251            _test_hex_encode(s);
252        }
253    }
254
255    fn _test_hex_decode(s: &String) {
256        let len = s.as_bytes().len();
257        {
258            let mut dst = Vec::with_capacity(len);
259            dst.resize(len, 0);
260            #[cfg(feature = "alloc")]
261            let hex_string = hex_string(s.as_bytes());
262            #[cfg(not(feature = "alloc"))]
263            let hex_string = hex_string::<CAPACITY>(s.as_bytes());
264
265            hex_decode(hex_string.as_bytes(), &mut dst).unwrap();
266
267            hex_decode_with_case(hex_string.as_bytes(), &mut dst, CheckCase::Lower).unwrap();
268
269            assert_eq!(&dst[..], s.as_bytes());
270        }
271        {
272            let mut dst = Vec::with_capacity(len);
273            dst.resize(len, 0);
274            #[cfg(feature = "alloc")]
275            let hex_string_upper = hex_string_upper(s.as_bytes());
276            #[cfg(not(feature = "alloc"))]
277            let hex_string_upper = hex_string_upper::<CAPACITY>(s.as_bytes());
278
279            hex_decode_with_case(hex_string_upper.as_bytes(), &mut dst, CheckCase::Upper).unwrap();
280
281            assert_eq!(&dst[..], s.as_bytes());
282        }
283    }
284
285    #[cfg(feature = "alloc")]
286    proptest! {
287        #[test]
288        fn test_hex_decode(ref s in ".+") {
289            _test_hex_decode(s);
290        }
291    }
292
293    #[cfg(not(feature = "alloc"))]
294    proptest! {
295        #[test]
296        fn test_hex_decode(ref s in ".{1,16}") {
297            _test_hex_decode(s);
298        }
299    }
300
301    fn _test_hex_decode_check(s: &String, ok: bool) {
302        let len = s.as_bytes().len();
303        let mut dst = Vec::with_capacity(len / 2);
304        dst.resize(len / 2, 0);
305        assert!(hex_decode(s.as_bytes(), &mut dst).is_ok() == ok);
306    }
307
308    proptest! {
309        #[test]
310        fn test_hex_decode_check(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
311            _test_hex_decode_check(s, true);
312        }
313    }
314
315    proptest! {
316        #[test]
317        fn test_hex_decode_check_odd(ref s in "[0-9a-fA-F]{11}") {
318            _test_hex_decode_check(s, false);
319        }
320    }
321}