Skip to main content

vortex_buffer/bit/
buf.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::convert::Infallible;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::fmt::Result as FmtResult;
8use std::ops::BitAnd;
9use std::ops::BitOr;
10use std::ops::BitXor;
11use std::ops::Not;
12use std::ops::RangeBounds;
13
14use crate::Alignment;
15use crate::BitBufferMeta;
16use crate::BitBufferMut;
17use crate::Buffer;
18use crate::BufferAllocatorRef;
19use crate::BufferMut;
20use crate::ByteBuffer;
21use crate::bit::BitChunks;
22use crate::bit::BitIndexIterator;
23use crate::bit::BitIterator;
24use crate::bit::BitSliceIterator;
25use crate::bit::UnalignedBitChunk;
26use crate::bit::collect_bool_word;
27use crate::bit::count_ones::count_ones;
28use crate::bit::get_bit_unchecked;
29use crate::bit::ops::bitwise_binary_op;
30use crate::bit::ops::bitwise_binary_op_lhs_owned;
31use crate::bit::ops::bitwise_unary_op;
32use crate::bit::ops::bitwise_unary_op_copy;
33use crate::bit::select::bit_select;
34
35/// An immutable bitset stored as a packed byte buffer.
36#[derive(Debug, Clone, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct BitBuffer {
39    buffer: ByteBuffer,
40    /// Represents the offset of the bit buffer into the first byte.
41    ///
42    /// This is always less than 8 (for when the bit buffer is not aligned to a byte).
43    offset: usize,
44    len: usize,
45}
46
47const LIMIT_LEN: usize = 16;
48impl Display for BitBuffer {
49    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
50        let limit = f.precision().unwrap_or(LIMIT_LEN);
51        let buf: Vec<bool> = self.into_iter().take(limit).collect();
52        f.debug_struct("BitBuffer")
53            .field("len", &self.len)
54            .field("buffer", &buf)
55            .finish()
56    }
57}
58
59impl PartialEq for BitBuffer {
60    fn eq(&self, other: &Self) -> bool {
61        if self.len != other.len {
62            return false;
63        }
64
65        if self.len == 0 {
66            return true;
67        }
68
69        // Fast path: both byte-aligned and same length — direct byte comparison.
70        if self.offset == 0 && other.offset == 0 {
71            let full_bytes = self.len / 8;
72            let self_bytes = &self.buffer.as_slice()[..full_bytes];
73            let other_bytes = &other.buffer.as_slice()[..full_bytes];
74            if self_bytes != other_bytes {
75                return false;
76            }
77            // Compare remaining bits in the last partial byte.
78            let rem = self.len % 8;
79            if rem != 0 {
80                let mask = (1u8 << rem) - 1;
81                let a = self.buffer.as_slice()[full_bytes] & mask;
82                let b = other.buffer.as_slice()[full_bytes] & mask;
83                return a == b;
84            }
85            return true;
86        }
87
88        self.chunks()
89            .iter_padded()
90            .zip(other.chunks().iter_padded())
91            .all(|(a, b)| a == b)
92    }
93}
94
95impl BitBuffer {
96    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view.
97    ///
98    /// Panics if the buffer is not large enough to hold `len` bits.
99    #[inline]
100    pub fn new(buffer: ByteBuffer, len: usize) -> Self {
101        assert!(
102            buffer.len() * 8 >= len,
103            "provided ByteBuffer not large enough to back BoolBuffer with len {len}"
104        );
105
106        // BitBuffers make no assumptions on byte alignment, so we strip any alignment.
107        let buffer = buffer.aligned(Alignment::none());
108
109        Self {
110            buffer,
111            len,
112            offset: 0,
113        }
114    }
115
116    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view, starting at
117    /// the given `offset` (in bits).
118    ///
119    /// Panics if the buffer is not large enough to hold `len` bits after the offset.
120    #[inline]
121    pub fn new_with_offset(buffer: ByteBuffer, len: usize, offset: usize) -> Self {
122        assert!(
123            len.saturating_add(offset) <= buffer.len().saturating_mul(8),
124            "provided ByteBuffer (len={}) not large enough to back BoolBuffer with offset {offset} len {len}",
125            buffer.len()
126        );
127
128        // BitBuffers make no assumptions on byte alignment, so we strip any alignment.
129        let buffer = buffer.aligned(Alignment::none());
130
131        // Slice the buffer to ensure the offset is within the first byte
132        let byte_offset = offset / 8;
133        let offset = offset % 8;
134        let buffer = if byte_offset != 0 {
135            buffer.slice(byte_offset..)
136        } else {
137            buffer
138        };
139
140        Self {
141            buffer,
142            offset,
143            len,
144        }
145    }
146
147    /// Create a new `BoolBuffer` of length `len` where all bits are set (true).
148    #[inline]
149    pub fn new_set(len: usize) -> Self {
150        Self::new_set_in(len, BufferAllocatorRef::statically_allocated())
151    }
152
153    /// Create a set bit buffer with the provided allocator.
154    #[inline]
155    pub fn new_set_in(len: usize, allocator: BufferAllocatorRef) -> Self {
156        let words = len.div_ceil(8);
157        let buffer = Buffer::full_in(0xFF, words, allocator);
158
159        Self {
160            buffer,
161            len,
162            offset: 0,
163        }
164    }
165
166    /// Create a new `BoolBuffer` of length `len` where all bits are unset (false).
167    #[inline]
168    pub fn new_unset(len: usize) -> Self {
169        Self::new_unset_in(len, BufferAllocatorRef::statically_allocated())
170    }
171
172    /// Create an unset bit buffer with the provided allocator.
173    #[inline]
174    pub fn new_unset_in(len: usize, allocator: BufferAllocatorRef) -> Self {
175        let words = len.div_ceil(8);
176        let buffer = Buffer::zeroed_in(words, allocator);
177
178        Self {
179            buffer,
180            len,
181            offset: 0,
182        }
183    }
184
185    /// Create a bit buffer of `len` with `indices` set as true.
186    pub fn from_indices(len: usize, indices: impl IntoIterator<Item = usize>) -> BitBuffer {
187        BitBufferMut::from_indices(len, indices).freeze()
188    }
189
190    /// Create a new empty `BitBuffer`.
191    #[inline]
192    pub fn empty() -> Self {
193        Self::new_set(0)
194    }
195
196    /// Create a new `BitBuffer` of length `len` where all bits are set to `value`.
197    #[inline]
198    pub fn full(value: bool, len: usize) -> Self {
199        Self::full_in(value, len, BufferAllocatorRef::statically_allocated())
200    }
201
202    /// Create a full bit buffer with the provided allocator.
203    #[inline]
204    pub fn full_in(value: bool, len: usize, allocator: BufferAllocatorRef) -> Self {
205        if value {
206            Self::new_set_in(len, allocator)
207        } else {
208            Self::new_unset_in(len, allocator)
209        }
210    }
211
212    /// Collects `len` Boolean values from `f` into a packed [`BitBuffer`].
213    ///
214    /// Calls `f` exactly once for each index in `0..len`, in order.
215    ///
216    /// # Code generation
217    ///
218    /// `collect_bool_words` calls `collect_bool_words_inline`, which selects a `pack_bool_word_*`
219    /// kernel at compile time and passes it to `collect_bool_words_with`. That shared word loop
220    /// materializes each full 64-value chunk as a byte-per-value `[bool; 64]`, then passes it to the
221    /// selected kernel. For simple predicates, LLVM vectorizes the loop and removes the physical
222    /// stack array. On AVX-512, it still combines the comparison masks, expands the result into 64
223    /// `0` or `1` bytes with `vpbroadcastq` and `vmovdqu8`, then recreates the mask with `vptestmb`.
224    /// [LLVM issue #219235](https://github.com/llvm/llvm-project/issues/219235) tracks replacing
225    /// that round trip with a direct `kmovq` store. The conversion is per chunk. This method does
226    /// not create a full-column byte buffer.
227    ///
228    /// # Performance
229    ///
230    /// `collect_bool_words_inline` and `collect_bool_words_with` can inline into the caller, so LLVM
231    /// sees `f`, the fill loop, and the packing kernel together. A retained bounds check inside `f`
232    /// can prevent vectorization. A caller that proves `len <= values.len()` can use
233    /// `unsafe { *values.get_unchecked(i) }` because this method only passes indices in `0..len`.
234    ///
235    /// Use this method for general predicates. Use [`Self::collect_bool_multiversioned`] only for
236    /// the specialized predicates described there.
237    #[inline]
238    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
239        BitBufferMut::collect_bool(len, f).freeze()
240    }
241
242    /// Collects predicate results with the provided allocator.
243    #[inline]
244    pub fn collect_bool_in<F: FnMut(usize) -> bool>(
245        len: usize,
246        f: F,
247        allocator: BufferAllocatorRef,
248    ) -> Self {
249        BitBufferMut::collect_bool_in(len, f, allocator).freeze()
250    }
251
252    /// Collects Boolean values with a fill-and-pack loop selected for the current CPU.
253    ///
254    /// This has the same callback contract as [`Self::collect_bool`]. On x86-64,
255    /// `collect_bool_words_multiversioned` selects `collect_bool_words_avx512`,
256    /// `collect_bool_words_avx2`, or `collect_bool_words_inline` at runtime. Each wider version is a
257    /// `#[target_feature]` function, so Rust cannot inline it into a caller compiled without those
258    /// features.
259    ///
260    /// Use this method only for a small, bounds-check-free predicate whose wider loop has been
261    /// benchmarked. Use [`Self::collect_bool`] for general predicates.
262    #[inline]
263    pub fn collect_bool_multiversioned<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
264        Self::collect_bool_multiversioned_in(len, f, BufferAllocatorRef::statically_allocated())
265    }
266
267    /// Collects multiversioned predicate results with the provided allocator.
268    #[inline]
269    pub fn collect_bool_multiversioned_in<F: FnMut(usize) -> bool>(
270        len: usize,
271        f: F,
272        allocator: BufferAllocatorRef,
273    ) -> Self {
274        BitBufferMut::collect_bool_multiversioned_in(len, f, allocator).freeze()
275    }
276
277    /// Maps over each bit in this buffer, calling `f(index, bit_value)` and collecting results.
278    ///
279    /// This is more efficient than `collect_bool` when you need to read the current bit value,
280    /// as it unpacks each u64 chunk only once rather than doing random access for each bit.
281    pub fn map_cmp<F>(&self, mut f: F) -> Self
282    where
283        F: FnMut(usize, bool) -> bool,
284    {
285        let len = self.len;
286        let mut buffer: BufferMut<u64> = BufferMut::with_capacity(len.div_ceil(64));
287
288        let chunks_count = len / 64;
289        let remainder = len % 64;
290        let chunks = self.chunks();
291
292        for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
293            let packed = collect_bool_word(64, |bit_idx| {
294                let i = bit_idx + chunk_idx * 64;
295                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
296                f(i, bit_value)
297            });
298
299            // SAFETY: Already allocated sufficient capacity
300            unsafe { buffer.push_unchecked(packed) }
301        }
302
303        if remainder != 0 {
304            let src_chunk = chunks.remainder_bits();
305            let packed = collect_bool_word(remainder, |bit_idx| {
306                let i = bit_idx + chunks_count * 64;
307                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
308                f(i, bit_value)
309            });
310
311            // SAFETY: Already allocated sufficient capacity
312            unsafe { buffer.push_unchecked(packed) }
313        }
314
315        let mut bytes = buffer.into_byte_buffer();
316        bytes.truncate(len.div_ceil(8));
317
318        Self {
319            buffer: bytes.freeze(),
320            offset: 0,
321            len,
322        }
323    }
324
325    /// Clear all bits in the buffer, preserving existing capacity.
326    #[inline]
327    pub fn clear(&mut self) {
328        self.buffer.clear();
329        self.len = 0;
330        self.offset = 0;
331    }
332
333    /// Get the logical length of this `BoolBuffer`.
334    ///
335    /// This may differ from the physical length of the backing buffer, for example if it was
336    /// created using the `new_with_offset` constructor, or if it was sliced.
337    #[inline]
338    pub fn len(&self) -> usize {
339        self.len
340    }
341
342    /// Returns `true` if the `BoolBuffer` is empty.
343    #[inline]
344    pub fn is_empty(&self) -> bool {
345        self.len() == 0
346    }
347
348    /// Offset of the start of the buffer in bits.
349    #[allow(clippy::inline_always)]
350    #[inline(always)]
351    pub fn offset(&self) -> usize {
352        self.offset
353    }
354
355    /// Get a reference to the underlying buffer.
356    #[allow(clippy::inline_always)]
357    #[inline(always)]
358    pub fn inner(&self) -> &ByteBuffer {
359        &self.buffer
360    }
361
362    /// Return the backing bytes for this bit buffer when its logical offset is byte-aligned.
363    ///
364    /// The returned slice contains exactly `self.len().div_ceil(8)` bytes. Bits past the logical
365    /// length in the final byte are outside the buffer's logical range and should be ignored by
366    /// callers.
367    #[inline]
368    pub fn byte_aligned_bytes(&self) -> Option<&[u8]> {
369        if !self.offset.is_multiple_of(8) {
370            return None;
371        }
372
373        let n_bytes = self.len.div_ceil(8);
374        let start = self.offset / 8;
375        let end = start + n_bytes;
376        Some(&self.buffer.as_slice()[start..end])
377    }
378
379    /// Retrieve the value at the given index.
380    ///
381    /// Panics if the index is out of bounds.
382    ///
383    /// Please note for repeatedly calling this function, please prefer [`crate::get_bit`].
384    #[inline]
385    pub fn value(&self, index: usize) -> bool {
386        assert!(index < self.len);
387        unsafe { self.value_unchecked(index) }
388    }
389
390    /// Retrieve the value at the given index without bounds checking
391    ///
392    /// # SAFETY
393    /// Caller must ensure that index is within the range of the buffer
394    #[inline]
395    pub unsafe fn value_unchecked(&self, index: usize) -> bool {
396        unsafe { get_bit_unchecked(self.buffer.as_ptr(), index + self.offset) }
397    }
398
399    /// Create a new zero-copy slice of this BoolBuffer that begins at the `start` index and extends
400    /// for `len` bits.
401    ///
402    /// Panics if the slice would extend beyond the end of the buffer.
403    #[inline]
404    pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
405        let (byte_offset, meta) = BitBufferMeta::new(self.offset, self.len).slice(range);
406
407        // Trim whole bytes off the front directly rather than going through `new_with_offset`,
408        // which would slice (and re-clone) the clone we'd have to pass it.
409        let buffer = if byte_offset != 0 {
410            self.buffer.slice_unaligned(byte_offset..)
411        } else {
412            self.buffer.clone().aligned(Alignment::none())
413        };
414
415        Self {
416            buffer,
417            offset: meta.offset(),
418            len: meta.len(),
419        }
420    }
421
422    /// Slice any full bytes from the buffer, leaving the offset < 8.
423    pub fn shrink_offset(self) -> Self {
424        let word_start = self.offset / 8;
425        let word_end = (self.offset + self.len).div_ceil(8);
426
427        let buffer = self.buffer.slice(word_start..word_end);
428
429        let bit_offset = self.offset % 8;
430        let len = self.len;
431        BitBuffer::new_with_offset(buffer, len, bit_offset)
432    }
433
434    /// Access chunks of the buffer aligned to 8 byte boundary as [prefix, \<full chunks\>, suffix]
435    #[inline]
436    pub fn unaligned_chunks(&self) -> UnalignedBitChunk<'_> {
437        UnalignedBitChunk::new(self.buffer.as_slice(), self.offset, self.len)
438    }
439
440    /// Access chunks of the underlying buffer as 8 byte chunks with a final trailer
441    ///
442    /// If you're performing operations on a single buffer, prefer [BitBuffer::unaligned_chunks]
443    #[inline]
444    pub fn chunks(&self) -> BitChunks<'_> {
445        BitChunks::new(self.buffer.as_slice(), self.offset, self.len)
446    }
447
448    /// Get the number of set bits in the buffer.
449    #[inline]
450    pub fn true_count(&self) -> usize {
451        count_ones(self.buffer.as_slice(), self.offset, self.len)
452    }
453
454    /// Get the number of set bits in the bit range `[start, end)`.
455    ///
456    /// Unlike `self.slice(start..end).true_count()`, this counts directly over the
457    /// existing backing buffer without allocating or cloning a new [`BitBuffer`],
458    /// making it cheap to call repeatedly over many small ranges.
459    ///
460    /// Panics if `start > end` or `end > len`.
461    #[inline]
462    pub fn count_range(&self, start: usize, end: usize) -> usize {
463        assert!(start <= end, "start {start} exceeds end {end}");
464        assert!(end <= self.len, "end {end} exceeds len {}", self.len);
465        count_ones(self.buffer.as_slice(), self.offset + start, end - start)
466    }
467
468    /// Returns the position of the `nth` set bit (0-indexed).
469    ///
470    /// This is the "select" operation on a bitmap: given a rank `nth`, find
471    /// which logical bit position holds that rank.
472    ///
473    /// Returns `None` if `nth` is greater than or equal to the number of set bits.
474    #[inline]
475    pub fn select(&self, nth: usize) -> Option<usize> {
476        bit_select(self.buffer.as_slice(), self.offset, self.len, nth)
477    }
478
479    /// Returns the index of the last set bit, or `None` if every bit is unset.
480    ///
481    /// This scans from the end a word at a time, avoiding the full forward scan required by
482    /// [`Self::select`] when selecting the final set bit.
483    #[inline]
484    pub fn last_set_index(&self) -> Option<usize> {
485        let chunks = self.unaligned_chunks();
486        let lead = chunks.lead_padding();
487        let prefix_words = usize::from(chunks.prefix().is_some());
488
489        if let Some(word) = chunks.suffix()
490            && word != 0
491        {
492            let word_index = prefix_words + chunks.chunks().len();
493            return Some(word_index * 64 + 63 - word.leading_zeros() as usize - lead);
494        }
495
496        for (index, &word) in chunks.chunks().iter().enumerate().rev() {
497            if word != 0 {
498                let word_index = prefix_words + index;
499                return Some(word_index * 64 + 63 - word.leading_zeros() as usize - lead);
500            }
501        }
502
503        chunks.prefix().filter(|word| *word != 0).map(|word| {
504            debug_assert!(word.trailing_zeros() as usize >= lead);
505            63 - word.leading_zeros() as usize - lead
506        })
507    }
508
509    /// Get the number of unset bits in the buffer.
510    #[inline]
511    pub fn false_count(&self) -> usize {
512        self.len - self.true_count()
513    }
514
515    /// Iterator over bits in the buffer
516    #[inline]
517    pub fn iter(&self) -> BitIterator<'_> {
518        BitIterator::new(self.buffer.as_slice(), self.offset, self.len)
519    }
520
521    /// Iterator over set indices of the underlying buffer
522    #[inline]
523    pub fn set_indices(&self) -> BitIndexIterator<'_> {
524        BitIndexIterator::new(self.buffer.as_slice(), self.offset, self.len)
525    }
526
527    /// Iterator over set slices of the underlying buffer
528    #[inline]
529    pub fn set_slices(&self) -> BitSliceIterator<'_> {
530        BitSliceIterator::new(self.buffer.as_slice(), self.offset, self.len)
531    }
532
533    /// Invoke `f(index)` for every set bit, in ascending order, processing a `u64`
534    /// word at a time.
535    ///
536    /// This is the fast way to "do something for each set bit": it skips all-zero
537    /// words, fast-paths all-one words, and walks the remaining bits with
538    /// `trailing_zeros`. Prefer it over `for i in 0..len { if buf.value(i) { f(i) } }`
539    /// (which pays a branch per element) and over collecting [`Self::set_indices`]
540    /// (whose per-`next` iterator state does not inline as well).
541    #[inline]
542    pub fn for_each_set_index<F: FnMut(usize)>(&self, mut f: F) {
543        let Ok(()) = self.try_for_each_set_index(|index| {
544            f(index);
545            Ok::<_, Infallible>(())
546        });
547    }
548
549    /// Fallible variant of [`for_each_set_index`](Self::for_each_set_index).
550    ///
551    /// Stops and returns the first error from `f`.
552    #[inline]
553    pub fn try_for_each_set_index<E, F>(&self, mut f: F) -> Result<(), E>
554    where
555        F: FnMut(usize) -> Result<(), E>,
556    {
557        let mut base = 0usize;
558        for word in self.chunks().iter_padded() {
559            if word == u64::MAX {
560                for k in 0..64 {
561                    f(base + k)?;
562                }
563            } else {
564                let mut w = word;
565                while w != 0 {
566                    f(base + w.trailing_zeros() as usize)?;
567                    w &= w - 1;
568                }
569            }
570            base += 64;
571        }
572
573        Ok(())
574    }
575
576    /// Created a new BitBuffer with offset reset to 0
577    pub fn sliced(&self) -> Self {
578        if self.offset.is_multiple_of(8) {
579            return Self::new(
580                self.buffer
581                    .slice(self.offset / 8..(self.offset + self.len).div_ceil(8)),
582                self.len,
583            );
584        }
585
586        // Allocate directly rather than clone + identity op which would fail try_into_mut.
587        bitwise_unary_op_copy(self, |a| a)
588    }
589}
590
591// Conversions
592
593impl BitBuffer {
594    /// Returns the offset, len and underlying buffer.
595    #[inline]
596    pub fn into_inner(self) -> (usize, usize, ByteBuffer) {
597        (self.offset, self.len, self.buffer)
598    }
599
600    /// Attempt to convert this `BitBuffer` into a mutable version.
601    #[inline]
602    pub fn try_into_mut(self) -> Result<BitBufferMut, Self> {
603        match self.buffer.try_into_mut() {
604            Ok(buffer) => Ok(BitBufferMut::from_buffer(buffer, self.offset, self.len)),
605            Err(buffer) => Err(BitBuffer::new_with_offset(buffer, self.len, self.offset)),
606        }
607    }
608}
609
610impl From<&[bool]> for BitBuffer {
611    fn from(value: &[bool]) -> Self {
612        BitBufferMut::from(value).freeze()
613    }
614}
615
616impl From<Vec<bool>> for BitBuffer {
617    fn from(value: Vec<bool>) -> Self {
618        BitBufferMut::from(value).freeze()
619    }
620}
621
622impl FromIterator<bool> for BitBuffer {
623    #[inline]
624    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
625        BitBufferMut::from_iter(iter).freeze()
626    }
627}
628
629impl BitOr for BitBuffer {
630    type Output = Self;
631
632    #[inline]
633    fn bitor(self, rhs: Self) -> Self::Output {
634        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a | b)
635    }
636}
637
638impl BitOr for &BitBuffer {
639    type Output = BitBuffer;
640
641    #[inline]
642    fn bitor(self, rhs: Self) -> Self::Output {
643        bitwise_binary_op(self, rhs, |a, b| a | b)
644    }
645}
646
647impl BitOr<&BitBuffer> for BitBuffer {
648    type Output = BitBuffer;
649
650    #[inline]
651    fn bitor(self, rhs: &BitBuffer) -> Self::Output {
652        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a | b)
653    }
654}
655
656impl BitAnd for &BitBuffer {
657    type Output = BitBuffer;
658
659    #[inline]
660    fn bitand(self, rhs: Self) -> Self::Output {
661        bitwise_binary_op(self, rhs, |a, b| a & b)
662    }
663}
664
665impl BitAnd<BitBuffer> for &BitBuffer {
666    type Output = BitBuffer;
667
668    #[inline]
669    fn bitand(self, rhs: BitBuffer) -> Self::Output {
670        self.bitand(&rhs)
671    }
672}
673
674impl BitAnd<&BitBuffer> for BitBuffer {
675    type Output = BitBuffer;
676
677    #[inline]
678    fn bitand(self, rhs: &BitBuffer) -> Self::Output {
679        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & b)
680    }
681}
682
683impl BitAnd<BitBuffer> for BitBuffer {
684    type Output = BitBuffer;
685
686    #[inline]
687    fn bitand(self, rhs: BitBuffer) -> Self::Output {
688        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a & b)
689    }
690}
691
692impl Not for &BitBuffer {
693    type Output = BitBuffer;
694
695    #[inline]
696    fn not(self) -> Self::Output {
697        // Allocate directly rather than clone+try_into_mut, which always fails
698        // since the clone shares the Arc with the original reference.
699        bitwise_unary_op_copy(self, |a| !a)
700    }
701}
702
703impl Not for BitBuffer {
704    type Output = BitBuffer;
705
706    #[inline]
707    fn not(self) -> Self::Output {
708        bitwise_unary_op(self, |a| !a)
709    }
710}
711
712impl BitXor for &BitBuffer {
713    type Output = BitBuffer;
714
715    #[inline]
716    fn bitxor(self, rhs: Self) -> Self::Output {
717        bitwise_binary_op(self, rhs, |a, b| a ^ b)
718    }
719}
720
721impl BitXor<&BitBuffer> for BitBuffer {
722    type Output = BitBuffer;
723
724    #[inline]
725    fn bitxor(self, rhs: &BitBuffer) -> Self::Output {
726        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a ^ b)
727    }
728}
729
730impl BitBuffer {
731    /// Create a new BitBuffer by performing a bitwise AND NOT operation between two BitBuffers.
732    ///
733    /// This operation is sufficiently common that we provide a dedicated method for it avoid
734    /// making two passes over the data.
735    pub fn bitand_not(&self, rhs: &BitBuffer) -> BitBuffer {
736        bitwise_binary_op(self, rhs, |a, b| a & !b)
737    }
738
739    /// Owned variant of [`bitand_not`](Self::bitand_not) that can mutate in-place when possible.
740    pub fn into_bitand_not(self, rhs: &BitBuffer) -> BitBuffer {
741        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & !b)
742    }
743
744    /// Iterate through bits in a buffer.
745    ///
746    /// # Arguments
747    ///
748    /// * `f` - Callback function taking (bit_index, is_set)
749    ///
750    /// # Panics
751    ///
752    /// Panics if the range is outside valid bounds of the buffer.
753    #[inline]
754    pub fn iter_bits<F>(&self, mut f: F)
755    where
756        F: FnMut(usize, bool),
757    {
758        let total_bits = self.len;
759        if total_bits == 0 {
760            return;
761        }
762
763        // Process in 64-bit chunks for better ILP and fewer loop iterations.
764        let chunks = self.chunks();
765        let chunks_count = total_bits / 64;
766        let remainder = total_bits % 64;
767
768        for (chunk_idx, chunk) in chunks.iter().enumerate() {
769            let base = chunk_idx * 64;
770            for bit_idx in 0..64 {
771                f(base + bit_idx, (chunk >> bit_idx) & 1 == 1);
772            }
773        }
774
775        if remainder != 0 {
776            let rem_chunk = chunks.remainder_bits();
777            let base = chunks_count * 64;
778            for bit_idx in 0..remainder {
779                f(base + bit_idx, (rem_chunk >> bit_idx) & 1 == 1);
780            }
781        }
782    }
783}
784
785impl<'a> IntoIterator for &'a BitBuffer {
786    type Item = bool;
787    type IntoIter = BitIterator<'a>;
788
789    fn into_iter(self) -> Self::IntoIter {
790        self.iter()
791    }
792}
793
794#[cfg(test)]
795mod tests {
796    use rstest::rstest;
797
798    use crate::ByteBuffer;
799    use crate::bit::BitBuffer;
800    use crate::buffer;
801
802    #[test]
803    fn test_bool() {
804        // Create a new Buffer<u64> of length 1024 where the 8th bit is set.
805        let buffer: ByteBuffer = buffer![1 << 7; 1024];
806        let bools = BitBuffer::new(buffer, 1024 * 8);
807
808        // sanity checks
809        assert_eq!(bools.len(), 1024 * 8);
810        assert!(!bools.is_empty());
811        assert_eq!(bools.true_count(), 1024);
812        assert_eq!(bools.false_count(), 1024 * 7);
813
814        // Check all the values
815        for word in 0..1024 {
816            for bit in 0..8 {
817                if bit == 7 {
818                    assert!(bools.value(word * 8 + bit));
819                } else {
820                    assert!(!bools.value(word * 8 + bit));
821                }
822            }
823        }
824
825        // Slice the buffer to create a new subset view.
826        let sliced = bools.slice(64..72);
827
828        // sanity checks
829        assert_eq!(sliced.len(), 8);
830        assert!(!sliced.is_empty());
831        assert_eq!(sliced.true_count(), 1);
832        assert_eq!(sliced.false_count(), 7);
833
834        // Check all of the values like before
835        for bit in 0..8 {
836            if bit == 7 {
837                assert!(sliced.value(bit));
838            } else {
839                assert!(!sliced.value(bit));
840            }
841        }
842    }
843
844    #[test]
845    fn test_padded_equaltiy() {
846        let buf1 = BitBuffer::new_set(64); // All bits set.
847        let buf2 = BitBuffer::collect_bool(64, |x| x < 32); // First half set, other half unset.
848
849        for i in 0..32 {
850            assert_eq!(buf1.value(i), buf2.value(i), "Bit {} should be the same", i);
851        }
852
853        for i in 32..64 {
854            assert_ne!(buf1.value(i), buf2.value(i), "Bit {} should differ", i);
855        }
856
857        assert_eq!(
858            buf1.slice(0..32),
859            buf2.slice(0..32),
860            "Buffer slices with same bits should be equal (`PartialEq` needs `iter_padded()`)"
861        );
862        assert_ne!(
863            buf1.slice(32..64),
864            buf2.slice(32..64),
865            "Buffer slices with different bits should not be equal (`PartialEq` needs `iter_padded()`)"
866        );
867    }
868
869    #[test]
870    fn test_slice_offset_calculation() {
871        let buf = BitBuffer::collect_bool(16, |_| true);
872        let sliced = buf.slice(10..16);
873        assert_eq!(sliced.len(), 6);
874        // Ensure the offset is modulo 8
875        assert_eq!(sliced.offset(), 2);
876    }
877
878    #[test]
879    fn test_byte_aligned_bytes() {
880        let bytes: ByteBuffer = buffer![0b1010_0101u8, 0b0000_0011];
881        let buf = BitBuffer::new(bytes.clone(), 10);
882        assert_eq!(buf.byte_aligned_bytes(), Some(bytes.as_slice()));
883
884        let byte_sliced = buf.slice(8..10);
885        assert_eq!(byte_sliced.byte_aligned_bytes(), Some(&[0b0000_0011][..]));
886
887        let bit_sliced = buf.slice(1..9);
888        assert!(bit_sliced.byte_aligned_bytes().is_none());
889    }
890
891    #[test]
892    fn test_from_indices_dense_crosses_words() {
893        let len = 130;
894        let indices = (0..len).filter(|idx| idx % 3 != 1);
895        let buf = BitBuffer::from_indices(len, indices);
896
897        assert_eq!(buf.len(), len);
898        for idx in 0..len {
899            assert_eq!(buf.value(idx), idx % 3 != 1, "mismatch at {idx}");
900        }
901    }
902
903    #[test]
904    #[should_panic(expected = "index 5 exceeds len 5")]
905    fn test_from_indices_out_of_bounds() {
906        BitBuffer::from_indices(5, [0, 5]);
907    }
908
909    #[rstest]
910    #[case(0, 0, None)]
911    #[case(3, 7, None)]
912    #[case(0, 1, Some(0))]
913    #[case(8, 64, Some(63))]
914    #[case(13, 65, Some(0))]
915    #[case(13, 65, Some(64))]
916    #[case(67, 151, Some(97))]
917    #[case(67, 151, Some(150))]
918    fn last_set_index_handles_offsets_and_padding(
919        #[case] offset: usize,
920        #[case] len: usize,
921        #[case] expected: Option<usize>,
922    ) {
923        let backing = BitBuffer::from_iter(
924            std::iter::repeat_n(true, offset)
925                .chain((0..len).map(|index| Some(index) == expected))
926                .chain(std::iter::repeat_n(true, 7)),
927        );
928        let buffer = BitBuffer::new_with_offset(backing.inner().clone(), len, offset);
929
930        assert_eq!(buffer.last_set_index(), expected);
931    }
932
933    #[rstest]
934    #[case(5)]
935    #[case(8)]
936    #[case(10)]
937    #[case(13)]
938    #[case(16)]
939    #[case(23)]
940    #[case(100)]
941    fn test_iter_bits(#[case] len: usize) {
942        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
943
944        let mut collected = Vec::new();
945        buf.iter_bits(|idx, is_set| {
946            collected.push((idx, is_set));
947        });
948
949        assert_eq!(collected.len(), len);
950
951        for (idx, is_set) in collected {
952            assert_eq!(is_set, idx % 2 == 0);
953        }
954    }
955
956    #[rstest]
957    #[case(3, 5)]
958    #[case(3, 8)]
959    #[case(5, 10)]
960    #[case(2, 16)]
961    #[case(8, 16)]
962    #[case(9, 16)]
963    #[case(17, 16)]
964    fn test_iter_bits_with_offset(#[case] offset: usize, #[case] len: usize) {
965        let total_bits = offset + len;
966        let buf = BitBuffer::collect_bool(total_bits, |i| i % 2 == 0);
967        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
968
969        let mut collected = Vec::new();
970        buf_with_offset.iter_bits(|idx, is_set| {
971            collected.push((idx, is_set));
972        });
973
974        assert_eq!(collected.len(), len);
975
976        for (idx, is_set) in collected {
977            // The bits should match the original buffer at positions offset + idx
978            assert_eq!(is_set, (offset + idx).is_multiple_of(2));
979        }
980    }
981
982    #[rstest]
983    #[case(8, 10)]
984    #[case(9, 7)]
985    #[case(16, 8)]
986    #[case(17, 10)]
987    fn test_iter_bits_catches_wrong_byte_offset(#[case] offset: usize, #[case] len: usize) {
988        let total_bits = offset + len;
989        // Alternating pattern to catch byte offset errors: Bits are set for even indexed bytes.
990        let buf = BitBuffer::collect_bool(total_bits, |i| (i / 8) % 2 == 0);
991
992        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
993
994        let mut collected = Vec::new();
995        buf_with_offset.iter_bits(|idx, is_set| {
996            collected.push((idx, is_set));
997        });
998
999        assert_eq!(collected.len(), len);
1000
1001        for (idx, is_set) in collected {
1002            let bit_position = offset + idx;
1003            let byte_index = bit_position / 8;
1004            let expected_is_set = byte_index.is_multiple_of(2);
1005
1006            assert_eq!(
1007                is_set, expected_is_set,
1008                "Bit mismatch at index {}: expected {} got {}",
1009                bit_position, expected_is_set, is_set
1010            );
1011        }
1012    }
1013
1014    #[rstest]
1015    #[case(5)]
1016    #[case(8)]
1017    #[case(10)]
1018    #[case(64)]
1019    #[case(65)]
1020    #[case(100)]
1021    #[case(128)]
1022    fn test_map_cmp_identity(#[case] len: usize) {
1023        // map_cmp with identity function should return the same buffer
1024        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
1025        let mapped = buf.map_cmp(|_idx, bit| bit);
1026
1027        assert_eq!(buf.len(), mapped.len());
1028        for i in 0..len {
1029            assert_eq!(buf.value(i), mapped.value(i), "Mismatch at index {}", i);
1030        }
1031    }
1032
1033    #[rstest]
1034    #[case(5)]
1035    #[case(8)]
1036    #[case(64)]
1037    #[case(65)]
1038    #[case(100)]
1039    fn test_map_cmp_negate(#[case] len: usize) {
1040        // map_cmp negating all bits
1041        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
1042        let mapped = buf.map_cmp(|_idx, bit| !bit);
1043
1044        assert_eq!(buf.len(), mapped.len());
1045        for i in 0..len {
1046            assert_eq!(!buf.value(i), mapped.value(i), "Mismatch at index {}", i);
1047        }
1048    }
1049
1050    #[rstest]
1051    #[case(0, 0)]
1052    #[case(0, 64)]
1053    #[case(5, 70)]
1054    #[case(64, 130)]
1055    #[case(0, 200)]
1056    fn test_count_range(#[case] start: usize, #[case] end: usize) {
1057        let len = 200;
1058        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
1059        let expected = (start..end).filter(|i| i % 3 == 0).count();
1060        assert_eq!(buf.count_range(start, end), expected);
1061        // Must agree with slicing then counting.
1062        assert_eq!(
1063            buf.count_range(start, end),
1064            buf.slice(start..end).true_count()
1065        );
1066    }
1067
1068    #[rstest]
1069    #[case(3)]
1070    #[case(7)]
1071    fn test_count_range_with_offset(#[case] offset: usize) {
1072        let len = 150;
1073        let buf = BitBuffer::collect_bool(offset + len, |i| i % 2 == 0);
1074        let view = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
1075        for (start, end) in [(0, len), (10, 100), (1, 2), (63, 129)] {
1076            let expected = (offset + start..offset + end)
1077                .filter(|i| i % 2 == 0)
1078                .count();
1079            assert_eq!(view.count_range(start, end), expected, "[{start}, {end})");
1080        }
1081    }
1082
1083    #[rstest]
1084    #[case(0)]
1085    #[case(1)]
1086    #[case(63)]
1087    #[case(64)]
1088    #[case(65)]
1089    #[case(200)]
1090    #[case(1000)]
1091    fn test_set_index_visitors_match_set_indices(#[case] len: usize) {
1092        let buf = BitBuffer::collect_bool(len, |i| i % 5 == 0 || i % 7 == 0);
1093        let expected: Vec<usize> = buf.set_indices().collect();
1094
1095        let mut got = Vec::new();
1096        buf.for_each_set_index(|i| got.push(i));
1097        assert_eq!(got, expected);
1098
1099        let mut fallible_got = Vec::new();
1100        let result = buf.try_for_each_set_index(|i| {
1101            fallible_got.push(i);
1102            Ok::<(), ()>(())
1103        });
1104        assert_eq!(result, Ok(()));
1105        assert_eq!(fallible_got, expected);
1106    }
1107
1108    #[rstest]
1109    #[case(3, 200)]
1110    #[case(7, 130)]
1111    fn test_for_each_set_index_with_offset(#[case] offset: usize, #[case] len: usize) {
1112        let base = BitBuffer::collect_bool(offset + len, |i| i % 3 == 0);
1113        let view = BitBuffer::new_with_offset(base.inner().clone(), len, offset);
1114        let expected: Vec<usize> = view.set_indices().collect();
1115        let mut got = Vec::new();
1116        view.for_each_set_index(|i| got.push(i));
1117        assert_eq!(got, expected);
1118    }
1119
1120    #[test]
1121    fn test_for_each_set_index_all_set() {
1122        let buf = BitBuffer::new_set(130);
1123        let mut got = Vec::new();
1124        buf.for_each_set_index(|i| got.push(i));
1125        assert_eq!(got, (0..130).collect::<Vec<_>>());
1126    }
1127
1128    #[test]
1129    fn test_try_for_each_set_index_stops_on_error() {
1130        for (buffer, stop) in [
1131            (BitBuffer::new_set(130), 65),
1132            (BitBuffer::collect_bool(130, |i| i % 3 == 0), 66),
1133        ] {
1134            let mut visited = Vec::new();
1135            let result = buffer.try_for_each_set_index(|index| {
1136                visited.push(index);
1137                if index == stop {
1138                    return Err(index);
1139                }
1140
1141                Ok(())
1142            });
1143
1144            assert_eq!(result, Err(stop));
1145            assert_eq!(
1146                visited,
1147                buffer
1148                    .set_indices()
1149                    .take_while(|&i| i <= stop)
1150                    .collect::<Vec<_>>()
1151            );
1152        }
1153    }
1154
1155    #[test]
1156    fn test_map_cmp_conditional() {
1157        // map_cmp with conditional logic based on index and bit value
1158        let len = 100;
1159        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
1160
1161        // Only keep bits that are set AND at even index divisible by 4
1162        let mapped = buf.map_cmp(|idx, bit| bit && idx % 4 == 0);
1163
1164        for i in 0..len {
1165            let expected = (i % 2 == 0) && (i % 4 == 0);
1166            assert_eq!(mapped.value(i), expected, "Mismatch at index {}", i);
1167        }
1168    }
1169}