/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zlib-rs-0.6.6/src/adler32/avx2.rs
Line | Count | Source |
1 | | //! # Safety |
2 | | //! |
3 | | //! The functions in this module should only be executed on x86 machines with the AVX2 extension. |
4 | | use core::arch::x86_64::{ |
5 | | __m256i, _mm256_add_epi32, _mm256_castsi256_si128, _mm256_extracti128_si256, _mm256_madd_epi16, |
6 | | _mm256_maddubs_epi16, _mm256_permutevar8x32_epi32, _mm256_sad_epu8, _mm256_slli_epi32, |
7 | | _mm256_zextsi128_si256, _mm_add_epi32, _mm_cvtsi128_si32, _mm_cvtsi32_si128, _mm_shuffle_epi32, |
8 | | _mm_unpackhi_epi64, |
9 | | }; |
10 | | |
11 | | use crate::adler32::{ |
12 | | generic::{adler32_len_16, adler32_len_64}, |
13 | | BASE, NMAX, |
14 | | }; |
15 | | |
16 | 0 | const fn __m256i_literal(bytes: [u8; 32]) -> __m256i { |
17 | | // SAFETY: any valid [u8; 32] represents a valid __m256i |
18 | 0 | unsafe { core::mem::transmute(bytes) } |
19 | 0 | } |
20 | | |
21 | | const DOT2V: __m256i = __m256i_literal([ |
22 | | 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, |
23 | | 8, 7, 6, 5, 4, 3, 2, 1, |
24 | | ]); |
25 | | |
26 | | const DOT3V: __m256i = __m256i_literal([ |
27 | | 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, |
28 | | ]); |
29 | | |
30 | | const ZERO: __m256i = __m256i_literal([0u8; 32]); |
31 | | |
32 | | /// 32 bit horizontal sum, adapted from Agner Fog's vector library. |
33 | | #[target_feature(enable = "avx2")] |
34 | 97.5k | unsafe fn hsum256(x: __m256i) -> u32 { |
35 | | #[allow(unused_unsafe)] // because target features 1.1 |
36 | | unsafe { |
37 | 97.5k | let sum1 = _mm_add_epi32(_mm256_extracti128_si256(x, 1), _mm256_castsi256_si128(x)); |
38 | 97.5k | let sum2 = _mm_add_epi32(sum1, _mm_unpackhi_epi64(sum1, sum1)); |
39 | 97.5k | let sum3 = _mm_add_epi32(sum2, _mm_shuffle_epi32(sum2, 1)); |
40 | 97.5k | _mm_cvtsi128_si32(sum3) as u32 |
41 | | } |
42 | 97.5k | } |
43 | | |
44 | | #[target_feature(enable = "avx2")] |
45 | 97.5k | unsafe fn partial_hsum256(x: __m256i) -> u32 { |
46 | | const PERM_VEC: __m256i = __m256i_literal([ |
47 | | 0, 0, 0, 0, // |
48 | | 2, 0, 0, 0, // |
49 | | 4, 0, 0, 0, // |
50 | | 6, 0, 0, 0, // |
51 | | 1, 0, 0, 0, // |
52 | | 1, 0, 0, 0, // |
53 | | 1, 0, 0, 0, // |
54 | | 1, 0, 0, 0, // |
55 | | ]); |
56 | | |
57 | | #[allow(unused_unsafe)] // because target features 1.1 |
58 | | unsafe { |
59 | 97.5k | let non_zero = _mm256_permutevar8x32_epi32(x, PERM_VEC); |
60 | 97.5k | let non_zero_sse = _mm256_castsi256_si128(non_zero); |
61 | 97.5k | let sum2 = _mm_add_epi32(non_zero_sse, _mm_unpackhi_epi64(non_zero_sse, non_zero_sse)); |
62 | 97.5k | let sum3 = _mm_add_epi32(sum2, _mm_shuffle_epi32(sum2, 1)); |
63 | 97.5k | _mm_cvtsi128_si32(sum3) as u32 |
64 | | } |
65 | 97.5k | } |
66 | | |
67 | 4.36k | pub fn adler32_avx2(adler: u32, src: &[u8]) -> u32 { |
68 | 4.36k | assert!(crate::cpu_features::is_enabled_avx2_and_bmi2()); |
69 | | // SAFETY: the assertion above ensures this code is not executed unless the CPU has AVX2. |
70 | 4.36k | unsafe { adler32_avx2_help(adler, src) } |
71 | 4.36k | } |
72 | | |
73 | | #[target_feature(enable = "avx2")] |
74 | | #[target_feature(enable = "bmi2")] |
75 | | #[target_feature(enable = "bmi1")] |
76 | 4.36k | unsafe fn adler32_avx2_help(adler: u32, src: &[u8]) -> u32 { |
77 | 4.36k | if src.is_empty() { |
78 | 1.37k | return adler; |
79 | 2.99k | } |
80 | | |
81 | | // SAFETY: [u8; 32] safely transmutes into __m256i. |
82 | 2.99k | let (before, middle, after) = unsafe { src.align_to::<__m256i>() }; |
83 | | |
84 | 2.99k | let mut adler1 = (adler >> 16) & 0xffff; |
85 | 2.99k | let mut adler0 = adler & 0xffff; |
86 | | |
87 | 2.99k | let adler = if before.len() < 16 { |
88 | 2.84k | adler32_len_16(adler0, before, adler1) |
89 | 156 | } else if before.len() < 32 { |
90 | 156 | adler32_len_64(adler0, before, adler1) |
91 | | } else { |
92 | 0 | adler |
93 | | }; |
94 | | |
95 | 2.99k | adler1 = (adler >> 16) & 0xffff; |
96 | 2.99k | adler0 = adler & 0xffff; |
97 | | |
98 | | // use largest step possible (without causing overflow) |
99 | 97.5k | for chunk in middle.chunks(NMAX as usize / 32) { |
100 | 97.5k | (adler0, adler1) = unsafe { helper_32_bytes(adler0, adler1, chunk) }; |
101 | 97.5k | } |
102 | | |
103 | 2.99k | if !after.is_empty() { |
104 | 2.45k | if after.len() < 16 { |
105 | 1.28k | return adler32_len_16(adler0, after, adler1); |
106 | 1.17k | } else if after.len() < 32 { |
107 | 1.17k | return adler32_len_64(adler0, after, adler1); |
108 | | } else { |
109 | 0 | unreachable!() |
110 | | } |
111 | 539 | } |
112 | | |
113 | 539 | adler0 | (adler1 << 16) |
114 | 4.36k | } |
115 | | |
116 | | #[target_feature(enable = "avx2")] |
117 | 97.5k | unsafe fn helper_32_bytes(mut adler0: u32, mut adler1: u32, src: &[__m256i]) -> (u32, u32) { |
118 | | unsafe { |
119 | 97.5k | let mut vs1 = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler0 as i32)); |
120 | 97.5k | let mut vs2 = _mm256_zextsi128_si256(_mm_cvtsi32_si128(adler1 as i32)); |
121 | | |
122 | 97.5k | let mut vs1_0 = vs1; |
123 | 97.5k | let mut vs3 = ZERO; |
124 | | |
125 | 16.6M | for vbuf in src.iter().copied() { |
126 | 16.6M | let vs1_sad = _mm256_sad_epu8(vbuf, ZERO); // Sum of abs diff, resulting in 2 x int32's |
127 | 16.6M | |
128 | 16.6M | vs1 = _mm256_add_epi32(vs1, vs1_sad); |
129 | 16.6M | vs3 = _mm256_add_epi32(vs3, vs1_0); |
130 | 16.6M | let v_short_sum2 = _mm256_maddubs_epi16(vbuf, DOT2V); // sum 32 uint8s to 16 shorts |
131 | 16.6M | let vsum2 = _mm256_madd_epi16(v_short_sum2, DOT3V); // sum 16 shorts to 8 uint32s |
132 | 16.6M | vs2 = _mm256_add_epi32(vsum2, vs2); |
133 | 16.6M | vs1_0 = vs1; |
134 | 16.6M | } |
135 | | |
136 | | /* Defer the multiplication with 32 to outside of the loop */ |
137 | 97.5k | vs3 = _mm256_slli_epi32(vs3, 5); |
138 | 97.5k | vs2 = _mm256_add_epi32(vs2, vs3); |
139 | | |
140 | 97.5k | adler0 = partial_hsum256(vs1) % BASE; |
141 | 97.5k | adler1 = hsum256(vs2) % BASE; |
142 | | |
143 | 97.5k | (adler0, adler1) |
144 | | } |
145 | 97.5k | } |
146 | | |
147 | | #[cfg(test)] |
148 | | #[cfg(target_feature = "avx2")] |
149 | | mod test { |
150 | | use super::*; |
151 | | |
152 | | #[test] |
153 | | fn empty_input() { |
154 | | let avx2 = adler32_avx2(0, &[]); |
155 | | let rust = crate::adler32::generic::adler32_rust(0, &[]); |
156 | | |
157 | | assert_eq!(rust, avx2); |
158 | | } |
159 | | |
160 | | quickcheck::quickcheck! { |
161 | | fn adler32_avx2_is_adler32_rust(v: Vec<u8>, start: u32) -> bool { |
162 | | let avx2 = adler32_avx2(start, &v); |
163 | | let rust = crate::adler32::generic::adler32_rust(start, &v); |
164 | | |
165 | | rust == avx2 |
166 | | } |
167 | | } |
168 | | |
169 | | const INPUT: [u8; 1024] = { |
170 | | let mut array = [0; 1024]; |
171 | | let mut i = 0; |
172 | | while i < array.len() { |
173 | | array[i] = i as u8; |
174 | | i += 1; |
175 | | } |
176 | | |
177 | | array |
178 | | }; |
179 | | |
180 | | #[test] |
181 | | fn start_alignment() { |
182 | | // SIMD algorithm is sensitive to alignment; |
183 | | for i in 0..16 { |
184 | | for start in [crate::ADLER32_INITIAL_VALUE as u32, 42] { |
185 | | let avx2 = adler32_avx2(start, &INPUT[i..]); |
186 | | let rust = crate::adler32::generic::adler32_rust(start, &INPUT[i..]); |
187 | | |
188 | | assert_eq!(avx2, rust, "offset = {i}, start = {start}"); |
189 | | } |
190 | | } |
191 | | } |
192 | | |
193 | | #[test] |
194 | | #[cfg_attr(miri, ignore)] |
195 | | fn large_input() { |
196 | | const DEFAULT: &[u8] = include_bytes!("../deflate/test-data/paper-100k.pdf"); |
197 | | |
198 | | let avx2 = adler32_avx2(42, DEFAULT); |
199 | | let rust = crate::adler32::generic::adler32_rust(42, DEFAULT); |
200 | | |
201 | | assert_eq!(avx2, rust); |
202 | | } |
203 | | } |