1#![allow(clippy::needless_range_loop)]
2#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
6#[inline]
7#[target_feature(enable = "avx2")]
8pub(crate) unsafe fn finalize_simd_avx2(counts: &[i32; 10240], threshold: i32) -> [u128; 80] {
11 use std::arch::x86_64::{
12 _mm256_castsi256_ps, _mm256_cmpgt_epi32, _mm256_loadu_si256, _mm256_movemask_ps,
13 _mm256_set1_epi32,
14 };
15 let mut data = [0u128; 80];
16 let threshold_vec = _mm256_set1_epi32(threshold);
17
18 for i in 0..80 {
19 let offset = i * 128;
20 let mut word_low = 0u64;
21 let mut word_high = 0u64;
22 for j in 0..8 {
23 let packed = unsafe {
25 let ptr = counts.as_ptr().add(offset + j * 8);
26 let chunk = _mm256_loadu_si256(ptr.cast());
27 let mask = _mm256_cmpgt_epi32(chunk, threshold_vec);
28 _mm256_movemask_ps(_mm256_castsi256_ps(mask)) as u64
29 };
30 word_low |= packed << (j * 8);
31 }
32 for j in 0..8 {
33 let packed = unsafe {
35 let ptr = counts.as_ptr().add(offset + 64 + j * 8);
36 let chunk = _mm256_loadu_si256(ptr.cast());
37 let mask = _mm256_cmpgt_epi32(chunk, threshold_vec);
38 _mm256_movemask_ps(_mm256_castsi256_ps(mask)) as u64
39 };
40 word_high |= packed << (j * 8);
41 }
42 data[i] = (word_low as u128) | ((word_high as u128) << 64);
43 }
44 data
45}
46
47#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
49#[inline]
50#[target_feature(enable = "neon")]
51pub(crate) unsafe fn finalize_simd_neon(counts: &[i32; 10240], threshold: i32) -> [u128; 80] {
54 use std::arch::aarch64::{vaddvq_u32, vandq_u32, vcgtq_s32, vdupq_n_s32, vld1q_s32};
55 let mut data = [0u128; 80];
56 let weights = unsafe {
58 let w = [1u32, 2, 4, 8];
59 std::arch::aarch64::vld1q_u32(w.as_ptr())
60 };
61
62 for i in 0..80 {
63 let offset = i * 128;
64 let mut word_low = 0u64;
65 let mut word_high = 0u64;
66 for j in 0..16 {
67 let packed = unsafe {
69 let ptr = counts.as_ptr().add(offset + j * 4);
70 let chunk = vld1q_s32(ptr);
71 let mask = vcgtq_s32(chunk, vdupq_n_s32(threshold));
72 let weighted = vandq_u32(mask, weights);
73 vaddvq_u32(weighted) as u64
74 };
75 word_low |= packed << (j * 4);
76 }
77 for j in 0..16 {
78 let packed = unsafe {
80 let ptr = counts.as_ptr().add(offset + 64 + j * 4);
81 let chunk = vld1q_s32(ptr);
82 let mask = vcgtq_s32(chunk, vdupq_n_s32(threshold));
83 let weighted = vandq_u32(mask, weights);
84 vaddvq_u32(weighted) as u64
85 };
86 word_high |= packed << (j * 4);
87 }
88 data[i] = (word_low as u128) | ((word_high as u128) << 64);
89 }
90 data
91}
92
93#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
95#[inline]
96#[target_feature(enable = "avx2")]
97pub(crate) unsafe fn update_counts_simd_avx2(
100 counts: &mut [i32; 10240],
101 hv: &[u128; 80],
102 sign: i32,
103) {
104 use std::arch::x86_64::{
105 _mm256_add_epi32, _mm256_and_si256, _mm256_cmpeq_epi32, _mm256_loadu_si256,
106 _mm256_set_epi32, _mm256_set1_epi32, _mm256_storeu_si256,
107 };
108
109 let sign_vec = _mm256_set1_epi32(sign);
110 let bit_selector = _mm256_set_epi32(128, 64, 32, 16, 8, 4, 2, 1);
113
114 for i in 0..80 {
115 let word_ptr = &hv[i] as *const u128 as *const u8;
116 let counts_ptr = unsafe { counts.as_mut_ptr().add(i * 128) };
118
119 for j in (0..16).step_by(2) {
121 let byte0 = unsafe { *word_ptr.add(j) };
123 let byte1 = unsafe { *word_ptr.add(j + 1) };
124
125 if byte0 != 0 {
126 let byte_vec = _mm256_set1_epi32(i32::from(byte0));
127 let mask =
128 _mm256_cmpeq_epi32(_mm256_and_si256(byte_vec, bit_selector), bit_selector);
129 let increment = _mm256_and_si256(mask, sign_vec);
130
131 let target_ptr = unsafe { counts_ptr.add(j * 8) };
132 let current_counts = unsafe { _mm256_loadu_si256(target_ptr.cast()) };
133 let new_counts = _mm256_add_epi32(current_counts, increment);
134 unsafe { _mm256_storeu_si256(target_ptr.cast(), new_counts) };
135 }
136
137 if byte1 != 0 {
138 let byte_vec = _mm256_set1_epi32(i32::from(byte1));
139 let mask =
140 _mm256_cmpeq_epi32(_mm256_and_si256(byte_vec, bit_selector), bit_selector);
141 let increment = _mm256_and_si256(mask, sign_vec);
142
143 let target_ptr = unsafe { counts_ptr.add((j + 1) * 8) };
144 let current_counts = unsafe { _mm256_loadu_si256(target_ptr.cast()) };
145 let new_counts = _mm256_add_epi32(current_counts, increment);
146 unsafe { _mm256_storeu_si256(target_ptr.cast(), new_counts) };
147 }
148 }
149 }
150}
151
152#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
154#[inline]
155#[target_feature(enable = "neon")]
156pub(crate) unsafe fn update_counts_simd_neon(
159 counts: &mut [i32; 10240],
160 hv: &[u128; 80],
161 sign: i32,
162) {
163 for i in 0..80 {
164 let word_ptr = &hv[i] as *const u128 as *const u8;
165 let counts_ptr = unsafe { counts.as_mut_ptr().add(i * 128) };
167
168 for j in 0..16 {
169 let byte = unsafe { *word_ptr.add(j) } as i32;
171 if byte == 0 {
172 continue;
173 }
174
175 for k in 0..8 {
176 if (byte & (1 << k)) != 0 {
177 unsafe { *counts_ptr.add(j * 8 + k) += sign };
179 }
180 }
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
188 use super::*;
189 use crate::hyperdim::HVec10240;
190 use rand::Rng;
191
192 fn finalize_scalar(counts: &[i32; 10240], threshold: i32) -> [u128; 80] {
193 let mut data = [0u128; 80];
194 for i in 0..80 {
195 let offset = i * 128;
196 for j in 0..128 {
197 if counts[offset + j] > threshold {
198 data[i] |= 1u128 << j;
199 }
200 }
201 }
202 data
203 }
204
205 fn make_test_counts(seed: u64) -> [i32; 10240] {
206 use rand::RngExt;
207 use rand::SeedableRng;
208 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
209 let mut counts = [0i32; 10240];
210 for i in 0..10240 {
211 counts[i] = rng.random_range(-10..10);
212 }
213 counts
214 }
215
216 #[test]
217 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
218 fn test_finalize_simd_avx2_consistency() {
219 if is_x86_feature_detected!("avx2") {
220 for seed in 0..10 {
221 let counts = make_test_counts(seed);
222 for threshold in [-2, -1, 0, 1, 2] {
223 let scalar = finalize_scalar(&counts, threshold);
224 let simd = unsafe { finalize_simd_avx2(&counts, threshold) };
226 assert_eq!(simd, scalar);
227 }
228 }
229 }
230 }
231
232 #[test]
233 #[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
234 fn test_finalize_simd_neon_consistency() {
235 for seed in 0..10 {
236 let counts = make_test_counts(seed);
237 for threshold in [-2, -1, 0, 1, 2] {
238 let scalar = finalize_scalar(&counts, threshold);
239 let simd = unsafe { finalize_simd_neon(&counts, threshold) };
241 assert_eq!(simd, scalar);
242 }
243 }
244 }
245
246 fn update_counts_scalar(counts: &mut [i32; 10240], hv: &[u128; 80], sign: i32) {
247 for i in 0..80 {
248 let mut val = hv[i];
249 let offset = i * 128;
250 for j in 0..128 {
251 if (val & 1) != 0 {
252 counts[offset + j] += sign;
253 }
254 val >>= 1;
255 }
256 }
257 }
258
259 #[test]
260 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
261 fn test_update_counts_simd_avx2_consistency() {
262 if is_x86_feature_detected!("avx2") {
263 let mut counts_scalar = [0i32; 10240];
264 let mut counts_simd = [0i32; 10240];
265 let mut hvs = Vec::new();
266 for i in 0..10 {
267 hvs.push(HVec10240::new_seeded(i).data);
268 }
269 for hv in &hvs {
270 update_counts_scalar(&mut counts_scalar, hv, 1);
271 unsafe { update_counts_simd_avx2(&mut counts_simd, hv, 1) };
273 }
274 assert_eq!(counts_scalar, counts_simd);
275 for hv in &hvs {
276 update_counts_scalar(&mut counts_scalar, hv, -1);
277 unsafe { update_counts_simd_avx2(&mut counts_simd, hv, -1) };
279 }
280 assert_eq!(counts_scalar, counts_simd);
281 }
282 }
283
284 #[test]
285 #[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
286 fn test_update_counts_simd_neon_consistency() {
287 let mut counts_scalar = [0i32; 10240];
288 let mut counts_simd = [0i32; 10240];
289 let mut hvs = Vec::new();
290 for i in 0..10 {
291 hvs.push(HVec10240::new_seeded(i).data);
292 }
293 for hv in &hvs {
294 update_counts_scalar(&mut counts_scalar, hv, 1);
295 unsafe { update_counts_simd_neon(&mut counts_simd, hv, 1) };
297 }
298 assert_eq!(counts_scalar, counts_simd);
299 for hv in &hvs {
300 update_counts_scalar(&mut counts_scalar, hv, -1);
301 unsafe { update_counts_simd_neon(&mut counts_simd, hv, -1) };
303 }
304 assert_eq!(counts_scalar, counts_simd);
305 }
306}