/work/libdeflate-src/lib/x86/crc32_pclmul_template.h
Line | Count | Source |
1 | | /* |
2 | | * x86/crc32_pclmul_template.h - gzip CRC-32 with PCLMULQDQ instructions |
3 | | * |
4 | | * Copyright 2016 Eric Biggers |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person |
7 | | * obtaining a copy of this software and associated documentation |
8 | | * files (the "Software"), to deal in the Software without |
9 | | * restriction, including without limitation the rights to use, |
10 | | * copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | | * copies of the Software, and to permit persons to whom the |
12 | | * Software is furnished to do so, subject to the following |
13 | | * conditions: |
14 | | * |
15 | | * The above copyright notice and this permission notice shall be |
16 | | * included in all copies or substantial portions of the Software. |
17 | | * |
18 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
19 | | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
20 | | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
21 | | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
22 | | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
23 | | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
24 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
25 | | * OTHER DEALINGS IN THE SOFTWARE. |
26 | | */ |
27 | | |
28 | | /* |
29 | | * This file is a "template" for instantiating PCLMULQDQ-based crc32_x86 |
30 | | * functions. The "parameters" are: |
31 | | * |
32 | | * SUFFIX: |
33 | | * Name suffix to append to all instantiated functions. |
34 | | * ATTRIBUTES: |
35 | | * Target function attributes to use. Must satisfy the dependencies of the |
36 | | * other parameters as follows: |
37 | | * VL=16 && USE_AVX512=0: at least pclmul,sse4.1 |
38 | | * VL=32 && USE_AVX512=0: at least vpclmulqdq,pclmul,avx2 |
39 | | * VL=32 && USE_AVX512=1: at least vpclmulqdq,pclmul,avx512bw,avx512vl |
40 | | * VL=64 && USE_AVX512=1: at least vpclmulqdq,pclmul,avx512bw,avx512vl |
41 | | * (Other combinations are not useful and have not been tested.) |
42 | | * VL: |
43 | | * Vector length in bytes. Must be 16, 32, or 64. |
44 | | * USE_AVX512: |
45 | | * If 1, take advantage of AVX-512 features such as masking and the |
46 | | * vpternlog instruction. This doesn't enable the use of 512-bit vectors; |
47 | | * the vector length is controlled by VL. If 0, assume that the CPU might |
48 | | * not support AVX-512. |
49 | | * |
50 | | * The overall algorithm used is CRC folding with carryless multiplication |
51 | | * instructions. Note that the x86 crc32 instruction cannot be used, as it is |
52 | | * for a different polynomial, not the gzip one. For an explanation of CRC |
53 | | * folding with carryless multiplication instructions, see |
54 | | * scripts/gen-crc32-consts.py and the following blog posts and papers: |
55 | | * |
56 | | * "An alternative exposition of crc32_4k_pclmulqdq" |
57 | | * https://www.corsix.org/content/alternative-exposition-crc32_4k_pclmulqdq |
58 | | * |
59 | | * "Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction" |
60 | | * https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf |
61 | | * |
62 | | * The original pclmulqdq instruction does one 64x64 to 128-bit carryless |
63 | | * multiplication. The VPCLMULQDQ feature added instructions that do two |
64 | | * parallel 64x64 to 128-bit carryless multiplications in combination with AVX |
65 | | * or AVX512VL, or four in combination with AVX512F. |
66 | | */ |
67 | | |
68 | | #if VL == 16 |
69 | 216 | # define vec_t __m128i |
70 | 1.44M | # define fold_vec fold_vec128 |
71 | 1.44M | # define VLOADU(p) _mm_loadu_si128((const void *)(p)) |
72 | 118 | # define VXOR(a, b) _mm_xor_si128((a), (b)) |
73 | 0 | # define M128I_TO_VEC(a) a |
74 | 216 | # define MULTS_8V _mm_set_epi64x(CRC32_X991_MODG, CRC32_X1055_MODG) |
75 | 216 | # define MULTS_4V _mm_set_epi64x(CRC32_X479_MODG, CRC32_X543_MODG) |
76 | 216 | # define MULTS_2V _mm_set_epi64x(CRC32_X223_MODG, CRC32_X287_MODG) |
77 | 216 | # define MULTS_1V _mm_set_epi64x(CRC32_X95_MODG, CRC32_X159_MODG) |
78 | | #elif VL == 32 |
79 | 0 | # define vec_t __m256i |
80 | 0 | # define fold_vec fold_vec256 |
81 | 0 | # define VLOADU(p) _mm256_loadu_si256((const void *)(p)) |
82 | 0 | # define VXOR(a, b) _mm256_xor_si256((a), (b)) |
83 | 0 | # define M128I_TO_VEC(a) _mm256_zextsi128_si256(a) |
84 | 0 | # define MULTS(a, b) _mm256_set_epi64x(a, b, a, b) |
85 | 0 | # define MULTS_8V MULTS(CRC32_X2015_MODG, CRC32_X2079_MODG) |
86 | 0 | # define MULTS_4V MULTS(CRC32_X991_MODG, CRC32_X1055_MODG) |
87 | 0 | # define MULTS_2V MULTS(CRC32_X479_MODG, CRC32_X543_MODG) |
88 | 0 | # define MULTS_1V MULTS(CRC32_X223_MODG, CRC32_X287_MODG) |
89 | | #elif VL == 64 |
90 | 0 | # define vec_t __m512i |
91 | 0 | # define fold_vec fold_vec512 |
92 | 0 | # define VLOADU(p) _mm512_loadu_si512((const void *)(p)) |
93 | 0 | # define VXOR(a, b) _mm512_xor_si512((a), (b)) |
94 | 0 | # define M128I_TO_VEC(a) _mm512_zextsi128_si512(a) |
95 | 0 | # define MULTS(a, b) _mm512_set_epi64(a, b, a, b, a, b, a, b) |
96 | 0 | # define MULTS_8V MULTS(CRC32_X4063_MODG, CRC32_X4127_MODG) |
97 | 0 | # define MULTS_4V MULTS(CRC32_X2015_MODG, CRC32_X2079_MODG) |
98 | 0 | # define MULTS_2V MULTS(CRC32_X991_MODG, CRC32_X1055_MODG) |
99 | 0 | # define MULTS_1V MULTS(CRC32_X479_MODG, CRC32_X543_MODG) |
100 | | #else |
101 | | # error "unsupported vector length" |
102 | | #endif |
103 | | |
104 | | #undef fold_vec128 |
105 | | static forceinline ATTRIBUTES __m128i |
106 | | ADD_SUFFIX(fold_vec128)(__m128i src, __m128i dst, __m128i /* __v2du */ mults) |
107 | 1.44M | { |
108 | 1.44M | dst = _mm_xor_si128(dst, _mm_clmulepi64_si128(src, mults, 0x00)); |
109 | 1.44M | dst = _mm_xor_si128(dst, _mm_clmulepi64_si128(src, mults, 0x11)); |
110 | 1.44M | return dst; |
111 | 1.44M | } Unexecuted instantiation: crc32.c:fold_vec128_vpclmulqdq_avx512_vl512 Unexecuted instantiation: crc32.c:fold_vec128_vpclmulqdq_avx512_vl256 Unexecuted instantiation: crc32.c:fold_vec128_vpclmulqdq_avx2 crc32.c:fold_vec128_pclmulqdq_avx Line | Count | Source | 107 | 1.44M | { | 108 | 1.44M | dst = _mm_xor_si128(dst, _mm_clmulepi64_si128(src, mults, 0x00)); | 109 | | dst = _mm_xor_si128(dst, _mm_clmulepi64_si128(src, mults, 0x11)); | 110 | 1.44M | return dst; | 111 | 1.44M | } |
Unexecuted instantiation: crc32.c:fold_vec128_pclmulqdq |
112 | 1.44M | #define fold_vec128 ADD_SUFFIX(fold_vec128) |
113 | | |
114 | | #if VL >= 32 |
115 | | #undef fold_vec256 |
116 | | static forceinline ATTRIBUTES __m256i |
117 | | ADD_SUFFIX(fold_vec256)(__m256i src, __m256i dst, __m256i /* __v4du */ mults) |
118 | 0 | { |
119 | | #if USE_AVX512 |
120 | | /* vpternlog with immediate 0x96 is a three-argument XOR. */ |
121 | | return _mm256_ternarylogic_epi32( |
122 | | _mm256_clmulepi64_epi128(src, mults, 0x00), |
123 | | _mm256_clmulepi64_epi128(src, mults, 0x11), |
124 | | dst, |
125 | | 0x96); |
126 | | #else |
127 | | return _mm256_xor_si256( |
128 | | _mm256_xor_si256(dst, |
129 | | _mm256_clmulepi64_epi128(src, mults, 0x00)), |
130 | | _mm256_clmulepi64_epi128(src, mults, 0x11)); |
131 | | #endif |
132 | 0 | } Unexecuted instantiation: crc32.c:fold_vec256_vpclmulqdq_avx512_vl512 Unexecuted instantiation: crc32.c:fold_vec256_vpclmulqdq_avx512_vl256 Unexecuted instantiation: crc32.c:fold_vec256_vpclmulqdq_avx2 |
133 | 0 | #define fold_vec256 ADD_SUFFIX(fold_vec256) |
134 | | #endif /* VL >= 32 */ |
135 | | |
136 | | #if VL >= 64 |
137 | | #undef fold_vec512 |
138 | | static forceinline ATTRIBUTES __m512i |
139 | | ADD_SUFFIX(fold_vec512)(__m512i src, __m512i dst, __m512i /* __v8du */ mults) |
140 | 0 | { |
141 | | /* vpternlog with immediate 0x96 is a three-argument XOR. */ |
142 | 0 | return _mm512_ternarylogic_epi32( |
143 | 0 | _mm512_clmulepi64_epi128(src, mults, 0x00), |
144 | 0 | _mm512_clmulepi64_epi128(src, mults, 0x11), |
145 | 0 | dst, |
146 | 0 | 0x96); |
147 | 0 | } |
148 | 0 | #define fold_vec512 ADD_SUFFIX(fold_vec512) |
149 | | #endif /* VL >= 64 */ |
150 | | |
151 | | /* |
152 | | * Given 'x' containing a 16-byte polynomial, and a pointer 'p' that points to |
153 | | * the next '1 <= len <= 15' data bytes, rearrange the concatenation of 'x' and |
154 | | * the data into vectors x0 and x1 that contain 'len' bytes and 16 bytes, |
155 | | * respectively. Then fold x0 into x1 and return the result. |
156 | | * Assumes that 'p + len - 16' is in-bounds. |
157 | | */ |
158 | | #undef fold_lessthan16bytes |
159 | | static forceinline ATTRIBUTES __m128i |
160 | | ADD_SUFFIX(fold_lessthan16bytes)(__m128i x, const u8 *p, size_t len, |
161 | | __m128i /* __v2du */ mults_128b) |
162 | 103 | { |
163 | 103 | __m128i lshift = _mm_loadu_si128((const void *)&shift_tab[len]); |
164 | 103 | __m128i rshift = _mm_loadu_si128((const void *)&shift_tab[len + 16]); |
165 | 103 | __m128i x0, x1; |
166 | | |
167 | | /* x0 = x left-shifted by '16 - len' bytes */ |
168 | 103 | x0 = _mm_shuffle_epi8(x, lshift); |
169 | | |
170 | | /* |
171 | | * x1 = the last '16 - len' bytes from x (i.e. x right-shifted by 'len' |
172 | | * bytes) followed by the remaining data. |
173 | | */ |
174 | 103 | x1 = _mm_blendv_epi8(_mm_shuffle_epi8(x, rshift), |
175 | 103 | _mm_loadu_si128((const void *)(p + len - 16)), |
176 | | /* msb 0/1 of each byte selects byte from arg1/2 */ |
177 | 103 | rshift); |
178 | | |
179 | 103 | return fold_vec128(x0, x1, mults_128b); |
180 | 103 | } Unexecuted instantiation: crc32.c:fold_lessthan16bytes_vpclmulqdq_avx512_vl512 Unexecuted instantiation: crc32.c:fold_lessthan16bytes_vpclmulqdq_avx512_vl256 Unexecuted instantiation: crc32.c:fold_lessthan16bytes_vpclmulqdq_avx2 crc32.c:fold_lessthan16bytes_pclmulqdq_avx Line | Count | Source | 162 | 103 | { | 163 | 103 | __m128i lshift = _mm_loadu_si128((const void *)&shift_tab[len]); | 164 | 103 | __m128i rshift = _mm_loadu_si128((const void *)&shift_tab[len + 16]); | 165 | 103 | __m128i x0, x1; | 166 | | | 167 | | /* x0 = x left-shifted by '16 - len' bytes */ | 168 | 103 | x0 = _mm_shuffle_epi8(x, lshift); | 169 | | | 170 | | /* | 171 | | * x1 = the last '16 - len' bytes from x (i.e. x right-shifted by 'len' | 172 | | * bytes) followed by the remaining data. | 173 | | */ | 174 | 103 | x1 = _mm_blendv_epi8(_mm_shuffle_epi8(x, rshift), | 175 | 103 | _mm_loadu_si128((const void *)(p + len - 16)), | 176 | | /* msb 0/1 of each byte selects byte from arg1/2 */ | 177 | 103 | rshift); | 178 | | | 179 | 103 | return fold_vec128(x0, x1, mults_128b); | 180 | 103 | } |
Unexecuted instantiation: crc32.c:fold_lessthan16bytes_pclmulqdq |
181 | 103 | #define fold_lessthan16bytes ADD_SUFFIX(fold_lessthan16bytes) |
182 | | |
183 | | static ATTRIBUTES u32 |
184 | | ADD_SUFFIX(crc32_x86)(u32 crc, const u8 *p, size_t len) |
185 | 216 | { |
186 | | /* |
187 | | * mults_{N}v are the vectors of multipliers for folding across N vec_t |
188 | | * vectors, i.e. N*VL*8 bits. mults_128b are the two multipliers for |
189 | | * folding across 128 bits. mults_128b differs from mults_1v when |
190 | | * VL != 16. All multipliers are 64-bit, to match what pclmulqdq needs, |
191 | | * but since this is for CRC-32 only their low 32 bits are nonzero. |
192 | | * For more details, see scripts/gen-crc32-consts.py. |
193 | | */ |
194 | 216 | const vec_t mults_8v = MULTS_8V; |
195 | 216 | const vec_t mults_4v = MULTS_4V; |
196 | 216 | const vec_t mults_2v = MULTS_2V; |
197 | 216 | const vec_t mults_1v = MULTS_1V; |
198 | 216 | const __m128i mults_128b = _mm_set_epi64x(CRC32_X95_MODG, CRC32_X159_MODG); |
199 | 216 | const __m128i barrett_reduction_constants = |
200 | 216 | _mm_set_epi64x(CRC32_BARRETT_CONSTANT_2, CRC32_BARRETT_CONSTANT_1); |
201 | 216 | vec_t v0, v1, v2, v3, v4, v5, v6, v7; |
202 | 216 | __m128i x0 = _mm_cvtsi32_si128(crc); |
203 | 216 | __m128i x1; |
204 | | |
205 | 216 | if (len < 8*VL) { |
206 | 123 | if (len < VL) { |
207 | 98 | STATIC_ASSERT(VL == 16 || VL == 32 || VL == 64); |
208 | 98 | if (len < 16) { |
209 | | #if USE_AVX512 |
210 | 0 | if (len < 4) |
211 | 0 | return crc32_slice1(crc, p, len); |
212 | | /* |
213 | | * Handle 4 <= len <= 15 bytes by doing a masked |
214 | | * load, XOR'ing the current CRC with the first |
215 | | * 4 bytes, left-shifting by '16 - len' bytes to |
216 | | * align the result to the end of x0 (so that it |
217 | | * becomes the low-order coefficients of a |
218 | | * 128-bit polynomial), and then doing the usual |
219 | | * reduction from 128 bits to 32 bits. |
220 | | */ |
221 | 0 | x0 = _mm_xor_si128( |
222 | 0 | x0, _mm_maskz_loadu_epi8((1 << len) - 1, p)); |
223 | 0 | x0 = _mm_shuffle_epi8( |
224 | 0 | x0, _mm_loadu_si128((const void *)&shift_tab[len])); |
225 | 0 | goto reduce_x0; |
226 | | #else |
227 | | return crc32_slice1(crc, p, len); |
228 | | #endif |
229 | 98 | } |
230 | | /* |
231 | | * Handle 16 <= len < VL bytes where VL is 32 or 64. |
232 | | * Use 128-bit instructions so that these lengths aren't |
233 | | * slower with VL > 16 than with VL=16. |
234 | | */ |
235 | 0 | x0 = _mm_xor_si128(_mm_loadu_si128((const void *)p), x0); |
236 | 0 | if (len >= 32) { |
237 | 0 | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)(p + 16)), |
238 | 0 | mults_128b); |
239 | 0 | if (len >= 48) |
240 | 0 | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)(p + 32)), |
241 | 0 | mults_128b); |
242 | 0 | } |
243 | 0 | p += len & ~15; |
244 | 0 | goto less_than_16_remaining; |
245 | 98 | } |
246 | 25 | v0 = VXOR(VLOADU(p), M128I_TO_VEC(x0)); |
247 | 25 | if (len < 2*VL) { |
248 | 4 | p += VL; |
249 | 4 | goto less_than_vl_remaining; |
250 | 4 | } |
251 | 21 | v1 = VLOADU(p + 1*VL); |
252 | 21 | if (len < 4*VL) { |
253 | 15 | p += 2*VL; |
254 | 15 | goto less_than_2vl_remaining; |
255 | 15 | } |
256 | 6 | v2 = VLOADU(p + 2*VL); |
257 | 6 | v3 = VLOADU(p + 3*VL); |
258 | 6 | p += 4*VL; |
259 | 93 | } else { |
260 | | /* |
261 | | * If the length is large and the pointer is misaligned, align |
262 | | * it. For smaller lengths, just take the misaligned load |
263 | | * penalty. Note that on recent x86 CPUs, vmovdqu with an |
264 | | * aligned address is just as fast as vmovdqa, so there's no |
265 | | * need to use vmovdqa in the main loop. |
266 | | */ |
267 | 93 | if (len > 65536 && ((uintptr_t)p & (VL-1))) { |
268 | 0 | size_t align = -(uintptr_t)p & (VL-1); |
269 | |
|
270 | 0 | len -= align; |
271 | 0 | x0 = _mm_xor_si128(_mm_loadu_si128((const void *)p), x0); |
272 | 0 | p += 16; |
273 | 0 | if (align & 15) { |
274 | 0 | x0 = fold_lessthan16bytes(x0, p, align & 15, |
275 | 0 | mults_128b); |
276 | 0 | p += align & 15; |
277 | 0 | align &= ~15; |
278 | 0 | } |
279 | 0 | while (align) { |
280 | 0 | x0 = fold_vec128(x0, *(const __m128i *)p, |
281 | 0 | mults_128b); |
282 | 0 | p += 16; |
283 | 0 | align -= 16; |
284 | 0 | } |
285 | 0 | v0 = M128I_TO_VEC(x0); |
286 | | # if VL == 32 |
287 | | v0 = _mm256_inserti128_si256(v0, *(const __m128i *)p, 1); |
288 | | # elif VL == 64 |
289 | | v0 = _mm512_inserti32x4(v0, *(const __m128i *)p, 1); |
290 | | v0 = _mm512_inserti64x4(v0, *(const __m256i *)(p + 16), 1); |
291 | | # endif |
292 | 0 | p -= 16; |
293 | 93 | } else { |
294 | 93 | v0 = VXOR(VLOADU(p), M128I_TO_VEC(x0)); |
295 | 93 | } |
296 | 93 | v1 = VLOADU(p + 1*VL); |
297 | 93 | v2 = VLOADU(p + 2*VL); |
298 | 93 | v3 = VLOADU(p + 3*VL); |
299 | 93 | v4 = VLOADU(p + 4*VL); |
300 | 93 | v5 = VLOADU(p + 5*VL); |
301 | 93 | v6 = VLOADU(p + 6*VL); |
302 | 93 | v7 = VLOADU(p + 7*VL); |
303 | 93 | p += 8*VL; |
304 | | |
305 | | /* |
306 | | * This is the main loop, processing 8*VL bytes per iteration. |
307 | | * 4*VL is usually enough and would result in smaller code, but |
308 | | * Skylake and Cascade Lake need 8*VL to get full performance. |
309 | | */ |
310 | 181k | while (len >= 16*VL) { |
311 | 180k | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_8v); |
312 | 180k | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_8v); |
313 | 180k | v2 = fold_vec(v2, VLOADU(p + 2*VL), mults_8v); |
314 | 180k | v3 = fold_vec(v3, VLOADU(p + 3*VL), mults_8v); |
315 | 180k | v4 = fold_vec(v4, VLOADU(p + 4*VL), mults_8v); |
316 | 180k | v5 = fold_vec(v5, VLOADU(p + 5*VL), mults_8v); |
317 | 180k | v6 = fold_vec(v6, VLOADU(p + 6*VL), mults_8v); |
318 | 180k | v7 = fold_vec(v7, VLOADU(p + 7*VL), mults_8v); |
319 | 180k | p += 8*VL; |
320 | 180k | len -= 8*VL; |
321 | 180k | } |
322 | | |
323 | | /* Fewer than 8*VL bytes remain. */ |
324 | 93 | v0 = fold_vec(v0, v4, mults_4v); |
325 | 93 | v1 = fold_vec(v1, v5, mults_4v); |
326 | 93 | v2 = fold_vec(v2, v6, mults_4v); |
327 | 93 | v3 = fold_vec(v3, v7, mults_4v); |
328 | 93 | if (len & (4*VL)) { |
329 | 52 | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_4v); |
330 | 52 | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_4v); |
331 | 52 | v2 = fold_vec(v2, VLOADU(p + 2*VL), mults_4v); |
332 | 52 | v3 = fold_vec(v3, VLOADU(p + 3*VL), mults_4v); |
333 | 52 | p += 4*VL; |
334 | 52 | } |
335 | 93 | } |
336 | | /* Fewer than 4*VL bytes remain. */ |
337 | 99 | v0 = fold_vec(v0, v2, mults_2v); |
338 | 99 | v1 = fold_vec(v1, v3, mults_2v); |
339 | 99 | if (len & (2*VL)) { |
340 | 59 | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_2v); |
341 | 59 | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_2v); |
342 | 59 | p += 2*VL; |
343 | 59 | } |
344 | 114 | less_than_2vl_remaining: |
345 | | /* Fewer than 2*VL bytes remain. */ |
346 | 114 | v0 = fold_vec(v0, v1, mults_1v); |
347 | 114 | if (len & VL) { |
348 | 51 | v0 = fold_vec(v0, VLOADU(p), mults_1v); |
349 | 51 | p += VL; |
350 | 51 | } |
351 | 118 | less_than_vl_remaining: |
352 | | /* |
353 | | * Fewer than VL bytes remain. Reduce v0 (length VL bytes) to x0 |
354 | | * (length 16 bytes) and fold in any 16-byte data segments that remain. |
355 | | */ |
356 | | #if VL == 16 |
357 | | x0 = v0; |
358 | | #else |
359 | | { |
360 | | #if VL == 32 |
361 | | __m256i y0 = v0; |
362 | | #else |
363 | | const __m256i mults_256b = |
364 | 0 | _mm256_set_epi64x(CRC32_X223_MODG, CRC32_X287_MODG, |
365 | 0 | CRC32_X223_MODG, CRC32_X287_MODG); |
366 | 0 | __m256i y0 = fold_vec256(_mm512_extracti64x4_epi64(v0, 0), |
367 | | _mm512_extracti64x4_epi64(v0, 1), |
368 | | mults_256b); |
369 | 0 | if (len & 32) { |
370 | 0 | y0 = fold_vec256(y0, _mm256_loadu_si256((const void *)p), |
371 | 0 | mults_256b); |
372 | 0 | p += 32; |
373 | 0 | } |
374 | | #endif |
375 | 0 | x0 = fold_vec128(_mm256_extracti128_si256(y0, 0), |
376 | | _mm256_extracti128_si256(y0, 1), mults_128b); |
377 | | } |
378 | 0 | if (len & 16) { |
379 | 0 | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)p), |
380 | 0 | mults_128b); |
381 | 0 | p += 16; |
382 | 0 | } |
383 | | #endif |
384 | 118 | less_than_16_remaining: |
385 | 118 | len &= 15; |
386 | | |
387 | | /* Handle any remainder of 1 to 15 bytes. */ |
388 | 118 | if (len) |
389 | 103 | x0 = fold_lessthan16bytes(x0, p, len, mults_128b); |
390 | | #if USE_AVX512 |
391 | 0 | reduce_x0: |
392 | 0 | #endif |
393 | | /* |
394 | | * Multiply the remaining 128-bit message polynomial 'x0' by x^32, then |
395 | | * reduce it modulo the generator polynomial G. This gives the CRC. |
396 | | * |
397 | | * This implementation matches that used in crc-pclmul-template.S from |
398 | | * https://lore.kernel.org/r/20250210174540.161705-4-ebiggers@kernel.org/ |
399 | | * with the parameters n=32 and LSB_CRC=1 (what the gzip CRC uses). See |
400 | | * there for a detailed explanation of the math used here. |
401 | | */ |
402 | 0 | x0 = _mm_xor_si128(_mm_clmulepi64_si128(x0, mults_128b, 0x10), |
403 | 0 | _mm_bsrli_si128(x0, 8)); |
404 | 0 | x1 = _mm_clmulepi64_si128(x0, barrett_reduction_constants, 0x00); |
405 | 0 | x1 = _mm_clmulepi64_si128(x1, barrett_reduction_constants, 0x10); |
406 | 0 | x0 = _mm_xor_si128(x0, x1); |
407 | | return _mm_extract_epi32(x0, 2); |
408 | 0 | } Unexecuted instantiation: crc32.c:crc32_x86_vpclmulqdq_avx512_vl512 Unexecuted instantiation: crc32.c:crc32_x86_vpclmulqdq_avx512_vl256 Unexecuted instantiation: crc32.c:crc32_x86_vpclmulqdq_avx2 crc32.c:crc32_x86_pclmulqdq_avx Line | Count | Source | 185 | 216 | { | 186 | | /* | 187 | | * mults_{N}v are the vectors of multipliers for folding across N vec_t | 188 | | * vectors, i.e. N*VL*8 bits. mults_128b are the two multipliers for | 189 | | * folding across 128 bits. mults_128b differs from mults_1v when | 190 | | * VL != 16. All multipliers are 64-bit, to match what pclmulqdq needs, | 191 | | * but since this is for CRC-32 only their low 32 bits are nonzero. | 192 | | * For more details, see scripts/gen-crc32-consts.py. | 193 | | */ | 194 | 216 | const vec_t mults_8v = MULTS_8V; | 195 | 216 | const vec_t mults_4v = MULTS_4V; | 196 | 216 | const vec_t mults_2v = MULTS_2V; | 197 | 216 | const vec_t mults_1v = MULTS_1V; | 198 | 216 | const __m128i mults_128b = _mm_set_epi64x(CRC32_X95_MODG, CRC32_X159_MODG); | 199 | 216 | const __m128i barrett_reduction_constants = | 200 | 216 | _mm_set_epi64x(CRC32_BARRETT_CONSTANT_2, CRC32_BARRETT_CONSTANT_1); | 201 | 216 | vec_t v0, v1, v2, v3, v4, v5, v6, v7; | 202 | 216 | __m128i x0 = _mm_cvtsi32_si128(crc); | 203 | 216 | __m128i x1; | 204 | | | 205 | 216 | if (len < 8*VL) { | 206 | 123 | if (len < VL) { | 207 | 98 | STATIC_ASSERT(VL == 16 || VL == 32 || VL == 64); | 208 | 98 | if (len < 16) { | 209 | | #if USE_AVX512 | 210 | | if (len < 4) | 211 | | return crc32_slice1(crc, p, len); | 212 | | /* | 213 | | * Handle 4 <= len <= 15 bytes by doing a masked | 214 | | * load, XOR'ing the current CRC with the first | 215 | | * 4 bytes, left-shifting by '16 - len' bytes to | 216 | | * align the result to the end of x0 (so that it | 217 | | * becomes the low-order coefficients of a | 218 | | * 128-bit polynomial), and then doing the usual | 219 | | * reduction from 128 bits to 32 bits. | 220 | | */ | 221 | | x0 = _mm_xor_si128( | 222 | | x0, _mm_maskz_loadu_epi8((1 << len) - 1, p)); | 223 | | x0 = _mm_shuffle_epi8( | 224 | | x0, _mm_loadu_si128((const void *)&shift_tab[len])); | 225 | | goto reduce_x0; | 226 | | #else | 227 | 98 | return crc32_slice1(crc, p, len); | 228 | 98 | #endif | 229 | 98 | } | 230 | | /* | 231 | | * Handle 16 <= len < VL bytes where VL is 32 or 64. | 232 | | * Use 128-bit instructions so that these lengths aren't | 233 | | * slower with VL > 16 than with VL=16. | 234 | | */ | 235 | 0 | x0 = _mm_xor_si128(_mm_loadu_si128((const void *)p), x0); | 236 | 0 | if (len >= 32) { | 237 | 0 | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)(p + 16)), | 238 | 0 | mults_128b); | 239 | 0 | if (len >= 48) | 240 | 0 | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)(p + 32)), | 241 | 0 | mults_128b); | 242 | 0 | } | 243 | 0 | p += len & ~15; | 244 | 0 | goto less_than_16_remaining; | 245 | 98 | } | 246 | 25 | v0 = VXOR(VLOADU(p), M128I_TO_VEC(x0)); | 247 | 25 | if (len < 2*VL) { | 248 | 4 | p += VL; | 249 | 4 | goto less_than_vl_remaining; | 250 | 4 | } | 251 | 21 | v1 = VLOADU(p + 1*VL); | 252 | 21 | if (len < 4*VL) { | 253 | 15 | p += 2*VL; | 254 | 15 | goto less_than_2vl_remaining; | 255 | 15 | } | 256 | 6 | v2 = VLOADU(p + 2*VL); | 257 | 6 | v3 = VLOADU(p + 3*VL); | 258 | 6 | p += 4*VL; | 259 | 93 | } else { | 260 | | /* | 261 | | * If the length is large and the pointer is misaligned, align | 262 | | * it. For smaller lengths, just take the misaligned load | 263 | | * penalty. Note that on recent x86 CPUs, vmovdqu with an | 264 | | * aligned address is just as fast as vmovdqa, so there's no | 265 | | * need to use vmovdqa in the main loop. | 266 | | */ | 267 | 93 | if (len > 65536 && ((uintptr_t)p & (VL-1))) { | 268 | 0 | size_t align = -(uintptr_t)p & (VL-1); | 269 | |
| 270 | 0 | len -= align; | 271 | 0 | x0 = _mm_xor_si128(_mm_loadu_si128((const void *)p), x0); | 272 | 0 | p += 16; | 273 | 0 | if (align & 15) { | 274 | 0 | x0 = fold_lessthan16bytes(x0, p, align & 15, | 275 | 0 | mults_128b); | 276 | 0 | p += align & 15; | 277 | 0 | align &= ~15; | 278 | 0 | } | 279 | 0 | while (align) { | 280 | 0 | x0 = fold_vec128(x0, *(const __m128i *)p, | 281 | 0 | mults_128b); | 282 | 0 | p += 16; | 283 | 0 | align -= 16; | 284 | 0 | } | 285 | 0 | v0 = M128I_TO_VEC(x0); | 286 | | # if VL == 32 | 287 | | v0 = _mm256_inserti128_si256(v0, *(const __m128i *)p, 1); | 288 | | # elif VL == 64 | 289 | | v0 = _mm512_inserti32x4(v0, *(const __m128i *)p, 1); | 290 | | v0 = _mm512_inserti64x4(v0, *(const __m256i *)(p + 16), 1); | 291 | | # endif | 292 | 0 | p -= 16; | 293 | 93 | } else { | 294 | 93 | v0 = VXOR(VLOADU(p), M128I_TO_VEC(x0)); | 295 | 93 | } | 296 | 93 | v1 = VLOADU(p + 1*VL); | 297 | 93 | v2 = VLOADU(p + 2*VL); | 298 | 93 | v3 = VLOADU(p + 3*VL); | 299 | 93 | v4 = VLOADU(p + 4*VL); | 300 | 93 | v5 = VLOADU(p + 5*VL); | 301 | 93 | v6 = VLOADU(p + 6*VL); | 302 | 93 | v7 = VLOADU(p + 7*VL); | 303 | 93 | p += 8*VL; | 304 | | | 305 | | /* | 306 | | * This is the main loop, processing 8*VL bytes per iteration. | 307 | | * 4*VL is usually enough and would result in smaller code, but | 308 | | * Skylake and Cascade Lake need 8*VL to get full performance. | 309 | | */ | 310 | 181k | while (len >= 16*VL) { | 311 | 180k | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_8v); | 312 | 180k | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_8v); | 313 | 180k | v2 = fold_vec(v2, VLOADU(p + 2*VL), mults_8v); | 314 | 180k | v3 = fold_vec(v3, VLOADU(p + 3*VL), mults_8v); | 315 | 180k | v4 = fold_vec(v4, VLOADU(p + 4*VL), mults_8v); | 316 | 180k | v5 = fold_vec(v5, VLOADU(p + 5*VL), mults_8v); | 317 | 180k | v6 = fold_vec(v6, VLOADU(p + 6*VL), mults_8v); | 318 | 180k | v7 = fold_vec(v7, VLOADU(p + 7*VL), mults_8v); | 319 | 180k | p += 8*VL; | 320 | 180k | len -= 8*VL; | 321 | 180k | } | 322 | | | 323 | | /* Fewer than 8*VL bytes remain. */ | 324 | 93 | v0 = fold_vec(v0, v4, mults_4v); | 325 | 93 | v1 = fold_vec(v1, v5, mults_4v); | 326 | 93 | v2 = fold_vec(v2, v6, mults_4v); | 327 | 93 | v3 = fold_vec(v3, v7, mults_4v); | 328 | 93 | if (len & (4*VL)) { | 329 | 52 | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_4v); | 330 | 52 | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_4v); | 331 | 52 | v2 = fold_vec(v2, VLOADU(p + 2*VL), mults_4v); | 332 | 52 | v3 = fold_vec(v3, VLOADU(p + 3*VL), mults_4v); | 333 | 52 | p += 4*VL; | 334 | 52 | } | 335 | 93 | } | 336 | | /* Fewer than 4*VL bytes remain. */ | 337 | 99 | v0 = fold_vec(v0, v2, mults_2v); | 338 | 99 | v1 = fold_vec(v1, v3, mults_2v); | 339 | 99 | if (len & (2*VL)) { | 340 | 59 | v0 = fold_vec(v0, VLOADU(p + 0*VL), mults_2v); | 341 | 59 | v1 = fold_vec(v1, VLOADU(p + 1*VL), mults_2v); | 342 | 59 | p += 2*VL; | 343 | 59 | } | 344 | 114 | less_than_2vl_remaining: | 345 | | /* Fewer than 2*VL bytes remain. */ | 346 | 114 | v0 = fold_vec(v0, v1, mults_1v); | 347 | 114 | if (len & VL) { | 348 | 51 | v0 = fold_vec(v0, VLOADU(p), mults_1v); | 349 | 51 | p += VL; | 350 | 51 | } | 351 | 118 | less_than_vl_remaining: | 352 | | /* | 353 | | * Fewer than VL bytes remain. Reduce v0 (length VL bytes) to x0 | 354 | | * (length 16 bytes) and fold in any 16-byte data segments that remain. | 355 | | */ | 356 | 118 | #if VL == 16 | 357 | 118 | x0 = v0; | 358 | | #else | 359 | | { | 360 | | #if VL == 32 | 361 | | __m256i y0 = v0; | 362 | | #else | 363 | | const __m256i mults_256b = | 364 | | _mm256_set_epi64x(CRC32_X223_MODG, CRC32_X287_MODG, | 365 | | CRC32_X223_MODG, CRC32_X287_MODG); | 366 | | __m256i y0 = fold_vec256(_mm512_extracti64x4_epi64(v0, 0), | 367 | | _mm512_extracti64x4_epi64(v0, 1), | 368 | | mults_256b); | 369 | | if (len & 32) { | 370 | | y0 = fold_vec256(y0, _mm256_loadu_si256((const void *)p), | 371 | | mults_256b); | 372 | | p += 32; | 373 | | } | 374 | | #endif | 375 | | x0 = fold_vec128(_mm256_extracti128_si256(y0, 0), | 376 | | _mm256_extracti128_si256(y0, 1), mults_128b); | 377 | | } | 378 | | if (len & 16) { | 379 | | x0 = fold_vec128(x0, _mm_loadu_si128((const void *)p), | 380 | | mults_128b); | 381 | | p += 16; | 382 | | } | 383 | | #endif | 384 | 118 | less_than_16_remaining: | 385 | 118 | len &= 15; | 386 | | | 387 | | /* Handle any remainder of 1 to 15 bytes. */ | 388 | 118 | if (len) | 389 | 103 | x0 = fold_lessthan16bytes(x0, p, len, mults_128b); | 390 | | #if USE_AVX512 | 391 | | reduce_x0: | 392 | | #endif | 393 | | /* | 394 | | * Multiply the remaining 128-bit message polynomial 'x0' by x^32, then | 395 | | * reduce it modulo the generator polynomial G. This gives the CRC. | 396 | | * | 397 | | * This implementation matches that used in crc-pclmul-template.S from | 398 | | * https://lore.kernel.org/r/20250210174540.161705-4-ebiggers@kernel.org/ | 399 | | * with the parameters n=32 and LSB_CRC=1 (what the gzip CRC uses). See | 400 | | * there for a detailed explanation of the math used here. | 401 | | */ | 402 | 118 | x0 = _mm_xor_si128(_mm_clmulepi64_si128(x0, mults_128b, 0x10), | 403 | 118 | _mm_bsrli_si128(x0, 8)); | 404 | 118 | x1 = _mm_clmulepi64_si128(x0, barrett_reduction_constants, 0x00); | 405 | 118 | x1 = _mm_clmulepi64_si128(x1, barrett_reduction_constants, 0x10); | 406 | 118 | x0 = _mm_xor_si128(x0, x1); | 407 | | return _mm_extract_epi32(x0, 2); | 408 | 118 | } |
Unexecuted instantiation: crc32.c:crc32_x86_pclmulqdq |
409 | | |
410 | | #undef vec_t |
411 | | #undef fold_vec |
412 | | #undef VLOADU |
413 | | #undef VXOR |
414 | | #undef M128I_TO_VEC |
415 | | #undef MULTS |
416 | | #undef MULTS_8V |
417 | | #undef MULTS_4V |
418 | | #undef MULTS_2V |
419 | | #undef MULTS_1V |
420 | | |
421 | | #undef SUFFIX |
422 | | #undef ATTRIBUTES |
423 | | #undef VL |
424 | | #undef USE_AVX512 |