Skip to main content

simd_kernels/kernels/bitmask/
simd.rs

1// Copyright 2025 Peter Garfield Bower
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # **Bitmask SIMD Kernels** - *Vectorised High-Performance Bitmask Operations*
16//!
17//! SIMD-accelerated implementations of bitmask operations using portable vectorisation with `std::simd`.
18//! These kernels provide optimal performance for large bitmask operations through
19//! SIMD-parallel processing of multiple 64-bit words simultaneously.
20//!
21//! ## Overview
22//!
23//! This module contains vectorised implementations of all bitmask operations.
24//! it uses configurable SIMD lane counts to adapt to different CPU architectures whilst maintaining code portability.
25//!
26//! We do not check for SIMD alignment here because it is guaranteed by the `Bitmask` as it is backed by *Minarrow*'s `Vec64`.
27//!
28//! ## Architecture Principles
29//!
30//! - **Portable SIMD**: Uses `std::simd` for cross-platform vectorisation without target-specific code
31//! - **Configurable lanes**: Lane counts determined at build time for optimal performance per architecture
32//! - **Hybrid processing**: SIMD inner loops with scalar tail handling for non-aligned lengths
33//! - **Low-cost abstraction**: `Bitmask` is a light-weight structure over a `Vec64`. See Minarrow for details and benchmarks
34//! demonstrating very low abstraction cost.
35//!
36//!
37//! ### **Memory Access Patterns**
38//! - Vectorised loads process multiple words per memory operation
39//! - Sequential access patterns optimise cache utilisation
40//! - Aligned access where possible for maximum performance
41//! - Streaming patterns for large bitmask operations
42//!
43//! ## Specialised Algorithms
44//!
45//! ### **Population Count (Popcount)**
46//! Uses SIMD reduction for optimal performance:
47//! ```rust,ignore
48//! let counts = simd_vector.count_ones();
49//! total += counts.reduce_sum() as usize;
50//! ```
51//!
52//! ### **Equality Testing**
53//! Leverages SIMD comparison operations:
54//! ```rust,ignore
55//! let eq_mask = vector_a.simd_eq(vector_b);
56//! if !eq_mask.all() { return false; }
57//! ```
58
59use core::simd::Simd;
60
61use crate::{Bitmask, BitmaskVT};
62use crate::kernels::arithmetic::simd::{W8, W16, W32, W64};
63
64use crate::enums::operators::{LogicalOperator, UnaryOperator};
65use crate::kernels::bitmask::{
66    bitmask_window_bytes, bitmask_window_bytes_mut, clear_trailing_bits, mask_bits_as_words,
67    mask_bits_as_words_mut,
68};
69
70/// Primitive bit ops
71
72/// Performs vectorised bitwise binary operations (AND/OR/XOR) with configurable lane counts.
73///
74/// Core SIMD implementation for logical operations between bitmask windows. Processes data using
75/// vectorised instructions with automatic scalar tail handling for optimal performance across
76/// different data sizes and architectures.
77///
78/// # Type Parameters
79/// - `LANES`: Number of u64 lanes to process simultaneously (typically 8, 16, 32, or 64)
80///
81/// # Parameters
82/// - `lhs`: Left-hand side bitmask window as `(mask, offset, length)` tuple
83/// - `rhs`: Right-hand side bitmask window as `(mask, offset, length)` tuple
84/// - `op`: Logical operation to perform (AND, OR, XOR)
85///
86/// # Returns
87/// A new `Bitmask` containing the vectorised operation results with proper trailing bit handling.
88///
89/// # Performance Characteristics
90/// - Vectorised inner loop processes `LANES` words per iteration
91/// - Scalar tail handling ensures correctness for non-aligned lengths
92/// - Memory access patterns optimised for cache efficiency
93/// - Lane count scaling provides architecture-specific optimisation
94#[inline(always)]
95pub fn bitmask_binop_simd<const LANES: usize>(
96    lhs: BitmaskVT<'_>,
97    rhs: BitmaskVT<'_>,
98    op: LogicalOperator,
99) -> Bitmask
100where
101{
102    let (lhs_mask, lhs_off, len) = lhs;
103    let (rhs_mask, rhs_off, _) = rhs;
104    if len == 0 {
105        return Bitmask::new_set_all(0, false);
106    }
107    let mut out = Bitmask::new_set_all(len, false);
108    let nw = (len + 63) / 64;
109    unsafe {
110        let lp = mask_bits_as_words(bitmask_window_bytes(lhs_mask, lhs_off, len));
111        let rp = mask_bits_as_words(bitmask_window_bytes(rhs_mask, rhs_off, len));
112        let dp = mask_bits_as_words_mut(bitmask_window_bytes_mut(&mut out, 0, len));
113        let mut i = 0;
114        while i + LANES <= nw {
115            let a = Simd::<u64, LANES>::from_slice(std::slice::from_raw_parts(lp.add(i), LANES));
116            let b = Simd::<u64, LANES>::from_slice(std::slice::from_raw_parts(rp.add(i), LANES));
117            let r = match op {
118                LogicalOperator::And => a & b,
119                LogicalOperator::Or => a | b,
120                LogicalOperator::Xor => a ^ b,
121            };
122            std::ptr::copy_nonoverlapping(r.as_array().as_ptr(), dp.add(i), LANES);
123            i += LANES;
124        }
125        // Tail often caused by `n % LANES != 0`; uses scalar fallback.
126        for k in i..nw {
127            let a = *lp.add(k);
128            let b = *rp.add(k);
129            *dp.add(k) = match op {
130                LogicalOperator::And => a & b,
131                LogicalOperator::Or => a | b,
132                LogicalOperator::Xor => a ^ b,
133            };
134        }
135    }
136    out.len = len;
137    clear_trailing_bits(&mut out);
138    out
139}
140
141/// Performs vectorised bitwise unary operations (NOT) with configurable lane counts.
142///
143/// Core SIMD implementation for unary logical operations on bitmask windows. Processes data using
144/// vectorised instructions with automatic scalar tail handling for optimal performance across
145/// different data sizes and CPU architectures.
146///
147/// # Type Parameters
148/// - `LANES`: Number of u64 lanes to process simultaneously (typically 8, 16, 32, or 64)
149///
150/// # Parameters
151/// - `src`: Source bitmask window as `(mask, offset, length)` tuple
152/// - `op`: Unary operation to perform (currently only NOT supported)
153///
154/// # Returns
155/// A new `Bitmask` containing the vectorised operation results with proper trailing bit handling.
156///
157/// # Implementation Details
158/// - Vectorised inner loop processes `LANES` words per iteration using SIMD NOT operations
159/// - Scalar tail handling ensures correctness for non-aligned lengths
160/// - Memory access patterns optimised for cache efficiency and sequential processing
161/// - Lane count scaling provides architecture-specific optimisation for different CPU capabilities
162///
163/// # Performance Characteristics
164/// - Memory bandwidth: Vectorised loads/stores improve memory subsystem utilisation
165/// - Instruction throughput: Reduced total instruction count for large operations
166/// - Pipeline efficiency: Better utilisation of modern CPU execution units
167/// - Cache locality: Sequential access patterns optimise cache utilisation
168#[inline(always)]
169pub fn bitmask_unop_simd<const LANES: usize>(src: BitmaskVT<'_>, op: UnaryOperator) -> Bitmask
170where
171{
172    let (mask, offset, len) = src;
173    if len == 0 {
174        return Bitmask::new_set_all(0, false);
175    }
176    let mut out = Bitmask::new_set_all(len, false);
177    let nw = (len + 63) / 64;
178    unsafe {
179        let sp = mask_bits_as_words(bitmask_window_bytes(mask, offset, len));
180        let dp = mask_bits_as_words_mut(bitmask_window_bytes_mut(&mut out, 0, len));
181        let mut i = 0;
182        while i + LANES <= nw {
183            let a = Simd::<u64, LANES>::from_slice(std::slice::from_raw_parts(sp.add(i), LANES));
184            let r = match op {
185                UnaryOperator::Not => !a,
186                _ => unreachable!(),
187            };
188            std::ptr::copy_nonoverlapping(r.as_array().as_ptr(), dp.add(i), LANES);
189            i += LANES;
190        }
191        // Tail often caused by `n % LANES != 0`; uses scalar fallback.
192        for k in i..nw {
193            let a = *sp.add(k);
194            *dp.add(k) = match op {
195                UnaryOperator::Not => !a,
196                _ => unreachable!(),
197            };
198        }
199    }
200    out.len = len;
201    clear_trailing_bits(&mut out);
202    out
203}
204
205// ---- Entry points ----
206/// Performs vectorised bitwise AND operation between two bitmask windows.
207///
208/// High-performance SIMD implementation of logical AND using configurable lane counts for optimal
209/// CPU architecture utilisation. Delegates to the core `bitmask_binop_simd` implementation with
210/// the AND operator.
211///
212/// # Type Parameters
213/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
214///
215/// # Parameters
216/// - `lhs`: Left-hand side bitmask window as `(mask, offset, length)` tuple
217/// - `rhs`: Right-hand side bitmask window as `(mask, offset, length)` tuple
218///
219/// # Returns
220/// A new `Bitmask` containing bitwise AND results with proper trailing bit masking.
221///
222/// # Usage Example
223/// ```rust,ignore
224/// use simd_kernels::kernels::bitmask::simd::and_masks_simd;
225///
226/// // Process 8 lanes simultaneously (512 bits per instruction)
227/// let result = and_masks_simd::<8>(lhs_window, rhs_window);
228/// ```
229#[inline(always)]
230pub fn and_masks_simd<const LANES: usize>(lhs: BitmaskVT<'_>, rhs: BitmaskVT<'_>) -> Bitmask
231where
232{
233    bitmask_binop_simd::<LANES>(lhs, rhs, LogicalOperator::And)
234}
235
236/// Performs vectorised bitwise OR operation between two bitmask windows.
237///
238/// High-performance SIMD implementation of logical OR using configurable lane counts for optimal
239/// CPU architecture utilisation. Delegates to the core `bitmask_binop_simd` implementation with
240/// the OR operator.
241///
242/// # Type Parameters
243/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
244///
245/// # Parameters
246/// - `lhs`: Left-hand side bitmask window as `(mask, offset, length)` tuple
247/// - `rhs`: Right-hand side bitmask window as `(mask, offset, length)` tuple
248///
249/// # Returns
250/// A new `Bitmask` containing bitwise OR results with proper trailing bit masking.
251///
252/// # Usage Example
253/// ```rust,ignore
254/// use simd_kernels::kernels::bitmask::simd::or_masks_simd;
255///
256/// // Process 16 lanes simultaneously (1024 bits per instruction)
257/// let result = or_masks_simd::<16>(lhs_window, rhs_window);
258/// ```
259#[inline(always)]
260pub fn or_masks_simd<const LANES: usize>(lhs: BitmaskVT<'_>, rhs: BitmaskVT<'_>) -> Bitmask
261where
262{
263    bitmask_binop_simd::<LANES>(lhs, rhs, LogicalOperator::Or)
264}
265
266/// Performs vectorised bitwise XOR operation between two bitmask windows.
267///
268/// High-performance SIMD implementation of logical exclusive-OR using configurable lane counts
269/// for optimal CPU architecture utilisation. Delegates to the core `bitmask_binop_simd`
270/// implementation with the XOR operator.
271///
272/// # Type Parameters
273/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
274///
275/// # Parameters
276/// - `lhs`: Left-hand side bitmask window as `(mask, offset, length)` tuple
277/// - `rhs`: Right-hand side bitmask window as `(mask, offset, length)` tuple
278///
279/// # Returns
280/// A new `Bitmask` containing bitwise XOR results with proper trailing bit masking.
281///
282/// # Usage Example
283/// ```rust,ignore
284/// use simd_kernels::kernels::bitmask::simd::xor_masks_simd;
285///
286/// // Process 32 lanes simultaneously (2048 bits per instruction)
287/// let result = xor_masks_simd::<32>(lhs_window, rhs_window);
288/// ```
289#[inline(always)]
290pub fn xor_masks_simd<const LANES: usize>(lhs: BitmaskVT<'_>, rhs: BitmaskVT<'_>) -> Bitmask
291where
292{
293    bitmask_binop_simd::<LANES>(lhs, rhs, LogicalOperator::Xor)
294}
295
296/// Performs vectorised bitwise NOT operation on a bitmask window.
297///
298/// High-performance SIMD implementation of logical NOT using configurable lane counts for optimal
299/// CPU architecture utilisation. Delegates to the core `bitmask_unop_simd` implementation with
300/// the NOT operator.
301///
302/// # Type Parameters
303/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
304///
305/// # Parameters
306/// - `src`: Source bitmask window as `(mask, offset, length)` tuple
307///
308/// # Returns
309/// A new `Bitmask` containing bitwise NOT results with proper trailing bit masking.
310///
311/// # Usage Example
312/// ```rust,ignore
313/// use simd_kernels::kernels::bitmask::simd::not_mask_simd;
314///
315/// // Process 8 lanes simultaneously (512 bits per instruction)
316/// let inverted = not_mask_simd::<8>(source_window);
317/// ```
318#[inline(always)]
319pub fn not_mask_simd<const LANES: usize>(src: BitmaskVT<'_>) -> Bitmask
320where
321{
322    bitmask_unop_simd::<LANES>(src, UnaryOperator::Not)
323}
324
325/// Bitwise "in" for boolean bitmasks: each output bit is true if lhs bit is in the set of bits in rhs.
326#[inline]
327pub fn in_mask_simd<const LANES: usize>(lhs: BitmaskVT<'_>, rhs: BitmaskVT<'_>) -> Bitmask
328where
329{
330    let (lhs_mask, lhs_off, len) = lhs;
331    let (rhs_mask, rhs_off, rlen) = rhs;
332    debug_assert_eq!(len, rlen, "in_mask: window length mismatch");
333
334    if len == 0 {
335        return Bitmask::new_set_all(0, false);
336    }
337
338    // Check which boolean values are present in rhs using word-level ops.
339    // Trailing bits in the last word must be masked off to avoid false positives.
340    let n_words = (len + 63) / 64;
341    let trailing = len & 63;
342    let mut any_set = 0u64;
343    let mut any_unset = 0u64;
344    unsafe {
345        let rp = rhs_mask.bits.as_ptr().cast::<u64>().add(rhs_off / 64);
346        for k in 0..n_words {
347            let mut w = *rp.add(k);
348            if k == n_words - 1 && trailing != 0 {
349                let valid_mask = (1u64 << trailing) - 1;
350                w &= valid_mask;
351                any_set |= w;
352                any_unset |= (!w) & valid_mask;
353            } else {
354                any_set |= w;
355                any_unset |= !w;
356            }
357            if any_set != 0 && any_unset != 0 {
358                break;
359            }
360        }
361    }
362    let has_true = any_set != 0;
363    let has_false = any_unset != 0;
364
365    match (has_true, has_false) {
366        // Set contains both values: every bit is a member
367        (true, true) => Bitmask::new_set_all(len, true),
368        // Only true in rhs: output bit is set iff lhs bit is true
369        (true, false) => lhs_mask.slice_clone(lhs_off, len),
370        // Only false in rhs: output bit is set iff lhs bit is false
371        (false, true) => not_mask_simd::<LANES>((lhs_mask, lhs_off, len)),
372        // Empty set: no bits are members
373        (false, false) => Bitmask::new_set_all(len, false),
374    }
375}
376
377/// Performs vectorised bitwise "not in" membership test for boolean bitmasks.
378///
379/// Computes the logical complement of the "in" operation where each output bit is true if the
380/// corresponding lhs bit is NOT in the set of bits defined by rhs. This function delegates to
381/// `in_mask_simd` followed by `not_mask_simd` for optimal performance.
382///
383/// # Type Parameters
384/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
385///
386/// # Parameters
387/// - `lhs`: Left-hand side bitmask window as `(mask, offset, length)` tuple (test values)
388/// - `rhs`: Right-hand side bitmask window as `(mask, offset, length)` tuple (set definition)
389///
390/// # Returns
391/// A new `Bitmask` where each bit is true if the corresponding lhs bit is not in the rhs set.
392#[inline]
393pub fn not_in_mask_simd<const LANES: usize>(lhs: BitmaskVT<'_>, rhs: BitmaskVT<'_>) -> Bitmask
394where
395{
396    let mask = in_mask_simd::<LANES>(lhs, rhs);
397    not_mask_simd::<LANES>((&mask, 0, mask.len))
398}
399
400/// Produces a bitmask where each output bit is 1 iff the corresponding bits of `a` and `b` are equal.
401#[inline]
402pub fn eq_mask_simd<const LANES: usize>(a: BitmaskVT<'_>, b: BitmaskVT<'_>) -> Bitmask
403where
404{
405    let (am, ao, len) = a;
406    let (bm, bo, blen) = b;
407    debug_assert_eq!(len, blen, "BitWindow length mismatch in eq_bits_mask");
408    if len == 0 {
409        return Bitmask::new_set_all(0, true);
410    }
411    if ao % 64 != 0 || bo % 64 != 0 {
412        panic!(
413            "eq_bits_mask: offsets must be 64-bit aligned (got a: {}, b: {})",
414            ao, bo
415        );
416    }
417    let n_words = (len + 63) / 64;
418    let mut out = Bitmask::new_set_all(len, false);
419
420    unsafe {
421        let ap = am.bits.as_ptr().cast::<u64>().add(ao / 64);
422        let bp = bm.bits.as_ptr().cast::<u64>().add(bo / 64);
423        let dp = out.bits.as_mut_ptr().cast::<u64>();
424        let aw = std::slice::from_raw_parts(ap, n_words);
425        let bw = std::slice::from_raw_parts(bp, n_words);
426
427        #[cfg(feature = "simd")]
428        {
429            let mut i = 0;
430            while i + LANES <= n_words {
431                let sa = Simd::<u64, LANES>::from_slice(&aw[i..i + LANES]);
432                let sb = Simd::<u64, LANES>::from_slice(&bw[i..i + LANES]);
433                let eq = !(sa ^ sb);
434                std::ptr::copy_nonoverlapping(eq.as_array().as_ptr(), dp.add(i), LANES);
435                i += LANES;
436            }
437            for k in i..n_words {
438                *dp.add(k) = !(aw[k] ^ bw[k]);
439            }
440        }
441        #[cfg(not(feature = "simd"))]
442        {
443            for k in 0..n_words {
444                *dp.add(k) = !(aw[k] ^ bw[k]);
445            }
446        }
447    }
448    out.mask_trailing_bits();
449    out
450}
451
452/// Performs vectorised bitwise inequality comparison between two bitmask windows.
453///
454/// Computes the logical complement of equality where each output bit is true if the corresponding
455/// bits from the two input windows are different. This function delegates to `eq_mask_simd`
456/// followed by bitwise NOT for optimal performance.
457///
458/// # Type Parameters
459/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised operations
460///
461/// # Parameters
462/// - `a`: First bitmask window as `(mask, offset, length)` tuple
463/// - `b`: Second bitmask window as `(mask, offset, length)` tuple
464///
465/// # Returns
466/// A new `Bitmask` where each bit is true if the corresponding input bits are different.
467#[inline]
468pub fn ne_mask_simd<const LANES: usize>(a: BitmaskVT<'_>, b: BitmaskVT<'_>) -> Bitmask
469where
470{
471    !eq_mask_simd::<LANES>(a, b)
472}
473
474/// Tests if all corresponding bits between two bitmask windows are different.
475///
476/// Performs bulk inequality comparison across entire bitmask windows by computing the logical
477/// complement of `all_eq_mask_simd`. Returns true only if every corresponding bit pair differs
478/// between the two input windows.
479///
480/// # Type Parameters
481/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised comparison
482///
483/// # Parameters
484/// - `a`: First bitmask window as `(mask, offset, length)` tuple
485/// - `b`: Second bitmask window as `(mask, offset, length)` tuple
486///
487/// # Returns
488/// `true` if all corresponding bits are different, `false` if any bits are equal.
489#[inline]
490pub fn all_ne_mask_simd<const LANES: usize>(a: BitmaskVT<'_>, b: BitmaskVT<'_>) -> bool
491where
492{
493    !all_eq_mask_simd::<LANES>(a, b)
494}
495
496/// Vectorised equality test across entire bitmask windows with early termination.
497///
498/// Performs bulk equality comparison between two bitmask windows using SIMD comparison operations.
499/// Processes multiple words simultaneously and terminates early when differences are detected.
500///
501/// # Type Parameters
502/// - `LANES`: Number of u64 lanes to process simultaneously for vectorised comparison
503///
504/// # Parameters
505/// - `a`: First bitmask window as `(mask, offset, length)` tuple
506/// - `b`: Second bitmask window as `(mask, offset, length)` tuple
507///
508/// # Returns
509/// `true` if all corresponding bits are equal (ignoring slack bits), `false` otherwise.
510#[inline]
511pub fn all_eq_mask_simd<const LANES: usize>(a: BitmaskVT<'_>, b: BitmaskVT<'_>) -> bool
512where
513{
514    let (am, ao, len) = a;
515    let (bm, bo, blen) = b;
516    debug_assert_eq!(len, blen, "BitWindow length mismatch in all_eq_mask");
517
518    if len == 0 {
519        return true;
520    }
521
522    // Short masks: single word comparison with trailing bit mask
523    if len < 64 {
524        let wa = unsafe { am.word_unchecked(ao / 64) };
525        let wb = unsafe { bm.word_unchecked(bo / 64) };
526        let valid_mask = (1u64 << len) - 1;
527        return (wa & valid_mask) == (wb & valid_mask);
528    }
529
530    if ao % 64 != 0 || bo % 64 != 0 {
531        panic!(
532            "all_eq_mask_simd: offsets must be 64-bit aligned (got a: {}, b: {})",
533            ao, bo
534        );
535    }
536    let n_words = (len + 63) / 64;
537    let trailing = len & 63;
538
539    unsafe {
540        let aw = std::slice::from_raw_parts(am.bits.as_ptr().cast::<u64>().add(ao / 64), n_words);
541        let bw = std::slice::from_raw_parts(bm.bits.as_ptr().cast::<u64>().add(bo / 64), n_words);
542
543        #[cfg(feature = "simd")]
544        {
545            use std::simd::prelude::SimdPartialEq;
546            let mut i = 0;
547            while i + LANES <= n_words {
548                let sa = Simd::<u64, LANES>::from_slice(&aw[i..i + LANES]);
549                let sb = Simd::<u64, LANES>::from_slice(&bw[i..i + LANES]);
550                if !sa.simd_eq(sb).all() {
551                    return false;
552                }
553                i += LANES;
554            }
555            for k in i..n_words {
556                if k == n_words - 1 && trailing != 0 {
557                    let mask = (1u64 << trailing) - 1;
558                    if (aw[k] & mask) != (bw[k] & mask) {
559                        return false;
560                    }
561                } else if aw[k] != bw[k] {
562                    return false;
563                }
564            }
565        }
566        #[cfg(not(feature = "simd"))]
567        {
568            for k in 0..n_words {
569                if k == n_words - 1 && trailing != 0 {
570                    let mask = (1u64 << trailing) - 1;
571                    if (aw[k] & mask) != (bw[k] & mask) {
572                        return false;
573                    }
574                } else if aw[k] != bw[k] {
575                    return false;
576                }
577            }
578        }
579    }
580    true
581}
582
583/// Vectorised population count with SIMD reduction.
584///
585/// Counts set bits in a bitmask window using SIMD popcount with horizontal reduction.
586///
587/// # Type Parameters
588/// - `LANES`: Number of u64 lanes to process simultaneously
589///
590/// # Parameters
591/// - `m`: Bitmask window as `(mask, offset, length)` tuple
592///
593/// # Returns
594/// The total count of set bits in the specified window.
595#[inline]
596pub fn popcount_mask_simd<const LANES: usize>(m: BitmaskVT<'_>) -> usize
597where
598{
599    let (mask, offset, len) = m;
600    if len == 0 {
601        return 0;
602    }
603    let n_words = (len + 63) / 64;
604    let word_start = offset / 64;
605    let mut acc = 0usize;
606
607    unsafe {
608        let words = std::slice::from_raw_parts(
609            mask.bits.as_ptr().cast::<u64>().add(word_start),
610            n_words,
611        );
612
613        #[cfg(feature = "simd")]
614        {
615            use std::simd::prelude::SimdUint;
616            let mut i = 0;
617            while i + LANES <= n_words {
618                let v = Simd::<u64, LANES>::from_slice(&words[i..i + LANES]);
619                acc += v.count_ones().reduce_sum() as usize;
620                i += LANES;
621            }
622            for k in i..n_words {
623                if k == n_words - 1 && len % 64 != 0 {
624                    let slack_mask = (1u64 << (len % 64)) - 1;
625                    acc += (words[k] & slack_mask).count_ones() as usize;
626                } else {
627                    acc += words[k].count_ones() as usize;
628                }
629            }
630        }
631        #[cfg(not(feature = "simd"))]
632        {
633            for k in 0..n_words {
634                if k == n_words - 1 && len % 64 != 0 {
635                    let slack_mask = (1u64 << (len % 64)) - 1;
636                    acc += (words[k] & slack_mask).count_ones() as usize;
637                } else {
638                    acc += words[k].count_ones() as usize;
639                }
640            }
641        }
642    }
643    acc
644}
645
646/// Returns true if all bits in the mask are set (1).
647#[inline]
648pub fn all_true_mask_simd<const LANES: usize>(mask: &Bitmask) -> bool
649where
650{
651    if mask.len == 0 {
652        return true;
653    }
654    // Short masks: single word comparison
655    if mask.len < 64 {
656        let w = unsafe { mask.word_unchecked(0) };
657        let valid_mask = (1u64 << mask.len) - 1;
658        return (w & valid_mask) == valid_mask;
659    }
660    let n_bits = mask.len;
661    let n_words = (n_bits + 63) / 64;
662    let words: &[u64] =
663        unsafe { std::slice::from_raw_parts(mask.bits.as_ptr() as *const u64, n_words) };
664
665    let simd_chunks = n_words / LANES;
666
667    let all_ones = Simd::<u64, LANES>::splat(!0u64);
668    for chunk in 0..simd_chunks {
669        let base = chunk * LANES;
670        let arr = Simd::<u64, LANES>::from_slice(&words[base..base + LANES]);
671        if arr != all_ones {
672            return false;
673        }
674    }
675    // Tail often caused by `n % LANES =! 0`; uses scalar fallback
676    let tail_words = n_words % LANES;
677    let base = simd_chunks * LANES;
678    for k in 0..tail_words {
679        if base + k == n_words - 1 && n_bits % 64 != 0 {
680            let valid_bits = n_bits % 64;
681            let slack_mask = (1u64 << valid_bits) - 1;
682            if words[base + k] != slack_mask {
683                return false;
684            }
685        } else {
686            if words[base + k] != !0u64 {
687                return false;
688            }
689        }
690    }
691    true
692}
693
694/// Returns true if all bits in the mask are set to (0).
695pub fn all_false_mask_simd<const LANES: usize>(mask: &Bitmask) -> bool
696where
697{
698    if mask.len == 0 {
699        return true;
700    }
701    // Short masks: single word comparison
702    if mask.len < 64 {
703        let w = unsafe { mask.word_unchecked(0) };
704        let valid_mask = (1u64 << mask.len) - 1;
705        return (w & valid_mask) == 0;
706    }
707    let n_bits = mask.len;
708    let n_words = (n_bits + 63) / 64;
709    let words: &[u64] =
710        unsafe { std::slice::from_raw_parts(mask.bits.as_ptr() as *const u64, n_words) };
711
712    let simd_chunks = n_words / LANES;
713    for chunk in 0..simd_chunks {
714        let base = chunk * LANES;
715        let arr = Simd::<u64, LANES>::from_slice(&words[base..base + LANES]);
716        if arr != Simd::<u64, LANES>::splat(0u64) {
717            return false;
718        }
719    }
720    let tail_words = n_words % LANES;
721    let base = simd_chunks * LANES;
722    for k in 0..tail_words {
723        if base + k == n_words - 1 && n_bits % 64 != 0 {
724            let valid_bits = n_bits % 64;
725            let slack_mask = (1u64 << valid_bits) - 1;
726            if words[base + k] & slack_mask != 0 {
727                return false;
728            }
729        } else {
730            if words[base + k] != 0u64 {
731                return false;
732            }
733        }
734    }
735    true
736}
737
738
739/// Generates a SIMD equality mask function for a given element type and lane count.
740/// Processes LANES elements per iteration, with a scalar tail for the remainder.
741macro_rules! impl_simd_eq_mask {
742    ($fn_name:ident, $t:ty, $lanes:expr) => {
743        pub fn $fn_name(data: &[$t], field_mask: $t, target: $t) -> Bitmask {
744            use vec64::Vec64;
745            use std::simd::cmp::SimdPartialEq;
746            let n = data.len();
747            let n_bytes = (n + 7) / 8;
748            let mut bytes = Vec64::<u8>::with_capacity(n_bytes);
749            bytes.resize(n_bytes, 0);
750
751            let mask_vec = Simd::<$t, $lanes>::splat(field_mask);
752            let target_vec = Simd::<$t, $lanes>::splat(target);
753
754            let chunks = n / $lanes;
755            for i in 0..chunks {
756                let d = Simd::<$t, $lanes>::from_slice(&data[i * $lanes..]);
757                let masked = d & mask_vec;
758                let cmp = masked.simd_eq(target_vec);
759                let bits = cmp.to_bitmask() as u64;
760                let bit_pos = i * $lanes;
761                let byte_idx = bit_pos / 8;
762                let bit_shift = bit_pos % 8;
763                // Write result bits. For LANES >= 8 bit_shift is always 0
764                // since LANES is a power of 2. For LANES < 8, sub-byte
765                // results OR into position within the byte.
766                let shifted = bits << bit_shift;
767                for b in 0..(($lanes + 7) / 8) {
768                    bytes[byte_idx + b] |= (shifted >> (b * 8)) as u8;
769                }
770            }
771
772            // Scalar tail
773            let start = chunks * $lanes;
774            for j in start..n {
775                if (data[j] & field_mask) == target {
776                    bytes[j / 8] |= 1 << (j % 8);
777                }
778            }
779
780            Bitmask::new(bytes, n)
781        }
782    };
783}
784
785impl_simd_eq_mask!(simd_eq_mask_u8, u8, W8);
786impl_simd_eq_mask!(simd_eq_mask_u16, u16, W16);
787impl_simd_eq_mask!(simd_eq_mask_u32, u32, W32);
788impl_simd_eq_mask!(simd_eq_mask_u64, u64, W64);
789
790
791#[cfg(test)]
792mod tests {
793    use crate::{Bitmask, BitmaskVT};
794
795    use super::*;
796
797    macro_rules! simd_bitmask_suite {
798        ($mod_name:ident, $lanes:expr) => {
799            mod $mod_name {
800                use super::*;
801                const LANES: usize = $lanes;
802
803                fn bm(bits: &[bool]) -> Bitmask {
804                    let mut m = Bitmask::new_set_all(bits.len(), false);
805                    for (i, b) in bits.iter().enumerate() {
806                        if *b {
807                            m.set(i, true);
808                        }
809                    }
810                    m
811                }
812                fn slice(mask: &Bitmask) -> BitmaskVT<'_> {
813                    (mask, 0, mask.len)
814                }
815
816                #[test]
817                fn test_and_masks_simd() {
818                    let a = bm(&[true, false, true, false, true, true, false, false]);
819                    let b = bm(&[true, true, false, false, true, false, true, false]);
820                    let c = and_masks_simd::<LANES>(slice(&a), slice(&b));
821                    for i in 0..a.len {
822                        assert_eq!(c.get(i), a.get(i) & b.get(i), "bit {i}");
823                    }
824                }
825
826                #[test]
827                fn test_or_masks_simd() {
828                    let a = bm(&[true, false, true, false, true, true, false, false]);
829                    let b = bm(&[true, true, false, false, true, false, true, false]);
830                    let c = or_masks_simd::<LANES>(slice(&a), slice(&b));
831                    for i in 0..a.len {
832                        assert_eq!(c.get(i), a.get(i) | b.get(i), "bit {i}");
833                    }
834                }
835
836                #[test]
837                fn test_xor_masks_simd() {
838                    let a = bm(&[true, false, true, false, true, true, false, false]);
839                    let b = bm(&[true, true, false, false, true, false, true, false]);
840                    let c = xor_masks_simd::<LANES>(slice(&a), slice(&b));
841                    for i in 0..a.len {
842                        assert_eq!(c.get(i), a.get(i) ^ b.get(i), "bit {i}");
843                    }
844                }
845
846                #[test]
847                fn test_not_mask_simd() {
848                    let a = bm(&[true, false, true, false]);
849                    let c = not_mask_simd::<LANES>(slice(&a));
850                    for i in 0..a.len {
851                        assert_eq!(c.get(i), !a.get(i));
852                    }
853                }
854
855                #[test]
856                fn test_in_mask_simd_variants() {
857                    let lhs = bm(&[true, false, true, false]);
858                    // RHS = [true]: only 'true' in rhs
859                    let rhs_true = bm(&[true; 4]);
860                    let out = in_mask_simd::<LANES>(slice(&lhs), slice(&rhs_true));
861                    for i in 0..lhs.len {
862                        assert_eq!(out.get(i), lhs.get(i), "in_mask, only true, bit {i}");
863                    }
864                    // RHS = [false]: only 'false' in rhs
865                    let rhs_false = bm(&[false; 4]);
866                    let out = in_mask_simd::<LANES>(slice(&lhs), slice(&rhs_false));
867                    for i in 0..lhs.len {
868                        assert_eq!(out.get(i), !lhs.get(i), "in_mask, only false, bit {i}");
869                    }
870                    // RHS = [true, false]: both present
871                    let rhs_both = bm(&[true, false, true, false]);
872                    let out = in_mask_simd::<LANES>(slice(&lhs), slice(&rhs_both));
873                    for i in 0..lhs.len {
874                        assert!(out.get(i), "in_mask, both true/false, bit {i}");
875                    }
876                    // RHS empty
877                    let rhs_empty = bm(&[false; 0]);
878                    let out = in_mask_simd::<LANES>((&lhs, 0, 0), (&rhs_empty, 0, 0));
879                    assert_eq!(out.len, 0);
880                }
881
882                #[test]
883                fn test_not_in_mask_simd() {
884                    let lhs = bm(&[true, false, true, false]);
885                    let rhs = bm(&[true, false, true, false]);
886                    let in_mask = in_mask_simd::<LANES>(slice(&lhs), slice(&rhs));
887                    let not_in = not_in_mask_simd::<LANES>(slice(&lhs), slice(&rhs));
888                    for i in 0..lhs.len {
889                        assert_eq!(not_in.get(i), !in_mask.get(i));
890                    }
891                }
892
893                #[test]
894                fn test_eq_mask_simd_and_ne_mask_simd() {
895                    let a = bm(&[true, false, true, false]);
896                    let b = bm(&[true, false, false, true]);
897                    let eq = eq_mask_simd::<LANES>(slice(&a), slice(&b));
898                    let ne = ne_mask_simd::<LANES>(slice(&a), slice(&b));
899                    for i in 0..a.len {
900                        assert_eq!(eq.get(i), a.get(i) == b.get(i), "eq_mask bit {i}");
901                        assert_eq!(ne.get(i), a.get(i) != b.get(i), "ne_mask bit {i}");
902                    }
903                }
904
905                #[test]
906                fn test_all_eq_mask_simd() {
907                    let a = bm(&[true, false, true, false, true, true, false, false]);
908                    let b = bm(&[true, false, true, false, true, true, false, false]);
909                    assert!(all_eq_mask_simd::<LANES>(slice(&a), slice(&b)));
910                    let mut b2 = b.clone();
911                    b2.set(0, false);
912                    assert!(!all_eq_mask_simd::<LANES>(slice(&a), slice(&b2)));
913                }
914
915                #[test]
916                fn test_all_ne_mask_simd() {
917                    let a = bm(&[true, false, true]);
918                    let b = bm(&[false, true, false]);
919                    assert!(all_ne_mask_simd::<LANES>(slice(&a), slice(&b)));
920                    assert!(!all_ne_mask_simd::<LANES>(slice(&a), slice(&a)));
921                }
922
923                #[test]
924                fn test_popcount_mask_simd() {
925                    let a = bm(&[true, false, true, false, true, false, false, true]);
926                    let pop = popcount_mask_simd::<LANES>(slice(&a));
927                    assert_eq!(pop, 4);
928                }
929
930                #[test]
931                fn test_all_true_mask_simd_and_false() {
932                    let all_true = Bitmask::new_set_all(64 * LANES, true);
933                    assert!(all_true_mask_simd::<LANES>(&all_true));
934                    let mut not_true = all_true.clone();
935                    not_true.set(3, false);
936                    assert!(!all_true_mask_simd::<LANES>(&not_true));
937                }
938
939                #[test]
940                fn test_all_false_mask_simd() {
941                    let all_true = Bitmask::new_set_all(64 * LANES, true);
942                    assert!(!all_false_mask_simd::<LANES>(&all_true));
943                    let all_false = Bitmask::new_set_all(64 * LANES, false);
944                    assert!(all_false_mask_simd::<LANES>(&all_false));
945                }
946            }
947        };
948    }
949
950    include!(concat!(env!("OUT_DIR"), "/simd_lanes.rs"));
951
952    simd_bitmask_suite!(simd_bitmask_w8, W8);
953    simd_bitmask_suite!(simd_bitmask_w16, W16);
954    simd_bitmask_suite!(simd_bitmask_w32, W32);
955    simd_bitmask_suite!(simd_bitmask_w64, W64);
956}