Skip to main content

csm_core_lib/
hyperdim_simd_bundle.rs

1//! SIMD-optimized hypervector bundle operations.
2
3/// AVX2-optimized bit-sliced bundling for a single 256-bit block (2 words).
4#[cfg(all(not(target_arch = "wasm32"), target_arch = "x86_64"))]
5#[inline]
6#[target_feature(enable = "avx2")]
7/// # SAFETY
8/// Caller must ensure AVX2 is supported.
9pub(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        // SAFETY: v.data is [u128; 80]. word_idx + 2 is within bounds.
23        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"))]
46/// # SAFETY
47/// Caller must ensure AVX2 is supported.
48pub(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        // SAFETY: AVX2 is detected at runtime.
57        let res = unsafe { bundle_block_avx2_single(vectors, i, threshold, num_planes) };
58        // SAFETY: out is [u128; 80]. i + 2 is within bounds.
59        unsafe { _mm256_storeu_si256(out.as_mut_ptr().add(i).cast(), res) };
60    }
61    out
62}
63
64/// ARM NEON-optimized bit-sliced bundling for a single 128-bit block (1 word).
65#[cfg(all(not(target_arch = "wasm32"), target_arch = "aarch64"))]
66#[inline]
67#[target_feature(enable = "neon")]
68/// # SAFETY
69/// Caller must ensure NEON is supported.
70pub(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        // SAFETY: v.data is [u128; 80]. word_idx is within bounds.
84        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"))]
108/// # SAFETY
109/// Caller must ensure NEON is supported.
110pub(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        // SAFETY: NEON is supported.
119        let res = unsafe { bundle_block_neon_single(vectors, i, threshold, num_planes) };
120        // SAFETY: out is [u128; 80]. i is within bounds.
121        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}