/src/zlib-ng/arch/x86/adler32_avx2_vnni.c
Line | Count | Source |
1 | | /* adler32_avx2_vnni.c -- compute the Adler-32 checksum of a data stream |
2 | | * Based on Brian Bockelman's AVX2 version |
3 | | * Copyright (C) 1995-2011 Mark Adler |
4 | | * Authors: |
5 | | * Adam Stylinski <kungfujesus06@gmail.com> |
6 | | * Brian Bockelman <bockelman@gmail.com> |
7 | | * For conditions of distribution and use, see copyright notice in zlib.h |
8 | | */ |
9 | | |
10 | | #ifdef X86_AVX2VNNI |
11 | | |
12 | | #include "zbuild.h" |
13 | | #include "adler32_p.h" |
14 | | #include "arch_functions.h" |
15 | | #include <immintrin.h> |
16 | | #include "x86_intrins.h" |
17 | | #include "adler32_avx2_p.h" |
18 | | |
19 | 0 | Z_INTERNAL uint32_t adler32_avx2_vnni(uint32_t adler, const uint8_t *src, size_t len) { |
20 | 0 | uint32_t adler0, adler1; |
21 | 0 | adler1 = (adler >> 16) & 0xffff; |
22 | 0 | adler0 = adler & 0xffff; |
23 | |
|
24 | 0 | rempeel: |
25 | 0 | if (len < 16) { |
26 | 0 | return adler32_copy_tail(adler0, NULL, src, len, adler1, 1, 15, 0); |
27 | 0 | } else if (len < 32) { |
28 | 0 | return adler32_ssse3(adler, src, len); |
29 | 0 | } |
30 | | |
31 | 0 | const __m256i dot2v = _mm256_setr_epi8(64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, |
32 | 0 | 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33); |
33 | 0 | const __m256i dot2v_0 = _mm256_setr_epi8(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, |
34 | 0 | 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); |
35 | |
|
36 | 0 | const __m256i zero = _mm256_setzero_si256(); |
37 | | /* Just like the avx512 version but we need to split over 2 registers */ |
38 | 0 | __m256i vs1a, vs2a, vs2_0a; |
39 | 0 | __m256i vs1b, vs2b, vs2_0b; |
40 | |
|
41 | 0 | while (len >= 32) { |
42 | | /* Set the bottommost bytes */ |
43 | 0 | vs1a = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler0)); |
44 | 0 | vs2a = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler1)); |
45 | |
|
46 | 0 | vs1b = zero; |
47 | 0 | vs2b = zero; |
48 | |
|
49 | 0 | __m256i vs1_0a = vs1a; |
50 | 0 | __m256i vs1_0b = vs1b; |
51 | 0 | __m256i vs3 = zero; |
52 | 0 | __m256i vs3b = zero; |
53 | 0 | vs2_0a = zero; |
54 | 0 | vs2_0b = zero; |
55 | |
|
56 | 0 | size_t k = ALIGN_DOWN(MIN(len, NMAX), 32); |
57 | 0 | len -= k; |
58 | | |
59 | | /* We might get a tad bit more ILP here if we sum to a second register in the loop */ |
60 | 0 | __m256i vbuf0, vbuf1, vbuf2, vbuf3; |
61 | | |
62 | | /* Manually unrolled this loop by 4 for a decent amount of ILP */ |
63 | 0 | while (k >= 128) { |
64 | | /* |
65 | | vs1 = adler + sum(c[i]) |
66 | | vs2 = sum2 + 64 vs1 + sum( (64-i+1) c[i] ) |
67 | | */ |
68 | 0 | vbuf0 = _mm256_loadu_si256((__m256i*)src); |
69 | 0 | vbuf1 = _mm256_loadu_si256((__m256i*)(src + 32)); |
70 | 0 | vbuf2 = _mm256_loadu_si256((__m256i*)(src + 64)); |
71 | 0 | vbuf3 = _mm256_loadu_si256((__m256i*)(src + 96)); |
72 | 0 | src += 128; |
73 | 0 | k -= 128; |
74 | |
|
75 | 0 | __m256i vs1_sada = _mm256_sad_epu8(vbuf0, zero); |
76 | 0 | __m256i vs1_sadb = _mm256_sad_epu8(vbuf1, zero); |
77 | |
|
78 | 0 | vs1a = _mm256_add_epi32(vs1a, vs1_sada); |
79 | 0 | vs1b = _mm256_add_epi32(vs1b, vs1_sadb); |
80 | |
|
81 | 0 | vs3 = _mm256_add_epi32(vs3, vs1_0a); |
82 | 0 | vs3b = _mm256_add_epi32(vs3b, vs1_0b); |
83 | |
|
84 | 0 | vs2a = _mm256_dpbusd_epi32(vs2a, vbuf0, dot2v); |
85 | 0 | vs2b = _mm256_dpbusd_epi32(vs2b, vbuf1, dot2v_0); |
86 | |
|
87 | 0 | vs3 = _mm256_add_epi32(vs3, vs1a); |
88 | 0 | vs3b = _mm256_add_epi32(vs3b, vs1b); |
89 | |
|
90 | 0 | vs1_sada = _mm256_sad_epu8(vbuf2, zero); |
91 | 0 | vs1_sadb = _mm256_sad_epu8(vbuf3, zero); |
92 | |
|
93 | 0 | vs1a = _mm256_add_epi32(vs1a, vs1_sada); |
94 | 0 | vs1b = _mm256_add_epi32(vs1b, vs1_sadb); |
95 | |
|
96 | 0 | vs2_0a = _mm256_dpbusd_epi32(vs2_0a, vbuf2, dot2v); |
97 | 0 | vs2_0b = _mm256_dpbusd_epi32(vs2_0b, vbuf3, dot2v_0); |
98 | |
|
99 | 0 | vs1_0a = vs1a; |
100 | 0 | vs1_0b = vs1b; |
101 | 0 | } |
102 | |
|
103 | 0 | vs3 = _mm256_add_epi32(vs3, vs3b); |
104 | 0 | vs3 = _mm256_slli_epi32(vs3, 6); |
105 | 0 | vs2_0a = _mm256_add_epi32(vs2_0a, vs2_0b); |
106 | 0 | vs2a = _mm256_add_epi32(vs2a, vs3); |
107 | 0 | vs2a = _mm256_add_epi32(vs2a, vs2_0a); |
108 | 0 | vs2a = _mm256_add_epi32(vs2a, vs2b); |
109 | 0 | vs1a = _mm256_add_epi32(vs1a, vs1b); |
110 | 0 | vs1_0a = _mm256_add_epi32(vs1_0a, vs1_0b); |
111 | |
|
112 | 0 | vs3 = zero; |
113 | | |
114 | |
|
115 | 0 | while (k >= 32) { |
116 | 0 | __m256i vbuf = _mm256_loadu_si256((__m256i*)src); |
117 | 0 | src += 32; |
118 | 0 | k -= 32; |
119 | |
|
120 | 0 | __m256i vs1_sad = _mm256_sad_epu8(vbuf, zero); // Sum of abs diff, resulting in 2 x int32's |
121 | |
|
122 | 0 | vs1a = _mm256_add_epi32(vs1a, vs1_sad); |
123 | 0 | vs3 = _mm256_add_epi32(vs3, vs1_0a); |
124 | 0 | vs2a = _mm256_dpbusd_epi32(vs2a, vbuf, dot2v_0); |
125 | 0 | vs1_0a = vs1a; |
126 | 0 | } |
127 | |
|
128 | 0 | vs3 = _mm256_slli_epi32(vs3, 5); |
129 | 0 | vs2a = _mm256_add_epi32(vs2a, vs3); |
130 | |
|
131 | 0 | adler0 = partial_hsum256(vs1a) % BASE; |
132 | 0 | adler1 = hsum256(vs2a) % BASE; |
133 | 0 | } |
134 | |
|
135 | 0 | adler = adler0 | (adler1 << 16); |
136 | |
|
137 | 0 | if (len) { |
138 | 0 | goto rempeel; |
139 | 0 | } |
140 | | |
141 | 0 | return adler; |
142 | 0 | } |
143 | | |
144 | | /* There's a data dependency bubble to pop here to maximize throughput but I haven't been able to quite factor |
145 | | * out exactly what gets us there. I'm leaving this here as this is the closest I managed to get. For performance |
146 | | * cores on Raptor Lake, the avx2 copy function is faster. For E-cores, this function is faster. There's |
147 | | * likely some sophisticated fusion happening on Raptor Cove that is just not happening on Gracemont */ |
148 | | |
149 | | #if 0 |
150 | | Z_INTERNAL uint32_t adler32_copy_avx2_vnni(uint32_t adler, uint8_t *dst, const uint8_t *src, size_t len) { |
151 | | uint32_t adler0, adler1; |
152 | | adler1 = (adler >> 16) & 0xffff; |
153 | | adler0 = adler & 0xffff; |
154 | | |
155 | | rem_peel_copy: |
156 | | if (len < 16) { |
157 | | return adler32_copy_tail(adler0, dst, src, len, adler1, 1, 15, 1); |
158 | | } else if (len < 32) { |
159 | | return adler32_copy_sse42(adler, dst, src, len); |
160 | | } |
161 | | |
162 | | const __m256i dot2v = _mm256_setr_epi8(64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, |
163 | | 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33); |
164 | | const __m256i dot2v_0 = _mm256_setr_epi8(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, |
165 | | 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); |
166 | | |
167 | | const __m256i zero = _mm256_setzero_si256(); |
168 | | __m256i vs1, vs2, vs2_0, vs3_0; |
169 | | |
170 | | while (len >= 32) { |
171 | | vs1 = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler0)); |
172 | | vs2 = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler1)); |
173 | | __m256i vs1_0 = vs1; |
174 | | __m256i vs1_1 = zero; |
175 | | __m256i vs3 = zero; |
176 | | vs2_0 = vs3; |
177 | | vs3_0 = vs3; |
178 | | |
179 | | size_t k = MIN(len, NMAX); |
180 | | k -= k % 32; |
181 | | len -= k; |
182 | | |
183 | | /* We might get a tad bit more ILP here if we sum to a second register in the loop */ |
184 | | __m256i vbuf0, vbuf1; |
185 | | |
186 | | /* Manually unrolled this loop by 2 for a decent amount of ILP */ |
187 | | while (k >= 64) { |
188 | | /* |
189 | | vs1 = adler + sum(c[i]) |
190 | | vs2 = sum2 + 64 vs1 + sum( (64-i+1) c[i] ) |
191 | | */ |
192 | | vbuf0 = _mm256_loadu_si256((__m256i*)src); |
193 | | vbuf1 = _mm256_loadu_si256((__m256i*)(src + 32)); |
194 | | _mm256_storeu_si256((__m256i*)dst, vbuf0); |
195 | | _mm256_storeu_si256((__m256i*)(dst + 32), vbuf1); |
196 | | src += 64; |
197 | | dst += 64; |
198 | | k -= 64; |
199 | | |
200 | | __m256i vs1_sad = _mm256_sad_epu8(vbuf0, zero); |
201 | | __m256i vs1_sad2 = _mm256_sad_epu8(vbuf1, zero); |
202 | | |
203 | | /* multiply-add, resulting in 16 ints. Fuse with sum stage from prior versions, as we now have the dp |
204 | | * instructions to eliminate them */ |
205 | | vs2 = _mm256_dpbusd_epi32(vs2, vbuf0, dot2v); |
206 | | vs2_0 = _mm256_dpbusd_epi32(vs2_0, vbuf1, dot2v_0); |
207 | | |
208 | | vs1_0 = _mm256_add_epi32(vs1_0, vs1_sad); |
209 | | vs1_1 = _mm256_add_epi32(vs1_1, vs1_sad2); |
210 | | |
211 | | vs3_0 = _mm256_add_epi32(vs3_0, vs1_1); |
212 | | vs3 = _mm256_add_epi32(vs3, vs1_0); |
213 | | vs1_0 = vs1; |
214 | | } |
215 | | |
216 | | vs2 = _mm256_add_epi32(vs2_0, vs2); |
217 | | vs3 = _mm256_slli_epi32(vs3, 6); |
218 | | vs2 = _mm256_add_epi32(vs2, vs3); |
219 | | vs2 = _mm256_add_epi32(vs3_0, vs2); |
220 | | vs3 = _mm256_setzero_si256(); |
221 | | |
222 | | while (k >= 32) { |
223 | | __m256i vbuf = _mm256_loadu_si256((__m256i*)src); |
224 | | _mm256_storeu_si256((__m256i*)dst, vbuf); |
225 | | src += 32; |
226 | | dst += 32; |
227 | | k -= 32; |
228 | | |
229 | | __m256i vs1_sad = _mm256_sad_epu8(vbuf, zero); // Sum of abs diff, resulting in 2 x int32's |
230 | | |
231 | | vs1 = _mm256_add_epi32(vs1, vs1_sad); |
232 | | vs3 = _mm256_add_epi32(vs3, vs1_0); |
233 | | vs2 = _mm256_dpbusd_epi32(vs2, vbuf, dot2v_0); |
234 | | vs1_0 = vs1; |
235 | | } |
236 | | |
237 | | vs3 = _mm256_slli_epi32(vs3, 5); |
238 | | vs2 = _mm256_add_epi32(vs2, vs3); |
239 | | |
240 | | adler0 = partial_hsum256(vs1) % BASE; |
241 | | adler1 = hsum256(vs2) % BASE; |
242 | | } |
243 | | |
244 | | adler = adler0 | (adler1 << 16); |
245 | | |
246 | | /* Process tail (len < 64). */ |
247 | | if (len) { |
248 | | goto rem_peel_copy; |
249 | | } |
250 | | |
251 | | return adler; |
252 | | } |
253 | | #endif |
254 | | |
255 | | #endif |