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