crc_fast/arch/
mod.rs

1// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2
3//! This module provides the main entry point for the SIMD CRC calculation.
4//!
5//! It dispatches to the appropriate architecture-specific implementation
6//! based on the target architecture.
7
8#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
9use crate::algorithm;
10
11use crate::structs::CrcParams;
12
13#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
14use crate::structs::{Width32, Width64};
15
16#[cfg(target_arch = "aarch64")]
17use crate::arch::aarch64::AArch64Ops;
18
19#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
20use crate::arch::x86::X86Ops;
21
22#[cfg(all(target_arch = "x86_64", feature = "vpclmulqdq"))]
23use crate::arch::vpclmulqdq::VpclmulqdqOps;
24
25pub(crate) mod aarch64;
26mod software;
27mod vpclmulqdq;
28pub(crate) mod x86;
29
30/// Main entry point that dispatches to the appropriate architecture
31///
32///
33/// # Safety
34/// May use native CPU features
35#[inline]
36pub(crate) unsafe fn update(state: u64, bytes: &[u8], params: CrcParams) -> u64 {
37    #[cfg(target_arch = "aarch64")]
38    {
39        let ops = AArch64Ops;
40
41        match params.width {
42            64 => algorithm::update::<AArch64Ops, Width64>(state, bytes, params, &ops),
43            32 => {
44                algorithm::update::<AArch64Ops, Width32>(state as u32, bytes, params, &ops) as u64
45            }
46            _ => panic!("Unsupported CRC width: {}", params.width),
47        }
48    }
49
50    #[cfg(all(target_arch = "x86_64", feature = "vpclmulqdq"))]
51    {
52        use std::arch::is_x86_feature_detected;
53
54        if bytes.len() >= 256 && is_x86_feature_detected!("vpclmulqdq") {
55            let ops = vpclmulqdq::VpclmulqdqOps::new();
56
57            return match params.width {
58                64 => algorithm::update::<VpclmulqdqOps, Width64>(state, bytes, params, &ops),
59                32 => {
60                    algorithm::update::<VpclmulqdqOps, Width32>(state as u32, bytes, params, &ops)
61                        as u64
62                }
63                _ => panic!("Unsupported CRC width: {}", params.width),
64            };
65        }
66    }
67
68    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
69    {
70        let ops = X86Ops;
71
72        match params.width {
73            64 => algorithm::update::<X86Ops, Width64>(state, bytes, params, &ops),
74            32 => algorithm::update::<X86Ops, Width32>(state as u32, bytes, params, &ops) as u64,
75            _ => panic!("Unsupported CRC width: {}", params.width),
76        }
77    }
78
79    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
80    return software::update(state, bytes, params);
81}
82
83pub fn get_target() -> String {
84    #[cfg(target_arch = "aarch64")]
85    return "internal-aarch64-neon".to_string();
86
87    #[cfg(all(target_arch = "x86_64", feature = "vpclmulqdq"))]
88    return "internal-x86_64-avx512-vpclmulqdq".to_string();
89
90    #[allow(unreachable_code)]
91    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
92    return "internal-x86-sse-pclmulqdq".to_string();
93
94    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))]
95    return "software-fallback".to_string();
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::crc32::consts::CRC32_BZIP2;
102    use crate::crc64::consts::CRC64_NVME;
103    use crate::test::consts::{TEST_256_BYTES_STRING, TEST_ALL_CONFIGS, TEST_CHECK_STRING};
104    use rand::{rng, Rng};
105
106    #[test]
107    fn test_check_value() {
108        for config in TEST_ALL_CONFIGS {
109            // direct update() call, which needs XOROUT applied
110            let actual = unsafe {
111                update(config.get_init(), TEST_CHECK_STRING, *config.get_params())
112                    ^ config.get_xorout()
113            };
114
115            assert_eq!(
116                actual,
117                config.get_check(),
118                "Mismatch CRC, {}, expected {:#x}, got {:#x}",
119                config.get_name(),
120                config.get_check(),
121                actual
122            );
123        }
124    }
125
126    // CRC-64/NVME is a special flower in that Rust's crc library doesn't support it yet, so we have
127    // tested values to check against.
128    #[test]
129    fn test_crc64_nvme_standard_vectors() {
130        static CASES: &[(&[u8], u64)] = &[
131            // from our own internal tests, since the Check value in the NVM Express® NVM Command
132            // Set Specification (Revision 1.0d, December 2023) is incorrect
133            // (Section 5.2.1.3.4, Figure 120, page 83).
134            (b"123456789", 0xae8b14860a799888),
135
136            // from the NVM Express® NVM Command Set Specification (Revision 1.0d, December 2023),
137            // Section 5.2.1.3.5, Figure 122, page 84.
138            // https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0d-2023.12.28-Ratified.pdf
139            // and the Linux kernel
140            // https://github.com/torvalds/linux/blob/f3813f4b287e480b1fcd62ca798d8556644b8278/crypto/testmgr.h#L3685-L3695
141            (&[0; 4096], 0x6482d367eb22b64e),
142            (&[255; 4096], 0xc0ddba7302eca3ac),
143
144            // custom values
145            (TEST_256_BYTES_STRING, 0xabdb9e6c30937916),
146            (b"", 0),
147            (b"@", 0x2808afa9582aa47),
148            (b"1\x97", 0xb4af0ae0feb08e0f),
149            (b"M\"\xdf", 0x85d7cd041a2a8a5d),
150            (b"l\xcd\x13\xd7", 0x1860820ea79b0fa3),
151            (&[0; 32], 0xcf3473434d4ecf3b),
152            (&[255; 32], 0xa0a06974c34d63c4),
153            (b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", 0xb9d9d4a8492cbd7f),
154            (&[0; 1024], 0x691bb2b09be5498a),
155            (b"hello, world!", 0xf8046e40c403f1d0),
156        ];
157
158        for (input, expected) in CASES {
159            unsafe {
160                let actual = update(CRC64_NVME.init, input, CRC64_NVME) ^ CRC64_NVME.xorout;
161
162                assert_eq!(
163                    actual, *expected,
164                    "Mismatch CRC, expected {:#x}, got {:#x}, input: {:?}",
165                    expected, actual, input
166                );
167            }
168        }
169    }
170
171    /// Test the "crc32" variant used in PHP's hash() function, which is different from the
172    /// crc32() function. It's really just CRC-32/BZIP2 with the output byte-reversed to little
173    /// endian.
174    ///
175    /// https://www.php.net/manual/en/function.hash-file.php#104836
176    #[test]
177    fn test_crc32_php_standard_vectors() {
178        static CASES: &[(&[u8], u64)] = &[
179            (b"123456789", 0x181989fc),
180            (&[0; 4096], 0xe3380088),
181            (&[255; 4096], 0x8f2ae650),
182            (b"hello, world!", 0x5eacce7),
183        ];
184
185        for (input, expected) in CASES {
186            let bzip2_crc = unsafe {
187                (update(CRC32_BZIP2.init, input, CRC32_BZIP2) ^ CRC32_BZIP2.xorout) as u32
188            };
189
190            // PHP reverses the byte order of the CRC for some reason
191            let actual = bzip2_crc.swap_bytes();
192
193            assert_eq!(
194                actual, *expected as u32,
195                "Mismatch CRC, expected {:#x}, got {:#x}, input: {:?}",
196                expected, actual, input
197            );
198        }
199    }
200
201    #[test]
202    fn test_small_lengths_all() {
203        let mut rng = rng();
204
205        // Test each CRC-64 variant
206        for config in TEST_ALL_CONFIGS {
207            // Test each length from 0 to 255
208            for len in 0..=255 {
209                // Generate random data for this length
210                let mut data = vec![0u8; len];
211                rng.fill(&mut data[..]);
212
213                // Calculate expected CRC using the reference implementation
214                let expected = config.checksum_with_reference(&data);
215
216                // direct update() call, which needs XOROUT applied
217                let actual = unsafe {
218                    update(config.get_init(), &data, *config.get_params()) ^ config.get_xorout()
219                };
220
221                assert_eq!(
222                    actual,
223                    expected,
224                    "\nFailed for {} with length {}\nGot: {:016x}\nExpected: {:016x}",
225                    config.get_name(),
226                    len,
227                    actual,
228                    expected
229                );
230            }
231        }
232    }
233
234    #[test]
235    fn test_medium_lengths() {
236        let mut rng = rng();
237
238        // Test each CRC-64 variant
239        for config in TEST_ALL_CONFIGS {
240            // Test each length from 256 to 1024, which should fold and include handling remainders
241            for len in 256..=1024 {
242                // Generate random data for this length
243                let mut data = vec![0u8; len];
244                rng.fill(&mut data[..]);
245
246                // Calculate expected CRC using the reference implementation
247                let expected = config.checksum_with_reference(&data);
248
249                // direct update() call, which needs XOROUT applied
250                let actual = unsafe {
251                    update(config.get_init(), &data, *config.get_params()) ^ config.get_xorout()
252                };
253
254                assert_eq!(
255                    actual,
256                    expected,
257                    "\nFailed for {} with length {}\nGot: {:016x}\nExpected: {:016x}",
258                    config.get_name(),
259                    len,
260                    actual,
261                    expected
262                );
263            }
264        }
265    }
266
267    #[test]
268    fn test_large_lengths() {
269        let mut rng = rng();
270
271        // Test each CRC-64 variant
272        for config in TEST_ALL_CONFIGS {
273            // Test ~1 MiB just before, at, and just after the folding boundaries
274            for len in 1048575..=1048577 {
275                // Generate random data for this length
276                let mut data = vec![0u8; len];
277                rng.fill(&mut data[..]);
278
279                // Calculate expected CRC using the reference implementation
280                let expected = config.checksum_with_reference(&data);
281
282                // direct update() call, which needs XOROUT applied
283                let actual = unsafe {
284                    update(config.get_init(), &data, *config.get_params()) ^ config.get_xorout()
285                };
286
287                assert_eq!(
288                    actual,
289                    expected,
290                    "\nFailed for {} with length {}\\nGot: {:016x}\nExpected: {:016x}",
291                    config.get_name(),
292                    len,
293                    actual,
294                    expected
295                );
296            }
297        }
298    }
299}