1#[cfg(target_arch = "x86")]
22use core::arch::x86 as arch;
23#[cfg(target_arch = "x86_64")]
24use core::arch::x86_64 as arch;
25
26#[derive(Clone, Copy)]
28enum Kind {
29 Sse,
31 #[cfg(stable_vpclmulqdq)]
33 Avx2,
34 #[cfg(stable_vpclmulqdq)]
36 Avx512,
37}
38
39#[derive(Clone)]
40pub struct State {
41 kind: Kind,
42}
43
44impl State {
45 #[cfg(not(feature = "std"))]
46 #[inline]
47 fn detect() -> Option<Kind> {
48 if cfg!(target_feature = "pclmulqdq")
49 && cfg!(target_feature = "sse2")
50 && cfg!(target_feature = "sse4.1")
51 && cfg!(target_feature = "ssse3")
52 {
53 #[cfg(stable_vpclmulqdq)]
54 {
55 if cfg!(target_feature = "avx512f") && cfg!(target_feature = "vpclmulqdq") {
56 return Some(Kind::Avx512);
57 }
58 if cfg!(target_feature = "avx2") && cfg!(target_feature = "vpclmulqdq") {
59 return Some(Kind::Avx2);
60 }
61 }
62
63 return Some(Kind::Sse);
64 }
65
66 None
67 }
68
69 #[cfg(feature = "std")]
70 #[inline]
71 fn detect() -> Option<Kind> {
72 if is_x86_feature_detected!("pclmulqdq")
73 && is_x86_feature_detected!("sse2")
74 && is_x86_feature_detected!("sse4.1")
75 && is_x86_feature_detected!("ssse3")
76 {
77 #[cfg(stable_vpclmulqdq)]
78 {
79 if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("vpclmulqdq") {
80 return Some(Kind::Avx512);
81 }
82 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("vpclmulqdq") {
83 return Some(Kind::Avx2);
84 }
85 }
86
87 return Some(Kind::Sse);
88 }
89
90 None
91 }
92
93 #[inline]
94 pub fn new() -> Option<Self> {
95 Self::detect().map(|kind| Self { kind })
97 }
98
99 #[inline]
100 pub fn update(&self, state: u32, buf: &[u8]) -> u32 {
101 unsafe {
105 match self.kind {
106 Kind::Sse => calculate(state, buf),
107 #[cfg(stable_vpclmulqdq)]
108 Kind::Avx2 => calculate_avx2(state, buf),
109 #[cfg(stable_vpclmulqdq)]
110 Kind::Avx512 => calculate_avx512(state, buf),
111 }
112 }
113 }
114}
115
116const K1: i64 = 0x154442bd4;
117const K2: i64 = 0x1c6e41596;
118const K3: i64 = 0x1751997d0;
119const K4: i64 = 0x0ccaa009e;
120const K5: i64 = 0x163cd6124;
121
122const P_X: i64 = 0x1DB710641;
123const U_PRIME: i64 = 0x1F7011641;
124
125const MIN_FOLD_BY_4_BYTES: usize = 128;
128#[cfg(stable_vpclmulqdq)]
129const MIN_AVX512_BYTES: usize = 2 * 1024;
130
131#[cfg(stable_vpclmulqdq)]
135const K_1024_LOW: i64 = 0x1e88ef372;
136#[cfg(stable_vpclmulqdq)]
137const K_1024_HIGH: i64 = 0x14a7fe880;
138#[cfg(stable_vpclmulqdq)]
139const K_2048_LOW: i64 = 0x11542778a;
140#[cfg(stable_vpclmulqdq)]
141const K_2048_HIGH: i64 = 0x1322d1430;
142
143#[target_feature(
144 enable = "pclmulqdq",
145 enable = "sse2",
146 enable = "sse4.1",
147 enable = "ssse3"
148)]
149unsafe fn calculate(crc: u32, mut data: &[u8]) -> u32 {
150 if data.len() < 16 {
152 return crate::baseline::update_short(crc, data);
153 }
154
155 if data.len() < MIN_FOLD_BY_4_BYTES {
158 let mut x = get(&mut data);
159 x = arch::_mm_xor_si128(x, arch::_mm_cvtsi32_si128(!crc as i32));
160 return reduce_128_to_crc(x, data);
161 }
162
163 let mut x3 = get(&mut data);
165 let mut x2 = get(&mut data);
166 let mut x1 = get(&mut data);
167 let mut x0 = get(&mut data);
168
169 x3 = arch::_mm_xor_si128(x3, arch::_mm_cvtsi32_si128(!crc as i32));
171
172 let k1k2 = arch::_mm_set_epi64x(K2, K1);
173 while data.len() >= 64 {
174 x3 = reduce128(x3, get(&mut data), k1k2);
175 x2 = reduce128(x2, get(&mut data), k1k2);
176 x1 = reduce128(x1, get(&mut data), k1k2);
177 x0 = reduce128(x0, get(&mut data), k1k2);
178 }
179
180 let k3k4 = arch::_mm_set_epi64x(K4, K3);
181 let mut x = reduce128(x3, x2, k3k4);
182 x = reduce128(x, x1, k3k4);
183 x = reduce128(x, x0, k3k4);
184
185 reduce_128_to_crc(x, data)
186}
187
188#[cfg(stable_vpclmulqdq)]
191#[allow(clippy::incompatible_msrv)] #[target_feature(
193 enable = "pclmulqdq",
194 enable = "sse2",
195 enable = "sse4.1",
196 enable = "ssse3",
197 enable = "avx",
198 enable = "avx2",
199 enable = "vpclmulqdq"
200)]
201unsafe fn calculate_avx2(crc: u32, mut data: &[u8]) -> u32 {
202 if data.len() < 256 {
204 return calculate(crc, data);
205 }
206
207 let mut v0 = get256(&mut data);
209 let mut v1 = get256(&mut data);
210 let mut v2 = get256(&mut data);
211 let mut v3 = get256(&mut data);
212
213 v0 = arch::_mm256_xor_si256(
215 v0,
216 arch::_mm256_castsi128_si256(arch::_mm_cvtsi32_si128(!crc as i32)),
217 );
218
219 let k = arch::_mm256_set_epi64x(K_1024_HIGH, K_1024_LOW, K_1024_HIGH, K_1024_LOW);
220 while data.len() >= 128 {
221 v0 = reduce256(v0, get256(&mut data), k);
222 v1 = reduce256(v1, get256(&mut data), k);
223 v2 = reduce256(v2, get256(&mut data), k);
224 v3 = reduce256(v3, get256(&mut data), k);
225 }
226
227 let k3k4 = arch::_mm_set_epi64x(K4, K3);
229 let mut x = arch::_mm256_castsi256_si128(v0);
230 x = reduce128(x, arch::_mm256_extracti128_si256(v0, 1), k3k4);
231 x = reduce128(x, arch::_mm256_castsi256_si128(v1), k3k4);
232 x = reduce128(x, arch::_mm256_extracti128_si256(v1, 1), k3k4);
233 x = reduce128(x, arch::_mm256_castsi256_si128(v2), k3k4);
234 x = reduce128(x, arch::_mm256_extracti128_si256(v2, 1), k3k4);
235 x = reduce128(x, arch::_mm256_castsi256_si128(v3), k3k4);
236 x = reduce128(x, arch::_mm256_extracti128_si256(v3, 1), k3k4);
237
238 reduce_128_to_crc(x, data)
239}
240
241#[cfg(stable_vpclmulqdq)]
244#[allow(clippy::incompatible_msrv)] #[target_feature(
246 enable = "pclmulqdq",
247 enable = "sse2",
248 enable = "sse4.1",
249 enable = "ssse3",
250 enable = "avx",
251 enable = "avx2",
252 enable = "vpclmulqdq",
253 enable = "avx512f"
254)]
255unsafe fn calculate_avx512(crc: u32, mut data: &[u8]) -> u32 {
256 if data.len() < MIN_AVX512_BYTES {
258 return calculate_avx2(crc, data);
259 }
260
261 let mut v0 = get512(&mut data);
263 let mut v1 = get512(&mut data);
264 let mut v2 = get512(&mut data);
265 let mut v3 = get512(&mut data);
266
267 v0 = arch::_mm512_xor_si512(
269 v0,
270 arch::_mm512_castsi128_si512(arch::_mm_cvtsi32_si128(!crc as i32)),
271 );
272
273 let k = arch::_mm512_set_epi64(
274 K_2048_HIGH,
275 K_2048_LOW,
276 K_2048_HIGH,
277 K_2048_LOW,
278 K_2048_HIGH,
279 K_2048_LOW,
280 K_2048_HIGH,
281 K_2048_LOW,
282 );
283 while data.len() >= 256 {
284 v0 = reduce512(v0, get512(&mut data), k);
285 v1 = reduce512(v1, get512(&mut data), k);
286 v2 = reduce512(v2, get512(&mut data), k);
287 v3 = reduce512(v3, get512(&mut data), k);
288 }
289
290 let k3k4 = arch::_mm_set_epi64x(K4, K3);
292 let mut x = arch::_mm512_castsi512_si128(v0);
293 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 1), k3k4);
294 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 2), k3k4);
295 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 3), k3k4);
296 x = reduce128(x, arch::_mm512_castsi512_si128(v1), k3k4);
297 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 1), k3k4);
298 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 2), k3k4);
299 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 3), k3k4);
300 x = reduce128(x, arch::_mm512_castsi512_si128(v2), k3k4);
301 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 1), k3k4);
302 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 2), k3k4);
303 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 3), k3k4);
304 x = reduce128(x, arch::_mm512_castsi512_si128(v3), k3k4);
305 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 1), k3k4);
306 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 2), k3k4);
307 x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 3), k3k4);
308
309 if data.len() >= MIN_FOLD_BY_4_BYTES {
312 let crc = reduce_128_to_crc(x, &[]);
313 return calculate(crc, data);
314 }
315
316 reduce_128_to_crc(x, data)
317}
318
319#[target_feature(
323 enable = "pclmulqdq",
324 enable = "sse2",
325 enable = "sse4.1",
326 enable = "ssse3"
327)]
328unsafe fn reduce_128_to_crc(mut x: arch::__m128i, mut data: &[u8]) -> u32 {
329 let k3k4 = arch::_mm_set_epi64x(K4, K3);
330
331 while data.len() >= 16 {
333 x = reduce128(x, get(&mut data), k3k4);
334 }
335
336 let n = data.len();
341 if n > 0 {
342 let seq = arch::_mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
343 let shl = arch::_mm_add_epi8(seq, arch::_mm_set1_epi8(n as i8 - 16));
344 let shr = arch::_mm_xor_si128(shl, arch::_mm_set1_epi8(-128));
345
346 let overflow = arch::_mm_shuffle_epi8(x, shl);
347 x = arch::_mm_shuffle_epi8(x, shr);
348
349 let (lo, hi) = partial_words(data);
350 let part = arch::_mm_set_epi64x(hi as i64, lo as i64);
351 x = arch::_mm_xor_si128(x, arch::_mm_shuffle_epi8(part, shl));
352
353 x = reduce128(overflow, x, k3k4);
354 }
355
356 let x = arch::_mm_xor_si128(
375 arch::_mm_clmulepi64_si128(x, k3k4, 0x10),
376 arch::_mm_srli_si128(x, 8),
377 );
378 let x = arch::_mm_xor_si128(
379 arch::_mm_clmulepi64_si128(
380 arch::_mm_and_si128(x, arch::_mm_set_epi32(0, 0, 0, !0)),
381 arch::_mm_set_epi64x(0, K5),
382 0x00,
383 ),
384 arch::_mm_srli_si128(x, 4),
385 );
386
387 let pu = arch::_mm_set_epi64x(U_PRIME, P_X);
391
392 let t1 = arch::_mm_clmulepi64_si128(
394 arch::_mm_and_si128(x, arch::_mm_set_epi32(0, 0, 0, !0)),
395 pu,
396 0x10,
397 );
398 let t2 = arch::_mm_clmulepi64_si128(
400 arch::_mm_and_si128(t1, arch::_mm_set_epi32(0, 0, 0, !0)),
401 pu,
402 0x00,
403 );
404 !(arch::_mm_extract_epi32(arch::_mm_xor_si128(x, t2), 1) as u32)
409}
410
411#[inline]
412unsafe fn reduce128(a: arch::__m128i, b: arch::__m128i, keys: arch::__m128i) -> arch::__m128i {
413 let t1 = arch::_mm_clmulepi64_si128(a, keys, 0x00);
414 let t2 = arch::_mm_clmulepi64_si128(a, keys, 0x11);
415 arch::_mm_xor_si128(arch::_mm_xor_si128(b, t1), t2)
416}
417
418#[cfg(stable_vpclmulqdq)]
419#[allow(clippy::incompatible_msrv)] #[target_feature(enable = "avx2", enable = "vpclmulqdq")]
421#[inline]
422unsafe fn reduce256(a: arch::__m256i, b: arch::__m256i, keys: arch::__m256i) -> arch::__m256i {
423 let t1 = arch::_mm256_clmulepi64_epi128(a, keys, 0x00);
424 let t2 = arch::_mm256_clmulepi64_epi128(a, keys, 0x11);
425 arch::_mm256_xor_si256(arch::_mm256_xor_si256(b, t1), t2)
426}
427
428#[cfg(stable_vpclmulqdq)]
429#[allow(clippy::incompatible_msrv)] #[target_feature(enable = "avx512f", enable = "vpclmulqdq")]
431#[inline]
432unsafe fn reduce512(a: arch::__m512i, b: arch::__m512i, keys: arch::__m512i) -> arch::__m512i {
433 let t1 = arch::_mm512_clmulepi64_epi128(a, keys, 0x00);
434 let t2 = arch::_mm512_clmulepi64_epi128(a, keys, 0x11);
435 arch::_mm512_xor_si512(arch::_mm512_xor_si512(b, t1), t2)
436}
437
438#[inline]
444fn get(a: &mut &[u8]) -> arch::__m128i {
445 let r = unsafe { arch::_mm_loadu_si128(a[..16].as_ptr() as *const arch::__m128i) };
448 *a = &a[16..];
449 r
450}
451
452#[cfg(stable_vpclmulqdq)]
453#[target_feature(enable = "avx")]
454#[inline]
455unsafe fn get256(a: &mut &[u8]) -> arch::__m256i {
456 let r = arch::_mm256_loadu_si256(a[..32].as_ptr() as *const arch::__m256i);
457 *a = &a[32..];
458 r
459}
460
461#[cfg(stable_vpclmulqdq)]
462#[allow(clippy::incompatible_msrv)] #[target_feature(enable = "avx512f")]
464#[inline]
465unsafe fn get512(a: &mut &[u8]) -> arch::__m512i {
466 let r = arch::_mm512_loadu_si512(a[..64].as_ptr() as *const _);
467 *a = &a[64..];
468 r
469}
470
471#[inline]
475fn partial_words(data: &[u8]) -> (u64, u64) {
476 let n = data.len();
477 debug_assert!(n > 0 && n < 16);
478 let v: u128 = if n >= 8 {
479 let lo = u64::from_le_bytes(data[..8].try_into().unwrap());
480 let hi = u64::from_le_bytes(data[n - 8..].try_into().unwrap());
481 lo as u128 | (hi as u128) << (8 * (n - 8))
482 } else if n >= 4 {
483 let lo = u32::from_le_bytes(data[..4].try_into().unwrap());
484 let hi = u32::from_le_bytes(data[n - 4..].try_into().unwrap());
485 lo as u128 | (hi as u128) << (8 * (n - 4))
486 } else {
487 data[0] as u128
488 | (data[n / 2] as u128) << (8 * (n / 2))
489 | (data[n - 1] as u128) << (8 * (n - 1))
490 };
491 (v as u64, (v >> 64) as u64)
492}
493
494#[cfg(test)]
495mod test {
496 quickcheck::quickcheck! {
497 fn check_against_baseline(init: u32, chunks: Vec<(Vec<u8>, usize)>) -> bool {
498 let mut baseline = init;
499 let mut pclmulqdq = init;
500 let spec = super::State::new().expect("not supported");
501
502 for (chunk, mut offset) in chunks {
503 offset &= 0xF;
505 if chunk.len() <= offset {
506 baseline = crate::baseline::update_fast_16(baseline, &chunk);
507 pclmulqdq = spec.update(pclmulqdq, &chunk);
508 } else {
509 baseline = crate::baseline::update_fast_16(baseline, &chunk[offset..]);
510 pclmulqdq = spec.update(pclmulqdq, &chunk[offset..]);
511 }
512 }
513 pclmulqdq == baseline
514 }
515 }
516
517 #[test]
519 fn check_large_inputs_against_baseline() {
520 let mut data = vec![0u8; 8200];
521 let mut s: u32 = 0x1234_5678;
522 for b in data.iter_mut() {
523 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
524 *b = (s >> 24) as u8;
525 }
526
527 for &len in &[
528 0usize, 1, 15, 16, 17, 63, 64, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024,
529 1025, 2047, 2048, 2049, 2175, 2176, 2303, 2304, 4096, 8192, 8199,
530 ] {
531 for &offset in &[0usize, 1, 3, 7, 8, 15] {
532 if offset + len > data.len() {
533 continue;
534 }
535 let slice = &data[offset..offset + len];
536 let baseline = crate::baseline::update_fast_16(0, slice);
537 let spec = super::State::new().expect("not supported");
538 let specialized = spec.update(0, slice);
539
540 assert_eq!(
541 specialized, baseline,
542 "mismatch for len={len} offset={offset}",
543 );
544 }
545 }
546 }
547}