1#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
6use std::arch::x86_64::{
7 _mm256_add_epi8, _mm256_add_epi64, _mm256_and_si256, _mm256_loadu_si256, _mm256_sad_epu8,
8 _mm256_set1_epi8, _mm256_setr_epi8, _mm256_setzero_si256, _mm256_shuffle_epi8,
9 _mm256_srli_epi16, _mm256_storeu_si256, _mm256_xor_si256,
10};
11
12#[allow(dead_code)]
13pub(crate) fn hamming_distance_optimized(lhs: &[u128; 80], rhs: &[u128; 80]) -> u32 {
14 let mut d0 = 0;
15 let mut d1 = 0;
16 let mut d2 = 0;
17 let mut d3 = 0;
18
19 for i in (0..80).step_by(4) {
21 d0 += (lhs[i] ^ rhs[i]).count_ones();
22 d1 += (lhs[i + 1] ^ rhs[i + 1]).count_ones();
23 d2 += (lhs[i + 2] ^ rhs[i + 2]).count_ones();
24 d3 += (lhs[i + 3] ^ rhs[i + 3]).count_ones();
25 }
26 d0 + d1 + d2 + d3
27}
28
29#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
30#[inline]
31#[target_feature(enable = "avx2")]
32pub(crate) unsafe fn and_simd_avx2(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
35 let mut res = [0u128; 80];
36 for i in (0..80).step_by(2) {
37 unsafe {
41 let l = _mm256_loadu_si256(lhs.as_ptr().add(i).cast());
42 let r = _mm256_loadu_si256(rhs.as_ptr().add(i).cast());
43 _mm256_storeu_si256(res.as_mut_ptr().add(i).cast(), _mm256_and_si256(l, r));
44 }
45 }
46 res
47}
48
49#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
50#[inline]
51#[target_feature(enable = "avx2")]
52pub(crate) unsafe fn bind_simd_avx2(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
55 let mut res = [0u128; 80];
56 for i in (0..80).step_by(2) {
57 unsafe {
61 let l = _mm256_loadu_si256(lhs.as_ptr().add(i).cast());
62 let r = _mm256_loadu_si256(rhs.as_ptr().add(i).cast());
63 _mm256_storeu_si256(res.as_mut_ptr().add(i).cast(), _mm256_xor_si256(l, r));
64 }
65 }
66 res
67}
68
69#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
70#[inline]
71#[target_feature(enable = "avx2")]
72pub(crate) unsafe fn hamming_distance_simd_avx2(lhs: &[u128; 80], rhs: &[u128; 80]) -> u32 {
75 const LOADS_PER_FLUSH: usize = 20;
76 const TOTAL_LOADS: usize = 40;
77 const UNROLL_FACTOR: usize = 2;
78 const _: () = assert!(80 % (LOADS_PER_FLUSH * 2) == 0);
82 const _: () = assert!(LOADS_PER_FLUSH % (UNROLL_FACTOR * 2) == 0);
83
84 let lookup = _mm256_setr_epi8(
85 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3,
86 3, 4,
87 );
88 let low_mask = _mm256_set1_epi8(0x0f);
89 let mut acc = _mm256_setzero_si256();
90 let zero = _mm256_setzero_si256();
91
92 for i in (0..80).step_by(LOADS_PER_FLUSH * 2) {
97 let mut acc_8_low = _mm256_setzero_si256();
98 let mut acc_8_high = _mm256_setzero_si256();
99 for j in (0..LOADS_PER_FLUSH * 2).step_by(UNROLL_FACTOR * 2) {
100 let idx0 = i + j;
101 let idx1 = idx0 + 2;
102
103 unsafe {
107 let x0 = _mm256_xor_si256(
108 _mm256_loadu_si256(lhs.as_ptr().add(idx0).cast()),
109 _mm256_loadu_si256(rhs.as_ptr().add(idx0).cast()),
110 );
111 let x1 = _mm256_xor_si256(
112 _mm256_loadu_si256(lhs.as_ptr().add(idx1).cast()),
113 _mm256_loadu_si256(rhs.as_ptr().add(idx1).cast()),
114 );
115
116 acc_8_low = _mm256_add_epi8(
117 acc_8_low,
118 _mm256_add_epi8(
119 _mm256_shuffle_epi8(lookup, _mm256_and_si256(x0, low_mask)),
120 _mm256_shuffle_epi8(lookup, _mm256_and_si256(x1, low_mask)),
121 ),
122 );
123 acc_8_high = _mm256_add_epi8(
124 acc_8_high,
125 _mm256_add_epi8(
126 _mm256_shuffle_epi8(
127 lookup,
128 _mm256_and_si256(_mm256_srli_epi16(x0, 4), low_mask),
129 ),
130 _mm256_shuffle_epi8(
131 lookup,
132 _mm256_and_si256(_mm256_srli_epi16(x1, 4), low_mask),
133 ),
134 ),
135 );
136 }
137 }
138 acc = _mm256_add_epi64(
139 acc,
140 _mm256_add_epi64(
141 _mm256_sad_epu8(acc_8_low, zero),
142 _mm256_sad_epu8(acc_8_high, zero),
143 ),
144 );
145 }
146
147 let mut results = [0u64; 4];
148 unsafe {
149 _mm256_storeu_si256(results.as_mut_ptr().cast(), acc);
150 }
151 #[allow(clippy::cast_possible_truncation)]
152 let res = (results[0] + results[1] + results[2] + results[3]) as u32;
153 res
154}
155
156#[cfg(all(
157 not(target_arch = "wasm32"),
158 any(target_arch = "x86_64", target_arch = "x86")
159))]
160#[inline]
161pub(crate) fn and_simd_x86(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
162 let mut res = [0u128; 80];
163 for i in 0..80 {
164 res[i] = lhs[i] & rhs[i];
165 }
166 res
167}
168
169#[cfg(all(
170 not(target_arch = "wasm32"),
171 any(target_arch = "x86_64", target_arch = "x86")
172))]
173#[inline]
174pub(crate) fn bind_simd_x86(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
175 let mut res = [0u128; 80];
176 for i in 0..80 {
177 res[i] = lhs[i] ^ rhs[i];
178 }
179 res
180}
181
182#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
183#[inline]
184#[target_feature(enable = "neon")]
185pub(crate) unsafe fn and_simd_neon(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
188 use std::arch::aarch64::{vandq_u8, vld1q_u8, vst1q_u8};
189 let mut res = [0u128; 80];
190 for i in 0..80 {
191 unsafe {
195 let l = vld1q_u8(lhs.as_ptr().add(i).cast());
196 let r = vld1q_u8(rhs.as_ptr().add(i).cast());
197 vst1q_u8(res.as_mut_ptr().add(i).cast(), vandq_u8(l, r));
198 }
199 }
200 res
201}
202
203#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
204#[inline]
205#[target_feature(enable = "neon")]
206pub(crate) unsafe fn bind_simd_neon(lhs: &[u128; 80], rhs: &[u128; 80]) -> [u128; 80] {
209 use std::arch::aarch64::{veorq_u8, vld1q_u8, vst1q_u8};
210 let mut res = [0u128; 80];
211 for i in 0..80 {
212 unsafe {
216 let l = vld1q_u8(lhs.as_ptr().add(i).cast());
217 let r = vld1q_u8(rhs.as_ptr().add(i).cast());
218 vst1q_u8(res.as_mut_ptr().add(i).cast(), veorq_u8(l, r));
219 }
220 }
221 res
222}
223
224#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
225#[inline]
226#[target_feature(enable = "neon")]
227pub(crate) unsafe fn hamming_distance_simd_neon(lhs: &[u128; 80], rhs: &[u128; 80]) -> u32 {
230 use std::arch::aarch64::{
231 vaddlvq_u16, vaddq_u8, vaddq_u16, vcntq_u8, vdupq_n_u8, vdupq_n_u16, veorq_u8, vld1q_u8,
232 vpaddlq_u8,
233 };
234 const BATCH_SIZE: usize = 10;
235 const WORDS_PER_BATCH: usize = BATCH_SIZE * 2;
236 const _: () = assert!(80 % WORDS_PER_BATCH == 0);
238
239 let mut acc = vdupq_n_u16(0);
240
241 for i in (0..80).step_by(WORDS_PER_BATCH) {
242 let mut acc_8 = vdupq_n_u8(0);
249 for j in 0..BATCH_SIZE {
250 let idx = i + j * 2;
251 unsafe {
253 let l0 = vld1q_u8(lhs.as_ptr().add(idx).cast());
254 let r0 = vld1q_u8(rhs.as_ptr().add(idx).cast());
255 let x0 = veorq_u8(l0, r0);
256 let c0 = vcntq_u8(x0);
257 acc_8 = vaddq_u8(acc_8, c0);
258
259 let l1 = vld1q_u8(lhs.as_ptr().add(idx + 1).cast());
260 let r1 = vld1q_u8(rhs.as_ptr().add(idx + 1).cast());
261 let x1 = veorq_u8(l1, r1);
262 let c1 = vcntq_u8(x1);
263 acc_8 = vaddq_u8(acc_8, c1);
264 }
265 }
266 acc = vaddq_u16(acc, vpaddlq_u8(acc_8));
267 }
268 vaddlvq_u16(acc) as u32
269}
270
271#[cfg(test)]
272mod tests {
273 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
274 use super::*;
275
276 #[test]
277 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
278 fn and_simd_avx2_correctness() {
279 if is_x86_feature_detected!("avx2") {
280 let lhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
281 let rhs = [0xAAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAAu128; 80];
282 let res = unsafe { and_simd_avx2(&lhs, &rhs) };
284 for word in &res {
285 assert_eq!(*word, 0);
286 }
287 }
288 }
289
290 #[test]
291 #[cfg(all(
292 not(target_arch = "wasm32"),
293 any(target_arch = "x86_64", target_arch = "x86")
294 ))]
295 fn and_simd_x86_correctness() {
296 let lhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
297 let rhs = [0xAAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAAu128; 80];
298 let res = and_simd_x86(&lhs, &rhs);
299 for word in &res {
300 assert_eq!(*word, 0);
301 }
302 }
303
304 #[test]
305 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
306 fn bind_simd_avx2_correctness() {
307 if is_x86_feature_detected!("avx2") {
308 let lhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
309 let rhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
310 let res = unsafe { bind_simd_avx2(&lhs, &rhs) };
312 for word in &res {
313 assert_eq!(*word, 0);
314 }
315 }
316 }
317
318 #[test]
319 #[cfg(all(
320 not(target_arch = "wasm32"),
321 any(target_arch = "x86_64", target_arch = "x86")
322 ))]
323 fn bind_simd_x86_correctness() {
324 let lhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
325 let rhs = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
326 let res = bind_simd_x86(&lhs, &rhs);
327 for word in &res {
328 assert_eq!(*word, 0);
329 }
330 }
331
332 #[test]
333 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
334 fn hamming_distance_simd_avx2_correctness() {
335 if is_x86_feature_detected!("avx2") {
336 let lhs = [0u128; 80];
337 let rhs = [!0u128; 80];
338 let res = unsafe { hamming_distance_simd_avx2(&lhs, &rhs) };
340 assert_eq!(res, 10240);
341 }
342 }
343
344 #[test]
345 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
346 fn hamming_distance_simd_avx2_edge_cases() {
347 if is_x86_feature_detected!("avx2") {
348 let lhs = [0u128; 80];
349 let mut rhs = [0u128; 80];
350 rhs[0] = 1;
351 rhs[79] = 1 << 127;
352 let res = unsafe { hamming_distance_simd_avx2(&lhs, &rhs) };
354 assert_eq!(res, 2);
355 }
356 }
357
358 #[test]
359 fn hamming_distance_matches_bit_count() {
360 let lhs = [0u128; 80];
361 let mut rhs = [0u128; 80];
362 for i in 0..80 {
363 rhs[i] = i as u128;
364 }
365 let res = hamming_distance_optimized(&lhs, &rhs);
366 let mut expected = 0;
367 for i in 0..80 {
368 expected += (rhs[i]).count_ones();
369 }
370 assert_eq!(res, expected);
371 }
372
373 #[test]
374 fn hamming_distance_optimized_identical_vectors() {
375 let vec = [0x123456789ABCDEF0u128; 80];
376 assert_eq!(hamming_distance_optimized(&vec, &vec), 0);
377 }
378
379 #[test]
380 fn hamming_distance_optimized_complements() {
381 let vec = [0x5555_5555_5555_5555_5555_5555_5555_5555u128; 80];
382 let complement = [0xAAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAAu128; 80];
383 assert_eq!(hamming_distance_optimized(&vec, &complement), 10240);
384 }
385
386 #[test]
387 fn hamming_distance_optimized_correctness() {
388 let mut lhs = [0u128; 80];
389 let mut rhs = [0u128; 80];
390 lhs[0] = 0b1010;
391 rhs[0] = 0b1100;
392 assert_eq!(hamming_distance_optimized(&lhs, &rhs), 2);
394 }
395}