csm_core_lib/
hyperdim_simd_bundle.rs1#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
5#[inline]
6#[target_feature(enable = "avx2")]
7pub(crate) unsafe fn bundle_block_avx2_single(
10 vectors: &[crate::hyperdim::HVec10240],
11 word_idx: usize,
12 threshold: usize,
13 num_planes: usize,
14) -> std::arch::x86_64::__m256i {
15 use std::arch::x86_64::{
16 _mm256_and_si256, _mm256_andnot_si256, _mm256_loadu_si256, _mm256_or_si256,
17 _mm256_set1_epi64x, _mm256_setzero_si256, _mm256_testz_si256, _mm256_xor_si256,
18 };
19
20 let mut planes = [_mm256_setzero_si256(); 64];
21 for v in vectors {
22 let mut carry = unsafe { _mm256_loadu_si256(v.data.as_ptr().add(word_idx).cast()) };
24 for plane in planes.iter_mut().take(num_planes) {
25 let next_carry = _mm256_and_si256(*plane, carry);
26 *plane = _mm256_xor_si256(*plane, carry);
27 carry = next_carry;
28 if _mm256_testz_si256(carry, carry) != 0 {
29 break;
30 }
31 }
32 }
33 let (mut current_eq, mut current_gt) = (_mm256_set1_epi64x(-1), _mm256_setzero_si256());
34 for p in (0..num_planes).rev() {
35 if ((threshold >> p) & 1) == 1 {
36 current_eq = _mm256_and_si256(current_eq, planes[p]);
37 } else {
38 current_gt = _mm256_or_si256(current_gt, _mm256_and_si256(current_eq, planes[p]));
39 current_eq = _mm256_andnot_si256(planes[p], current_eq);
40 }
41 }
42 _mm256_or_si256(current_gt, current_eq)
43}
44
45#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
46pub(crate) unsafe fn bundle_block_avx2(
49 vectors: &[crate::hyperdim::HVec10240],
50 threshold: usize,
51 num_planes: usize,
52) -> [u128; 80] {
53 use std::arch::x86_64::_mm256_storeu_si256;
54 let mut out = [0u128; 80];
55 for i in (0..80).step_by(2) {
56 let res = unsafe { bundle_block_avx2_single(vectors, i, threshold, num_planes) };
58 unsafe { _mm256_storeu_si256(out.as_mut_ptr().add(i).cast(), res) };
60 }
61 out
62}
63
64#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
66#[inline]
67#[target_feature(enable = "neon")]
68pub(crate) unsafe fn bundle_block_neon_single(
71 vectors: &[crate::hyperdim::HVec10240],
72 word_idx: usize,
73 threshold: usize,
74 num_planes: usize,
75) -> std::arch::aarch64::uint8x16_t {
76 use std::arch::aarch64::{
77 vandq_u8, vbicq_u8, vdupq_n_u8, veorq_u8, vgetq_lane_u64, vld1q_u8, vorrq_u8,
78 vreinterpretq_u64_u8,
79 };
80
81 let mut planes = [vdupq_n_u8(0); 64];
82 for v in vectors {
83 let mut carry = unsafe { vld1q_u8(v.data.as_ptr().add(word_idx).cast()) };
85 for plane in planes.iter_mut().take(num_planes) {
86 let next_carry = vandq_u8(*plane, carry);
87 *plane = veorq_u8(*plane, carry);
88 carry = next_carry;
89 let c64 = vreinterpretq_u64_u8(carry);
90 if vgetq_lane_u64(c64, 0) == 0 && vgetq_lane_u64(c64, 1) == 0 {
91 break;
92 }
93 }
94 }
95 let (mut current_eq, mut current_gt) = (vdupq_n_u8(0xFF), vdupq_n_u8(0));
96 for p in (0..num_planes).rev() {
97 if ((threshold >> p) & 1) == 1 {
98 current_eq = vandq_u8(current_eq, planes[p]);
99 } else {
100 current_gt = vorrq_u8(current_gt, vandq_u8(current_eq, planes[p]));
101 current_eq = vbicq_u8(current_eq, planes[p]);
102 }
103 }
104 vorrq_u8(current_gt, current_eq)
105}
106
107#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
108pub(crate) unsafe fn bundle_block_neon(
111 vectors: &[crate::hyperdim::HVec10240],
112 threshold: usize,
113 num_planes: usize,
114) -> [u128; 80] {
115 use std::arch::aarch64::vst1q_u8;
116 let mut out = [0u128; 80];
117 for i in 0..80 {
118 let res = unsafe { bundle_block_neon_single(vectors, i, threshold, num_planes) };
120 unsafe { vst1q_u8(out.as_mut_ptr().add(i).cast(), res) };
122 }
123 out
124}
125
126#[cfg(test)]
127mod tests {
128 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
129 use super::*;
130 #[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
131 #[test]
132 fn bundle_block_avx2_correctness() {
133 if std::arch::is_x86_feature_detected!("avx2") {
134 use crate::hyperdim::HVec10240;
135 let vectors: Vec<HVec10240> = (0..10u64).map(HVec10240::new_seeded).collect();
136 let threshold = vectors.len() / 2 + 1;
137 let num_planes = (usize::BITS - vectors.len().leading_zeros()) as usize;
138 let simd_res = unsafe { bundle_block_avx2(&vectors, threshold, num_planes) };
139 let mut expected = [0u128; 80];
140 for i in 0..80 {
141 let mut planes = [0u128; 64];
142 for v in &vectors {
143 let mut carry = v.data[i];
144 for p in 0..num_planes {
145 let next_carry = planes[p] & carry;
146 planes[p] ^= carry;
147 carry = next_carry;
148 if carry == 0 {
149 break;
150 }
151 }
152 }
153 let (mut current_eq, mut current_gt) = (!0u128, 0u128);
154 for p in (0..num_planes).rev() {
155 if ((threshold >> p) & 1) == 1 {
156 current_eq &= planes[p];
157 } else {
158 current_gt |= current_eq & planes[p];
159 current_eq &= !planes[p];
160 }
161 }
162 expected[i] = current_gt | current_eq;
163 }
164 assert_eq!(simd_res, expected);
165 }
166 }
167
168 #[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
169 #[test]
170 fn bundle_block_neon_correctness() {
171 use crate::hyperdim::HVec10240;
172 let vectors: Vec<HVec10240> = (0..10u64).map(HVec10240::new_seeded).collect();
173 let threshold = vectors.len() / 2 + 1;
174 let num_planes = (usize::BITS - vectors.len().leading_zeros()) as usize;
175 let simd_res = unsafe { bundle_block_neon(&vectors, threshold, num_planes) };
176 let mut expected = [0u128; 80];
177 for i in 0..80 {
178 let mut planes = [0u128; 64];
179 for v in &vectors {
180 let mut carry = v.data[i];
181 for p in 0..num_planes {
182 let next_carry = planes[p] & carry;
183 planes[p] ^= carry;
184 carry = next_carry;
185 if carry == 0 {
186 break;
187 }
188 }
189 }
190 let (mut current_eq, mut current_gt) = (!0u128, 0u128);
191 for p in (0..num_planes).rev() {
192 if ((threshold >> p) & 1) == 1 {
193 current_eq &= planes[p];
194 } else {
195 current_gt |= current_eq & planes[p];
196 current_eq &= !planes[p];
197 }
198 }
199 expected[i] = current_gt | current_eq;
200 }
201 assert_eq!(simd_res, expected);
202 }
203}