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>(¬_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}