/rust/registry/src/index.crates.io-1949cf8c6b5b557f/crc32fast-1.5.1/src/specialized/pclmulqdq.rs
Line | Count | Source |
1 | | //! Specialized checksum code for the x86 CPU architecture, based on the efficient algorithm described |
2 | | //! in the following whitepaper: |
3 | | //! |
4 | | //! Gopal, V., Ozturk, E., Guilford, J., Wolrich, G., Feghali, W., Dixon, M., & Karakoyunlu, D. (2009). |
5 | | //! _Fast CRC computation for generic polynomials using PCLMULQDQ instruction_. Intel. |
6 | | //! (Mirror link: <https://fossies.org/linux/zlib-ng/doc/crc-pclmulqdq.pdf>, accessed 2024-05-20) |
7 | | //! |
8 | | //! Throughout the code, this work is referred to as "the paper". |
9 | | //! |
10 | | //! On top of the 128-bit `PCLMULQDQ` implementation, two wider variants use `VPCLMULQDQ` to fold |
11 | | //! several independent 128-bit streams per instruction: an `AVX2` variant over 256-bit `YMM` |
12 | | //! registers (8 streams) and an `AVX-512` variant over 512-bit `ZMM` registers (16 streams). Both |
13 | | //! rely on `VPCLMULQDQ` intrinsics stabilized in Rust 1.89 and are only compiled when the |
14 | | //! `stable_vpclmulqdq` cfg is set by `build.rs`, leaving the crate MSRV unchanged otherwise. The |
15 | | //! best variant supported by the running CPU is chosen at runtime. |
16 | | |
17 | | #[cfg(target_arch = "x86")] |
18 | | use core::arch::x86 as arch; |
19 | | #[cfg(target_arch = "x86_64")] |
20 | | use core::arch::x86_64 as arch; |
21 | | |
22 | | /// Which SIMD implementation to use, chosen once at construction from the CPU's features. |
23 | | #[derive(Clone, Copy)] |
24 | | enum Kind { |
25 | | /// 128-bit `PCLMULQDQ`, fold by 4. |
26 | | Sse, |
27 | | /// 256-bit `VPCLMULQDQ`, 8 streams. |
28 | | #[cfg(stable_vpclmulqdq)] |
29 | | Avx2, |
30 | | /// 512-bit `VPCLMULQDQ`, 16 streams. |
31 | | #[cfg(stable_vpclmulqdq)] |
32 | | Avx512, |
33 | | } |
34 | | |
35 | | #[derive(Clone)] |
36 | | pub struct State { |
37 | | state: u32, |
38 | | kind: Kind, |
39 | | } |
40 | | |
41 | | impl State { |
42 | | #[cfg(not(feature = "std"))] |
43 | | fn detect() -> Option<Kind> { |
44 | | if cfg!(target_feature = "pclmulqdq") |
45 | | && cfg!(target_feature = "sse2") |
46 | | && cfg!(target_feature = "sse4.1") |
47 | | && cfg!(target_feature = "ssse3") |
48 | | { |
49 | | #[cfg(stable_vpclmulqdq)] |
50 | | { |
51 | | if cfg!(target_feature = "avx512f") && cfg!(target_feature = "vpclmulqdq") { |
52 | | return Some(Kind::Avx512); |
53 | | } |
54 | | if cfg!(target_feature = "avx2") && cfg!(target_feature = "vpclmulqdq") { |
55 | | return Some(Kind::Avx2); |
56 | | } |
57 | | } |
58 | | |
59 | | return Some(Kind::Sse); |
60 | | } |
61 | | |
62 | | None |
63 | | } |
64 | | |
65 | | #[cfg(feature = "std")] |
66 | 15.5k | fn detect() -> Option<Kind> { |
67 | 15.5k | if is_x86_feature_detected!("pclmulqdq") |
68 | 15.5k | && is_x86_feature_detected!("sse2") |
69 | 15.5k | && is_x86_feature_detected!("sse4.1") |
70 | 15.5k | && is_x86_feature_detected!("ssse3") |
71 | | { |
72 | | #[cfg(stable_vpclmulqdq)] |
73 | | { |
74 | 15.5k | if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("vpclmulqdq") { |
75 | 0 | return Some(Kind::Avx512); |
76 | 15.5k | } |
77 | 15.5k | if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("vpclmulqdq") { |
78 | 0 | return Some(Kind::Avx2); |
79 | 15.5k | } |
80 | | } |
81 | | |
82 | 15.5k | return Some(Kind::Sse); |
83 | 0 | } |
84 | | |
85 | 0 | None |
86 | 15.5k | } |
87 | | |
88 | 15.5k | pub fn new(state: u32) -> Option<Self> { |
89 | | // SAFETY: `detect` only returns a `Kind` whose instructions the CPU supports. |
90 | 15.5k | Self::detect().map(|kind| Self { state, kind }) |
91 | 15.5k | } |
92 | | |
93 | 1.10M | pub fn update(&mut self, buf: &[u8]) { |
94 | | // SAFETY: `State::new` ensured the CPU supports the instructions for `self.kind`. |
95 | | self.state = unsafe { |
96 | 1.10M | match self.kind { |
97 | 1.10M | Kind::Sse => calculate(self.state, buf), |
98 | | #[cfg(stable_vpclmulqdq)] |
99 | 0 | Kind::Avx2 => calculate_avx2(self.state, buf), |
100 | | #[cfg(stable_vpclmulqdq)] |
101 | 0 | Kind::Avx512 => calculate_avx512(self.state, buf), |
102 | | } |
103 | | } |
104 | 1.10M | } |
105 | | |
106 | 169k | pub fn finalize(self) -> u32 { |
107 | 169k | self.state |
108 | 169k | } |
109 | | |
110 | 179k | pub fn reset(&mut self) { |
111 | 179k | self.state = 0; |
112 | 179k | } |
113 | | |
114 | 0 | pub fn combine(&mut self, other: u32, amount: u64) { |
115 | 0 | self.state = crate::combine::combine(self.state, other, amount); |
116 | 0 | } |
117 | | } |
118 | | |
119 | | const K1: i64 = 0x154442bd4; |
120 | | const K2: i64 = 0x1c6e41596; |
121 | | const K3: i64 = 0x1751997d0; |
122 | | const K4: i64 = 0x0ccaa009e; |
123 | | const K5: i64 = 0x163cd6124; |
124 | | |
125 | | const P_X: i64 = 0x1DB710641; |
126 | | const U_PRIME: i64 = 0x1F7011641; |
127 | | |
128 | | // The wider kernels have progressively more streams to initialize and collapse. Keep their |
129 | | // crossovers conservative so short inputs do not pay that fixed cost. |
130 | | const MIN_FOLD_BY_4_BYTES: usize = 128; |
131 | | #[cfg(stable_vpclmulqdq)] |
132 | | const MIN_AVX512_BYTES: usize = 2 * 1024; |
133 | | |
134 | | // Fold constants for the wider strides. For a fold by `D` bits the pair is |
135 | | // `(reflect(x^(D+32) mod P), reflect(x^(D-32) mod P))`, as for `K1`/`K2` (D = 512) and |
136 | | // `K3`/`K4` (D = 128). AVX2 uses 8 streams (D = 1024), AVX-512 uses 16 streams (D = 2048). |
137 | | #[cfg(stable_vpclmulqdq)] |
138 | | const K_1024_LOW: i64 = 0x1e88ef372; |
139 | | #[cfg(stable_vpclmulqdq)] |
140 | | const K_1024_HIGH: i64 = 0x14a7fe880; |
141 | | #[cfg(stable_vpclmulqdq)] |
142 | | const K_2048_LOW: i64 = 0x11542778a; |
143 | | #[cfg(stable_vpclmulqdq)] |
144 | | const K_2048_HIGH: i64 = 0x1322d1430; |
145 | | |
146 | | #[target_feature( |
147 | | enable = "pclmulqdq", |
148 | | enable = "sse2", |
149 | | enable = "sse4.1", |
150 | | enable = "ssse3" |
151 | | )] |
152 | 1.10M | unsafe fn calculate(crc: u32, mut data: &[u8]) -> u32 { |
153 | | // Below 16 bytes there isn't even a single full block to load, so use the scalar fallback. |
154 | 1.10M | if data.len() < 16 { |
155 | 937k | return crate::baseline::update_fast_16(crc, data); |
156 | 162k | } |
157 | | |
158 | | // For 16..127 bytes a single-accumulator fold-by-1 is enough; the fold-by-4 setup below only |
159 | | // pays off once there are several 64-byte groups. |
160 | 162k | if data.len() < MIN_FOLD_BY_4_BYTES { |
161 | 78.7k | let mut x = get(&mut data); |
162 | 78.7k | x = arch::_mm_xor_si128(x, arch::_mm_cvtsi32_si128(!crc as i32)); |
163 | 78.7k | return reduce_128_to_crc(x, data); |
164 | 83.4k | } |
165 | | |
166 | | // Step 1: fold by 4 loop |
167 | 83.4k | let mut x3 = get(&mut data); |
168 | 83.4k | let mut x2 = get(&mut data); |
169 | 83.4k | let mut x1 = get(&mut data); |
170 | 83.4k | let mut x0 = get(&mut data); |
171 | | |
172 | | // fold in our initial value, part of the incremental crc checksum |
173 | 83.4k | x3 = arch::_mm_xor_si128(x3, arch::_mm_cvtsi32_si128(!crc as i32)); |
174 | | |
175 | 83.4k | let k1k2 = arch::_mm_set_epi64x(K2, K1); |
176 | 1.07M | while data.len() >= 64 { |
177 | 996k | x3 = reduce128(x3, get(&mut data), k1k2); |
178 | 996k | x2 = reduce128(x2, get(&mut data), k1k2); |
179 | 996k | x1 = reduce128(x1, get(&mut data), k1k2); |
180 | 996k | x0 = reduce128(x0, get(&mut data), k1k2); |
181 | 996k | } |
182 | | |
183 | 83.4k | let k3k4 = arch::_mm_set_epi64x(K4, K3); |
184 | 83.4k | let mut x = reduce128(x3, x2, k3k4); |
185 | 83.4k | x = reduce128(x, x1, k3k4); |
186 | 83.4k | x = reduce128(x, x0, k3k4); |
187 | | |
188 | 83.4k | reduce_128_to_crc(x, data) |
189 | 1.10M | } |
190 | | |
191 | | /// 256-bit `VPCLMULQDQ` variant: 8 streams across four `YMM` registers (two lanes each), folding |
192 | | /// two streams per carry-less multiply. |
193 | | #[cfg(stable_vpclmulqdq)] |
194 | | #[allow(clippy::incompatible_msrv)] // intrinsics are gated to rustc >= 1.89 by build.rs |
195 | | #[target_feature( |
196 | | enable = "pclmulqdq", |
197 | | enable = "sse2", |
198 | | enable = "sse4.1", |
199 | | enable = "ssse3", |
200 | | enable = "avx", |
201 | | enable = "avx2", |
202 | | enable = "vpclmulqdq" |
203 | | )] |
204 | 0 | unsafe fn calculate_avx2(crc: u32, mut data: &[u8]) -> u32 { |
205 | | // Too small for the wide loop; use the 128-bit path. |
206 | 0 | if data.len() < 256 { |
207 | 0 | return calculate(crc, data); |
208 | 0 | } |
209 | | |
210 | | // First 128 bytes as 8 streams (four YMM registers, two lanes each). |
211 | 0 | let mut v0 = get256(&mut data); |
212 | 0 | let mut v1 = get256(&mut data); |
213 | 0 | let mut v2 = get256(&mut data); |
214 | 0 | let mut v3 = get256(&mut data); |
215 | | |
216 | | // Fold the initial CRC into the lowest-offset stream. |
217 | 0 | v0 = arch::_mm256_xor_si256( |
218 | 0 | v0, |
219 | 0 | arch::_mm256_castsi128_si256(arch::_mm_cvtsi32_si128(!crc as i32)), |
220 | 0 | ); |
221 | | |
222 | 0 | let k = arch::_mm256_set_epi64x(K_1024_HIGH, K_1024_LOW, K_1024_HIGH, K_1024_LOW); |
223 | 0 | while data.len() >= 128 { |
224 | 0 | v0 = reduce256(v0, get256(&mut data), k); |
225 | 0 | v1 = reduce256(v1, get256(&mut data), k); |
226 | 0 | v2 = reduce256(v2, get256(&mut data), k); |
227 | 0 | v3 = reduce256(v3, get256(&mut data), k); |
228 | 0 | } |
229 | | |
230 | | // Collapse the 8 streams, in increasing byte offset, folding each into the next by 128 bits. |
231 | 0 | let k3k4 = arch::_mm_set_epi64x(K4, K3); |
232 | 0 | let mut x = arch::_mm256_castsi256_si128(v0); |
233 | 0 | x = reduce128(x, arch::_mm256_extracti128_si256(v0, 1), k3k4); |
234 | 0 | x = reduce128(x, arch::_mm256_castsi256_si128(v1), k3k4); |
235 | 0 | x = reduce128(x, arch::_mm256_extracti128_si256(v1, 1), k3k4); |
236 | 0 | x = reduce128(x, arch::_mm256_castsi256_si128(v2), k3k4); |
237 | 0 | x = reduce128(x, arch::_mm256_extracti128_si256(v2, 1), k3k4); |
238 | 0 | x = reduce128(x, arch::_mm256_castsi256_si128(v3), k3k4); |
239 | 0 | x = reduce128(x, arch::_mm256_extracti128_si256(v3, 1), k3k4); |
240 | | |
241 | 0 | reduce_128_to_crc(x, data) |
242 | 0 | } |
243 | | |
244 | | /// 512-bit `VPCLMULQDQ` variant: 16 streams across four `ZMM` registers (four lanes each), folding |
245 | | /// four streams per carry-less multiply. |
246 | | #[cfg(stable_vpclmulqdq)] |
247 | | #[allow(clippy::incompatible_msrv)] // intrinsics are gated to rustc >= 1.89 by build.rs |
248 | | #[target_feature( |
249 | | enable = "pclmulqdq", |
250 | | enable = "sse2", |
251 | | enable = "sse4.1", |
252 | | enable = "ssse3", |
253 | | enable = "avx", |
254 | | enable = "avx2", |
255 | | enable = "vpclmulqdq", |
256 | | enable = "avx512f" |
257 | | )] |
258 | 0 | unsafe fn calculate_avx512(crc: u32, mut data: &[u8]) -> u32 { |
259 | | // Use a conservative crossover because the 16-stream setup and collapse are costly near 1 KiB. |
260 | 0 | if data.len() < MIN_AVX512_BYTES { |
261 | 0 | return calculate_avx2(crc, data); |
262 | 0 | } |
263 | | |
264 | | // First 256 bytes as 16 streams (four ZMM registers, four lanes each). |
265 | 0 | let mut v0 = get512(&mut data); |
266 | 0 | let mut v1 = get512(&mut data); |
267 | 0 | let mut v2 = get512(&mut data); |
268 | 0 | let mut v3 = get512(&mut data); |
269 | | |
270 | | // Fold the initial CRC into the lowest-offset stream. |
271 | 0 | v0 = arch::_mm512_xor_si512( |
272 | 0 | v0, |
273 | 0 | arch::_mm512_castsi128_si512(arch::_mm_cvtsi32_si128(!crc as i32)), |
274 | 0 | ); |
275 | | |
276 | 0 | let k = arch::_mm512_set_epi64( |
277 | | K_2048_HIGH, |
278 | | K_2048_LOW, |
279 | | K_2048_HIGH, |
280 | | K_2048_LOW, |
281 | | K_2048_HIGH, |
282 | | K_2048_LOW, |
283 | | K_2048_HIGH, |
284 | | K_2048_LOW, |
285 | | ); |
286 | 0 | while data.len() >= 256 { |
287 | 0 | v0 = reduce512(v0, get512(&mut data), k); |
288 | 0 | v1 = reduce512(v1, get512(&mut data), k); |
289 | 0 | v2 = reduce512(v2, get512(&mut data), k); |
290 | 0 | v3 = reduce512(v3, get512(&mut data), k); |
291 | 0 | } |
292 | | |
293 | | // Collapse the 16 streams, in increasing byte offset, folding each into the next by 128 bits. |
294 | 0 | let k3k4 = arch::_mm_set_epi64x(K4, K3); |
295 | 0 | let mut x = arch::_mm512_castsi512_si128(v0); |
296 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 1), k3k4); |
297 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 2), k3k4); |
298 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v0, 3), k3k4); |
299 | 0 | x = reduce128(x, arch::_mm512_castsi512_si128(v1), k3k4); |
300 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 1), k3k4); |
301 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 2), k3k4); |
302 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v1, 3), k3k4); |
303 | 0 | x = reduce128(x, arch::_mm512_castsi512_si128(v2), k3k4); |
304 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 1), k3k4); |
305 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 2), k3k4); |
306 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v2, 3), k3k4); |
307 | 0 | x = reduce128(x, arch::_mm512_castsi512_si128(v3), k3k4); |
308 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 1), k3k4); |
309 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 2), k3k4); |
310 | 0 | x = reduce128(x, arch::_mm512_extracti32x4_epi32(v3, 3), k3k4); |
311 | | |
312 | | // A substantial remainder is faster through SSE's complete fold-by-4 kernel than through the |
313 | | // serial fold-by-1 tail below. Finalizing and restarting is valid because CRC updates compose. |
314 | 0 | if data.len() >= MIN_FOLD_BY_4_BYTES { |
315 | 0 | let crc = reduce_128_to_crc(x, &[]); |
316 | 0 | return calculate(crc, data); |
317 | 0 | } |
318 | | |
319 | 0 | reduce_128_to_crc(x, data) |
320 | 0 | } |
321 | | |
322 | | /// Folds any remaining 16-byte chunks into `x`, then folds a final partial (`< 16` byte) block |
323 | | /// with a byte-shift, reduces from 128 to 32 bits with a Barrett reduction, and returns the CRC. |
324 | | /// Shared by all of the fold implementations. |
325 | | #[target_feature( |
326 | | enable = "pclmulqdq", |
327 | | enable = "sse2", |
328 | | enable = "sse4.1", |
329 | | enable = "ssse3" |
330 | | )] |
331 | 162k | unsafe fn reduce_128_to_crc(mut x: arch::__m128i, mut data: &[u8]) -> u32 { |
332 | 162k | let k3k4 = arch::_mm_set_epi64x(K4, K3); |
333 | | |
334 | | // Fold by 1 over any remaining whole 16-byte blocks. |
335 | 383k | while data.len() >= 16 { |
336 | 220k | x = reduce128(x, get(&mut data), k3k4); |
337 | 220k | } |
338 | | |
339 | | // Fold a final partial block of `n` (1..=15) bytes. The last `n` bytes of the accumulator are |
340 | | // shifted out (`overflow`) and folded back by 128 bits, while `x` is shifted down to make room |
341 | | // for the `n` new bytes, which are byte-aligned into the vacated high lanes. The shuffle masks |
342 | | // are built at runtime from the byte length, avoiding a lookup table. |
343 | 162k | let n = data.len(); |
344 | 162k | if n > 0 { |
345 | 130k | let seq = arch::_mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); |
346 | 130k | let shl = arch::_mm_add_epi8(seq, arch::_mm_set1_epi8(n as i8 - 16)); |
347 | 130k | let shr = arch::_mm_xor_si128(shl, arch::_mm_set1_epi8(-128)); |
348 | 130k | |
349 | 130k | let overflow = arch::_mm_shuffle_epi8(x, shl); |
350 | 130k | x = arch::_mm_shuffle_epi8(x, shr); |
351 | 130k | |
352 | 130k | let mut part = [0u8; 16]; |
353 | 130k | part[..n].copy_from_slice(data); |
354 | 130k | let part = arch::_mm_loadu_si128(part.as_ptr() as *const arch::__m128i); |
355 | 130k | x = arch::_mm_xor_si128(x, arch::_mm_shuffle_epi8(part, shl)); |
356 | 130k | |
357 | 130k | x = reduce128(overflow, x, k3k4); |
358 | 130k | } |
359 | | |
360 | | // Perform step 3, reduction from 128 bits to 64 bits. This is |
361 | | // significantly different from the paper and basically doesn't follow it |
362 | | // at all. It's not really clear why, but implementations of this algorithm |
363 | | // in Chrome/Linux diverge in the same way. It is beyond me why this is |
364 | | // different than the paper, maybe the paper has like errata or something? |
365 | | // Unclear. |
366 | | // |
367 | | // It's also not clear to me what's actually happening here and/or why, but |
368 | | // algebraically what's happening is: |
369 | | // |
370 | | // x = (x[0:63] • K4) ^ x[64:127] // 96 bit result |
371 | | // x = ((x[0:31] as u64) • K5) ^ x[32:95] // 64 bit result |
372 | | // |
373 | | // It's... not clear to me what's going on here. The paper itself is pretty |
374 | | // vague on this part but definitely uses different constants at least. |
375 | | // It's not clear to me, reading the paper, where the xor operations are |
376 | | // happening or why things are shifting around. This implementation... |
377 | | // appears to work though! |
378 | 162k | let x = arch::_mm_xor_si128( |
379 | 162k | arch::_mm_clmulepi64_si128(x, k3k4, 0x10), |
380 | 162k | arch::_mm_srli_si128(x, 8), |
381 | | ); |
382 | 162k | let x = arch::_mm_xor_si128( |
383 | 162k | arch::_mm_clmulepi64_si128( |
384 | 162k | arch::_mm_and_si128(x, arch::_mm_set_epi32(0, 0, 0, !0)), |
385 | 162k | arch::_mm_set_epi64x(0, K5), |
386 | | 0x00, |
387 | | ), |
388 | 162k | arch::_mm_srli_si128(x, 4), |
389 | | ); |
390 | | |
391 | | // Perform a Barrett reduction from our now 64 bits to 32 bits. The |
392 | | // algorithm for this is described at the end of the paper, and note that |
393 | | // this also implements the "bit reflected input" variant. |
394 | 162k | let pu = arch::_mm_set_epi64x(U_PRIME, P_X); |
395 | | |
396 | | // T1(x) = ⌊(R(x) % x^32)⌋ • μ |
397 | 162k | let t1 = arch::_mm_clmulepi64_si128( |
398 | 162k | arch::_mm_and_si128(x, arch::_mm_set_epi32(0, 0, 0, !0)), |
399 | 162k | pu, |
400 | | 0x10, |
401 | | ); |
402 | | // T2(x) = ⌊(T1(x) % x^32)⌋ • P(x) |
403 | 162k | let t2 = arch::_mm_clmulepi64_si128( |
404 | 162k | arch::_mm_and_si128(t1, arch::_mm_set_epi32(0, 0, 0, !0)), |
405 | 162k | pu, |
406 | | 0x00, |
407 | | ); |
408 | | // We're doing the bit-reflected variant, so get the upper 32-bits of the |
409 | | // 64-bit result instead of the lower 32-bits. |
410 | | // |
411 | | // C(x) = R(x) ^ T2(x) / x^32 |
412 | 162k | !(arch::_mm_extract_epi32(arch::_mm_xor_si128(x, t2), 1) as u32) |
413 | 162k | } |
414 | | |
415 | | #[inline] |
416 | 4.58M | unsafe fn reduce128(a: arch::__m128i, b: arch::__m128i, keys: arch::__m128i) -> arch::__m128i { |
417 | 4.58M | let t1 = arch::_mm_clmulepi64_si128(a, keys, 0x00); |
418 | 4.58M | let t2 = arch::_mm_clmulepi64_si128(a, keys, 0x11); |
419 | 4.58M | arch::_mm_xor_si128(arch::_mm_xor_si128(b, t1), t2) |
420 | 4.58M | } |
421 | | |
422 | | #[cfg(stable_vpclmulqdq)] |
423 | | #[allow(clippy::incompatible_msrv)] // intrinsics are gated to rustc >= 1.89 by build.rs |
424 | | #[target_feature(enable = "avx2", enable = "vpclmulqdq")] |
425 | | #[inline] |
426 | 0 | unsafe fn reduce256(a: arch::__m256i, b: arch::__m256i, keys: arch::__m256i) -> arch::__m256i { |
427 | 0 | let t1 = arch::_mm256_clmulepi64_epi128(a, keys, 0x00); |
428 | 0 | let t2 = arch::_mm256_clmulepi64_epi128(a, keys, 0x11); |
429 | 0 | arch::_mm256_xor_si256(arch::_mm256_xor_si256(b, t1), t2) |
430 | 0 | } |
431 | | |
432 | | #[cfg(stable_vpclmulqdq)] |
433 | | #[allow(clippy::incompatible_msrv)] // intrinsics are gated to rustc >= 1.89 by build.rs |
434 | | #[target_feature(enable = "avx512f", enable = "vpclmulqdq")] |
435 | | #[inline] |
436 | 0 | unsafe fn reduce512(a: arch::__m512i, b: arch::__m512i, keys: arch::__m512i) -> arch::__m512i { |
437 | 0 | let t1 = arch::_mm512_clmulepi64_epi128(a, keys, 0x00); |
438 | 0 | let t2 = arch::_mm512_clmulepi64_epi128(a, keys, 0x11); |
439 | 0 | arch::_mm512_xor_si512(arch::_mm512_xor_si512(b, t1), t2) |
440 | 0 | } |
441 | | |
442 | 4.61M | unsafe fn get(a: &mut &[u8]) -> arch::__m128i { |
443 | 4.61M | debug_assert!(a.len() >= 16); |
444 | 4.61M | let r = arch::_mm_loadu_si128(a.as_ptr() as *const arch::__m128i); |
445 | 4.61M | *a = &a[16..]; |
446 | 4.61M | r |
447 | 4.61M | } |
448 | | |
449 | | #[cfg(stable_vpclmulqdq)] |
450 | | #[target_feature(enable = "avx")] |
451 | | #[inline] |
452 | 0 | unsafe fn get256(a: &mut &[u8]) -> arch::__m256i { |
453 | 0 | debug_assert!(a.len() >= 32); |
454 | 0 | let r = arch::_mm256_loadu_si256(a.as_ptr() as *const arch::__m256i); |
455 | 0 | *a = &a[32..]; |
456 | 0 | r |
457 | 0 | } |
458 | | |
459 | | #[cfg(stable_vpclmulqdq)] |
460 | | #[allow(clippy::incompatible_msrv)] // intrinsics are gated to rustc >= 1.89 by build.rs |
461 | | #[target_feature(enable = "avx512f")] |
462 | | #[inline] |
463 | 0 | unsafe fn get512(a: &mut &[u8]) -> arch::__m512i { |
464 | 0 | debug_assert!(a.len() >= 64); |
465 | 0 | let r = arch::_mm512_loadu_si512(a.as_ptr() as *const _); |
466 | 0 | *a = &a[64..]; |
467 | 0 | r |
468 | 0 | } |
469 | | |
470 | | #[cfg(test)] |
471 | | mod test { |
472 | | quickcheck::quickcheck! { |
473 | | fn check_against_baseline(init: u32, chunks: Vec<(Vec<u8>, usize)>) -> bool { |
474 | | let mut baseline = super::super::super::baseline::State::new(init); |
475 | | let mut pclmulqdq = super::State::new(init).expect("not supported"); |
476 | | for (chunk, mut offset) in chunks { |
477 | | // simulate random alignments by offsetting the slice by up to 15 bytes |
478 | | offset &= 0xF; |
479 | | if chunk.len() <= offset { |
480 | | baseline.update(&chunk); |
481 | | pclmulqdq.update(&chunk); |
482 | | } else { |
483 | | baseline.update(&chunk[offset..]); |
484 | | pclmulqdq.update(&chunk[offset..]); |
485 | | } |
486 | | } |
487 | | pclmulqdq.finalize() == baseline.finalize() |
488 | | } |
489 | | } |
490 | | |
491 | | // Exercises the wide fold paths across the sizes and alignments where the strategy switches. |
492 | | #[test] |
493 | | fn check_large_inputs_against_baseline() { |
494 | | let mut data = vec![0u8; 8200]; |
495 | | let mut s: u32 = 0x1234_5678; |
496 | | for b in data.iter_mut() { |
497 | | s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); |
498 | | *b = (s >> 24) as u8; |
499 | | } |
500 | | |
501 | | for &len in &[ |
502 | | 0usize, 1, 15, 16, 17, 63, 64, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, |
503 | | 1025, 2047, 2048, 2049, 2175, 2176, 2303, 2304, 4096, 8192, 8199, |
504 | | ] { |
505 | | for &offset in &[0usize, 1, 3, 7, 8, 15] { |
506 | | if offset + len > data.len() { |
507 | | continue; |
508 | | } |
509 | | let slice = &data[offset..offset + len]; |
510 | | let mut baseline = super::super::super::baseline::State::new(0); |
511 | | baseline.update(slice); |
512 | | let mut specialized = super::State::new(0).expect("not supported"); |
513 | | specialized.update(slice); |
514 | | assert_eq!( |
515 | | specialized.finalize(), |
516 | | baseline.finalize(), |
517 | | "mismatch for len={len} offset={offset}", |
518 | | ); |
519 | | } |
520 | | } |
521 | | } |
522 | | } |