Skip to main content

arrow_buffer/util/
bit_util.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Utils for working with bits
19
20use crate::bit_chunk_iterator::BitChunks;
21
22/// Parallel bit extract: for each set bit in `mask`, extract the
23/// corresponding bit from `value` and pack them contiguously into the low
24/// bits of the return value.
25///
26/// Equivalent to the x86 BMI2 `PEXT` instruction. When compiled with the
27/// `bmi2` target feature enabled (for example `-C target-cpu=x86-64-v3`)
28/// this lowers to the hardware `pext` instruction; otherwise it falls back
29/// to a portable version that picks its method by the number of set bits in
30/// the mask.
31///
32/// # Functional Example
33///
34/// Using 8 bits for brevity (the function operates on all 64). Each
35/// set bit in `mask` selects the bit at the same position in `value`; the
36/// selected bits are then shifted down so they are contiguous in the low
37/// bits of the result, in their original order:
38///
39/// ```text
40/// bit:     7 6 5 4 3 2 1 0
41/// value:   a b c d e f g h
42/// mask:    0 1 1 0 1 1 0 1      set bits select b, c, e, f and h
43///            | |   | |   |
44///            v v   v v   v      copy the relevant bits into result
45/// result:  0 0 0 b c e f h
46/// ```
47///
48/// # Code Example
49///
50/// ```
51/// # use arrow_buffer::bit_util::compress;
52/// assert_eq!(compress(0b1011_0100, 0b0110_1101), 0b0000_1010);
53/// ```
54//
55// When `uint_gather_scatter_bits` is stabilised
56// (<https://github.com/rust-lang/rust/issues/149069>), measure
57// `value.extract_bits(mask)` against this before switching. Its portable
58// version runs six shift stages for every mask, with each stage's mask taken
59// from prefix counts of zero bits (Hacker's Delight 7-4, see
60// <https://github.com/rust-lang/rust/blob/main/library/core/src/num/imp/int_bits.rs>).
61// The fallback here does less work on sparse and dense masks, and on x86 also
62// in between (three stages inside each byte plus one multiply, fewer shifts in
63// total); on big AArch64 cores the six-stage version is faster in that middle
64// band.
65#[inline]
66pub fn compress(value: u64, mask: u64) -> u64 {
67    #[cfg(all(target_arch = "x86_64", target_feature = "bmi2"))]
68    {
69        // SAFETY: the `bmi2` target feature is statically enabled for this
70        // build, so the `pext` instruction is guaranteed to be available.
71        unsafe { std::arch::x86_64::_pext_u64(value, mask) }
72    }
73
74    #[cfg(not(all(target_arch = "x86_64", target_feature = "bmi2")))]
75    {
76        compress_portable(value, mask)
77    }
78}
79
80/// [`compress`] without `pext`, by the number of kept bits `k`:
81///
82/// * `k <= 2`: the two lowest kept bits, tested directly
83/// * `k <= 16`: one step per kept bit
84/// * `k >= 62`: `value` with its at most two dropped bits removed
85/// * otherwise [`compress_bytes`]
86///
87/// The two ends need no loop, so no loop exit to mispredict when `k`
88/// varies from word to word.
89// Always: called once a word, so a call would cost more than a sparse word
90#[cfg(any(test, not(all(target_arch = "x86_64", target_feature = "bmi2"))))]
91#[inline(always)]
92fn compress_portable(value: u64, mask: u64) -> u64 {
93    let kept = mask.count_ones();
94    if kept <= 2 {
95        let lowest = mask & mask.wrapping_neg();
96        let second = mask ^ lowest;
97        u64::from(value & lowest != 0) | (u64::from(value & second != 0) << 1)
98    } else if kept <= 16 {
99        let mut mask = mask;
100        let mut result = 0_u64;
101        let mut dest_bit = 1_u64;
102        while mask != 0 {
103            // Clear the lowest set bit; the loop-carried dependency is only
104            // this two-operation chain, everything else hangs off it
105            let rest = mask & (mask - 1);
106            let lowest = mask ^ rest;
107            let keep = ((value & lowest) != 0) as u64;
108            result |= dest_bit & keep.wrapping_neg();
109            dest_bit <<= 1;
110            mask = rest;
111        }
112        result
113    } else if kept >= 62 {
114        if kept == 64 {
115            return value;
116        }
117        // Remove the highest dropped bit first, so the lower ones stay put.
118        // With nothing left to drop a step keeps every bit, and each step
119        // shifts a zero in at the top.
120        let mut value = value;
121        let mut dropped = !mask;
122        for _ in 0..2 {
123            let below = if dropped == 0 {
124                u64::MAX
125            } else {
126                (1 << dropped.ilog2()) - 1
127            };
128            value = (value & below) | ((value >> 1) & !below);
129            dropped &= below;
130        }
131        value
132    } else {
133        compress_bytes(value, mask)
134    }
135}
136
137/// `b` in every byte of a `u64`
138#[cfg(any(test, not(all(target_arch = "x86_64", target_feature = "bmi2"))))]
139const fn repeat_byte(b: u8) -> u64 {
140    b as u64 * 0x0101_0101_0101_0101
141}
142
143/// [`compress`] in constant time, a byte at a time and all bytes at once.
144///
145/// Within a byte each kept bit moves down by the number of dropped bits
146/// below it. Written in binary, that distance is at most 7, so three rounds
147/// move by 1, 2 and 4 the bits whose distance has that bit set (the parallel
148/// suffix network, Hacker's Delight 7-4); shifts are masked to stay within
149/// the byte. Byte `j` then lands at the sum of the kept counts of bytes
150/// `0..j`, all eight sums from one multiply by `0x0101..01`.
151#[cfg(any(test, not(all(target_arch = "x86_64", target_feature = "bmi2"))))]
152#[inline]
153fn compress_bytes(value: u64, mask: u64) -> u64 {
154    let mut packed = value & mask;
155    let mut dropped = !mask;
156    let mut shift = 1;
157    while shift < 8 {
158        // The move mask of this round: the parity of the dropped bits
159        // below each bit, in strides of `shift`
160        let mut move_mask = dropped;
161        let mut step = shift;
162        while step < 8 {
163            move_mask ^= (move_mask << step) & repeat_byte(0xFF << step);
164            step <<= 1;
165        }
166        let moving = packed & move_mask;
167        packed ^= moving ^ ((moving >> shift) & repeat_byte(0xFF >> shift));
168        dropped &= !move_mask;
169        dropped ^= (dropped >> shift) & repeat_byte(0xFF >> shift);
170        shift <<= 1;
171    }
172    // Kept bits per byte (SWAR popcount, HD 5-1)
173    let pairs = mask - ((mask >> 1) & repeat_byte(0x55));
174    let nibbles = (pairs & repeat_byte(0x33)) + ((pairs >> 2) & repeat_byte(0x33));
175    let kept = (nibbles + (nibbles >> 4)) & repeat_byte(0x0F);
176    // Where byte `j` lands: the kept bits of bytes 0..j, all eight at once
177    let dest = kept.wrapping_mul(repeat_byte(1)) << 8;
178    let mut result = 0;
179    for j in 0..8 {
180        result |= ((packed >> (8 * j)) & 0xFF) << ((dest >> (8 * j)) & 0xFF);
181    }
182    result
183}
184
185/// Parallel bit deposit: scatter the lowest `mask.count_ones()` bits of
186/// `value` into the set positions of `mask`, preserving their order.
187/// All other bits in the result are zero; excess input bits are ignored.
188///
189/// This is the inverse of [`compress`] on the selected bits:
190/// `expand(compress(value, mask), mask) == value & mask`.
191///
192/// Equivalent to the x86 BMI2 `PDEP` instruction. When compiled with the
193/// `bmi2` target feature enabled (for example `-C target-cpu=x86-64-v3`)
194/// this lowers to the hardware `pdep` instruction; otherwise it falls back
195/// to a portable scalar loop that visits whichever is fewer: unset or set
196/// bits in `mask`.
197///
198/// # Functional Example
199///
200/// Using 8 bits for brevity (the function operates on all 64). The low bits
201/// of `value` are scattered into the set positions of `mask`:
202///
203/// ```text
204/// bit:     7 6 5 4 3 2 1 0
205/// value:   0 0 0 b c e f h
206/// mask:    0 1 1 0 1 1 0 1
207/// result:  0 b c 0 e f 0 h
208/// ```
209///
210/// # Code Example
211///
212/// ```
213/// # use arrow_buffer::bit_util::{compress, expand};
214/// assert_eq!(expand(0b0000_1010, 0b0110_1101), 0b0010_0100);
215/// let value = 0b1011_0100;
216/// let mask = 0b0110_1101;
217/// assert_eq!(expand(compress(value, mask), mask), value & mask);
218/// ```
219#[inline]
220pub fn expand(value: u64, mask: u64) -> u64 {
221    #[cfg(all(target_arch = "x86_64", target_feature = "bmi2"))]
222    {
223        // SAFETY: the `bmi2` target feature is statically enabled for this
224        // build, so the `pdep` instruction is guaranteed to be available.
225        unsafe { std::arch::x86_64::_pdep_u64(value, mask) }
226    }
227
228    #[cfg(not(all(target_arch = "x86_64", target_feature = "bmi2")))]
229    {
230        let mut value = value;
231        if value == 0 {
232            return 0;
233        }
234
235        let mut zeros = !mask;
236        if zeros.count_ones() <= 32 {
237            // Insert zeros from low to high; excess input bits shift out.
238            while zeros != 0 {
239                let lower = (1_u64 << zeros.trailing_zeros()) - 1;
240                value = (value & lower) | ((value & !lower) << 1);
241                zeros &= zeros - 1;
242            }
243            value
244        } else {
245            let mut output = 0;
246            let mut ones = mask;
247            while ones != 0 {
248                output |= (value & 1) << ones.trailing_zeros();
249                value >>= 1;
250                ones &= ones - 1;
251            }
252            output
253        }
254    }
255}
256
257/// Returns the nearest number that is `>=` than `num` and is a multiple of 64
258///
259/// # Panics
260///
261/// Panics if rounding `num` up overflows `usize`
262#[inline]
263pub fn round_upto_multiple_of_64(num: usize) -> usize {
264    num.checked_next_multiple_of(64)
265        .expect("failed to round upto multiple of 64")
266}
267
268/// Returns the nearest multiple of `factor` that is `>=` than `num`. Here `factor` must
269/// be a power of 2.
270///
271/// # Panics
272///
273/// Panics if rounding `num` up overflows `usize`
274pub fn round_upto_power_of_2(num: usize, factor: usize) -> usize {
275    debug_assert!(factor > 0 && factor.is_power_of_two());
276    num.checked_add(factor - 1)
277        .expect("failed to round to next highest power of 2")
278        & !(factor - 1)
279}
280
281/// Returns whether bit at position `i` in `data` is set or not
282///
283/// # Panics
284///
285/// Panics if `i / 8 >= data.len()`
286#[inline]
287pub fn get_bit(data: &[u8], i: usize) -> bool {
288    data[i / 8] & (1 << (i % 8)) != 0
289}
290
291/// Returns whether bit at position `i` in `data` is set or not.
292///
293/// # Safety
294///
295/// Note this doesn't do any bound checking, for performance reason. The caller is
296/// responsible to guarantee that `i` is within bounds.
297#[inline]
298pub unsafe fn get_bit_raw(data: *const u8, i: usize) -> bool {
299    unsafe { (*data.add(i / 8) & (1 << (i % 8))) != 0 }
300}
301
302/// Sets bit at position `i` for `data` to 1
303///
304/// # Panics
305///
306/// Panics if `i / 8 >= data.len()`
307#[inline]
308pub fn set_bit(data: &mut [u8], i: usize) {
309    data[i / 8] |= 1 << (i % 8);
310}
311
312/// Sets bit at position `i` for `data`
313///
314/// # Safety
315///
316/// Note this doesn't do any bound checking, for performance reason. The caller is
317/// responsible to guarantee that `i` is within bounds.
318#[inline]
319pub unsafe fn set_bit_raw(data: *mut u8, i: usize) {
320    unsafe {
321        *data.add(i / 8) |= 1 << (i % 8);
322    }
323}
324
325/// Sets bit at position `i` for `data` to 0
326///
327/// # Panics
328///
329/// Panics if `i / 8 >= data.len()`
330#[inline]
331pub fn unset_bit(data: &mut [u8], i: usize) {
332    data[i / 8] &= !(1 << (i % 8));
333}
334
335/// Sets bit at position `i` for `data` to 0
336///
337/// # Safety
338///
339/// Note this doesn't do any bound checking, for performance reason. The caller is
340/// responsible to guarantee that `i` is within bounds.
341#[inline]
342pub unsafe fn unset_bit_raw(data: *mut u8, i: usize) {
343    unsafe {
344        *data.add(i / 8) &= !(1 << (i % 8));
345    }
346}
347
348/// Returns the ceil of `value`/`divisor`
349#[inline]
350pub fn ceil(value: usize, divisor: usize) -> usize {
351    value.div_ceil(divisor)
352}
353
354/// Read a u64 from a byte slice, padding with zeros if necessary
355#[inline]
356pub(crate) fn read_u64(input: &[u8]) -> u64 {
357    let len = input.len().min(8);
358    let mut buf = [0_u8; 8];
359    buf[..len].copy_from_slice(input);
360    u64::from_le_bytes(buf)
361}
362
363/// Read up to 8 bits from a byte slice starting at a given bit offset.
364///
365/// # Arguments
366///
367/// * `slice` - The byte slice to read from
368/// * `number_of_bits_to_read` - Number of bits to read (must be < 8)
369/// * `bit_offset` - Starting bit offset within the first byte (must be < 8)
370///
371/// # Returns
372///
373/// A `u8` containing the requested bits in the least significant positions
374///
375/// # Panics
376/// - Panics if `number_of_bits_to_read` is 0 or >= 8
377/// - Panics if `bit_offset` is >= 8
378/// - Panics if `slice` is empty or too small to read the requested bits
379///
380#[inline]
381pub(crate) fn read_up_to_byte_from_offset(
382    slice: &[u8],
383    number_of_bits_to_read: usize,
384    bit_offset: usize,
385) -> u8 {
386    assert!(number_of_bits_to_read < 8, "can read up to 8 bits only");
387    assert!(bit_offset < 8, "bit offset must be less than 8");
388    assert_ne!(
389        number_of_bits_to_read, 0,
390        "number of bits to read must be greater than 0"
391    );
392    assert_ne!(slice.len(), 0, "slice must not be empty");
393
394    let number_of_bytes_to_read = ceil(number_of_bits_to_read + bit_offset, 8);
395
396    // number of bytes to read
397    assert!(slice.len() >= number_of_bytes_to_read, "slice is too small");
398
399    let mut bits = slice[0] >> bit_offset;
400    for (i, &byte) in slice
401        .iter()
402        .take(number_of_bytes_to_read)
403        .enumerate()
404        .skip(1)
405    {
406        bits |= byte << (i * 8 - bit_offset);
407    }
408
409    bits & ((1 << number_of_bits_to_read) - 1)
410}
411
412/// Applies a bitwise operation relative to another bit-packed byte slice
413/// (right) in place
414///
415/// Note: applies the operation 64-bits (u64) at a time.
416///
417/// # Arguments
418///
419/// * `left` - The mutable buffer to be modified in-place
420/// * `offset_in_bits` - Starting bit offset in Self buffer
421/// * `right` - slice of bit-packed bytes in LSB order
422/// * `right_offset_in_bits` - Starting bit offset in the right buffer
423/// * `len_in_bits` - Number of bits to process
424/// * `op` - Binary operation to apply (e.g., `|a, b| a & b`). Applied a word at a time
425///
426/// Only the bits in `left_offset_in_bits..left_offset_in_bits + len_in_bits` are
427/// modified. Bits of `left` outside that range are left unchanged, including the
428/// bits sharing a byte with either end of the range.
429///
430/// # Example: Modify entire buffer
431/// ```
432/// # use arrow_buffer::MutableBuffer;
433/// # use arrow_buffer::bit_util::apply_bitwise_binary_op;
434/// let mut left = MutableBuffer::new(2);
435/// left.extend_from_slice(&[0b11110000u8, 0b00110011u8]);
436/// let right = &[0b10101010u8, 0b10101010u8];
437/// // apply bitwise AND between left and right buffers, updating left in place
438/// apply_bitwise_binary_op(left.as_slice_mut(), 0, right, 0, 16, |a, b| a & b);
439/// assert_eq!(left.as_slice(), &[0b10100000u8, 0b00100010u8]);
440/// ```
441///
442/// # Example: Modify buffer with offsets
443/// ```
444/// # use arrow_buffer::MutableBuffer;
445/// # use arrow_buffer::bit_util::apply_bitwise_binary_op;
446/// let mut left = MutableBuffer::new(2);
447/// left.extend_from_slice(&[0b00000000u8, 0b00000000u8]);
448/// let right = &[0b10110011u8, 0b11111110u8];
449/// // apply bitwise OR between left and right buffers,
450/// // Apply only 8 bits starting from bit offset 3 in left and bit offset 2 in right
451/// apply_bitwise_binary_op(left.as_slice_mut(), 3, right, 2, 8, |a, b| a | b);
452/// assert_eq!(left.as_slice(), &[0b01100000, 0b00000101u8]);
453/// ```
454///
455/// # Panics
456///
457/// If the offset or lengths exceed the buffer or slice size.
458pub fn apply_bitwise_binary_op<F>(
459    left: &mut [u8],
460    left_offset_in_bits: usize,
461    right: impl AsRef<[u8]>,
462    right_offset_in_bits: usize,
463    len_in_bits: usize,
464    mut op: F,
465) where
466    F: FnMut(u64, u64) -> u64,
467{
468    if len_in_bits == 0 {
469        return;
470    }
471
472    // offset inside a byte
473    let bit_offset = left_offset_in_bits % 8;
474
475    let is_mutable_buffer_byte_aligned = bit_offset == 0;
476
477    if is_mutable_buffer_byte_aligned {
478        byte_aligned_bitwise_bin_op_helper(
479            left,
480            left_offset_in_bits,
481            right,
482            right_offset_in_bits,
483            len_in_bits,
484            op,
485        );
486    } else {
487        // If we are not byte aligned, run `op` on the first few bits to reach byte alignment
488        let bits_to_next_byte = (8 - bit_offset)
489            // Minimum with the amount of bits we need to process
490            // to avoid reading out of bounds
491            .min(len_in_bits);
492
493        {
494            let right_byte_offset = right_offset_in_bits / 8;
495
496            // Read the same amount of bits from the right buffer
497            let right_first_byte = crate::util::bit_util::read_up_to_byte_from_offset(
498                &right.as_ref()[right_byte_offset..],
499                bits_to_next_byte,
500                // Right bit offset
501                right_offset_in_bits % 8,
502            );
503
504            align_to_byte(
505                left,
506                // Hope it gets inlined
507                &mut |left| op(left, right_first_byte as u64),
508                left_offset_in_bits,
509                bits_to_next_byte,
510            );
511        }
512
513        let offset_in_bits = left_offset_in_bits + bits_to_next_byte;
514        let right_offset_in_bits = right_offset_in_bits + bits_to_next_byte;
515        let len_in_bits = len_in_bits.saturating_sub(bits_to_next_byte);
516
517        if len_in_bits == 0 {
518            return;
519        }
520
521        // We are now byte aligned
522        byte_aligned_bitwise_bin_op_helper(
523            left,
524            offset_in_bits,
525            right,
526            right_offset_in_bits,
527            len_in_bits,
528            op,
529        );
530    }
531}
532
533/// Apply a bitwise operation to a mutable buffer, updating it in place.
534///
535/// Note: applies the operation 64-bits (u64) at a time.
536///
537/// # Arguments
538///
539/// * `offset_in_bits` - Starting bit offset for the current buffer
540/// * `len_in_bits` - Number of bits to process
541/// * `op` - Unary operation to apply (e.g., `|a| !a`). Applied a word at a time
542///
543/// Only the bits in `offset_in_bits..offset_in_bits + len_in_bits` are modified.
544/// Bits outside that range are left unchanged, including the bits sharing a byte
545/// with either end of the range.
546///
547/// # Example: Modify entire buffer
548/// ```
549/// # use arrow_buffer::MutableBuffer;
550/// # use arrow_buffer::bit_util::apply_bitwise_unary_op;
551/// let mut buffer = MutableBuffer::new(2);
552/// buffer.extend_from_slice(&[0b11110000u8, 0b00110011u8]);
553/// // apply bitwise NOT to the buffer in place
554/// apply_bitwise_unary_op(buffer.as_slice_mut(), 0, 16, |a| !a);
555/// assert_eq!(buffer.as_slice(), &[0b00001111u8, 0b11001100u8]);
556/// ```
557///
558/// # Example: Modify buffer with offsets
559/// ```
560/// # use arrow_buffer::MutableBuffer;
561/// # use arrow_buffer::bit_util::apply_bitwise_unary_op;
562/// let mut buffer = MutableBuffer::new(2);
563/// buffer.extend_from_slice(&[0b00000000u8, 0b00000000u8]);
564/// // apply bitwise NOT to 8 bits starting from bit offset 3
565/// apply_bitwise_unary_op(buffer.as_slice_mut(), 3, 8, |a| !a);
566/// assert_eq!(buffer.as_slice(), &[0b11111000u8, 0b00000111u8]);
567/// ```
568///
569/// # Panics
570///
571/// If the offset and length exceed the buffer size.
572pub fn apply_bitwise_unary_op<F>(
573    buffer: &mut [u8],
574    offset_in_bits: usize,
575    len_in_bits: usize,
576    mut op: F,
577) where
578    F: FnMut(u64) -> u64,
579{
580    if len_in_bits == 0 {
581        return;
582    }
583
584    // offset inside a byte
585    let left_bit_offset = offset_in_bits % 8;
586
587    let is_mutable_buffer_byte_aligned = left_bit_offset == 0;
588
589    if is_mutable_buffer_byte_aligned {
590        byte_aligned_bitwise_unary_op_helper(buffer, offset_in_bits, len_in_bits, op);
591    } else {
592        align_to_byte(buffer, &mut op, offset_in_bits, len_in_bits);
593
594        // If we are not byte aligned we will read the first few bits
595        let bits_to_next_byte = 8 - left_bit_offset;
596
597        let offset_in_bits = offset_in_bits + bits_to_next_byte;
598        let len_in_bits = len_in_bits.saturating_sub(bits_to_next_byte);
599
600        if len_in_bits == 0 {
601            return;
602        }
603
604        // We are now byte aligned
605        byte_aligned_bitwise_unary_op_helper(buffer, offset_in_bits, len_in_bits, op);
606    }
607}
608
609/// Perform bitwise binary operation on byte-aligned buffers (i.e. not offsetting into a middle of a byte).
610///
611/// This is the optimized path for byte-aligned operations. It processes data in
612/// u64 chunks for maximum efficiency, then handles any remainder bits.
613///
614/// # Arguments
615///
616/// * `left` - The left mutable buffer (must be byte-aligned)
617/// * `left_offset_in_bits` - Starting bit offset in the left buffer (must be multiple of 8)
618/// * `right` - The right buffer as byte slice
619/// * `right_offset_in_bits` - Starting bit offset in the right buffer
620/// * `len_in_bits` - Number of bits to process
621/// * `op` - Binary operation to apply
622#[inline]
623fn byte_aligned_bitwise_bin_op_helper<F>(
624    left: &mut [u8],
625    left_offset_in_bits: usize,
626    right: impl AsRef<[u8]>,
627    right_offset_in_bits: usize,
628    len_in_bits: usize,
629    mut op: F,
630) where
631    F: FnMut(u64, u64) -> u64,
632{
633    // Must not reach here if we not byte aligned
634    assert_eq!(
635        left_offset_in_bits % 8,
636        0,
637        "offset_in_bits must be byte aligned"
638    );
639
640    // 1. Prepare the buffers
641    let (complete_u64_chunks, remainder_bytes) =
642        U64UnalignedSlice::split(left, left_offset_in_bits, len_in_bits);
643
644    let right_chunks = BitChunks::new(right.as_ref(), right_offset_in_bits, len_in_bits);
645    assert_eq!(
646        self::ceil(right_chunks.remainder_len(), 8),
647        remainder_bytes.len()
648    );
649
650    let right_chunks_iter = right_chunks.iter();
651    assert_eq!(right_chunks_iter.len(), complete_u64_chunks.len());
652
653    // 2. Process complete u64 chunks
654    complete_u64_chunks.zip_modify(right_chunks_iter, &mut op);
655
656    // Handle remainder bits if any
657    if right_chunks.remainder_len() > 0 {
658        handle_mutable_buffer_remainder(
659            &mut op,
660            remainder_bytes,
661            right_chunks.remainder_bits(),
662            right_chunks.remainder_len(),
663        )
664    }
665}
666
667/// Perform bitwise unary operation on byte-aligned buffer.
668///
669/// This is the optimized path for byte-aligned unary operations. It processes data in
670/// u64 chunks for maximum efficiency, then handles any remainder bits.
671///
672/// # Arguments
673///
674/// * `buffer` - The mutable buffer (must be byte-aligned)
675/// * `offset_in_bits` - Starting bit offset (must be multiple of 8)
676/// * `len_in_bits` - Number of bits to process
677/// * `op` - Unary operation to apply (e.g., `|a| !a`)
678#[inline]
679fn byte_aligned_bitwise_unary_op_helper<F>(
680    buffer: &mut [u8],
681    offset_in_bits: usize,
682    len_in_bits: usize,
683    mut op: F,
684) where
685    F: FnMut(u64) -> u64,
686{
687    // Must not reach here if we not byte aligned
688    assert_eq!(offset_in_bits % 8, 0, "offset_in_bits must be byte aligned");
689
690    let remainder_len = len_in_bits % 64;
691
692    let (complete_u64_chunks, remainder_bytes) =
693        U64UnalignedSlice::split(buffer, offset_in_bits, len_in_bits);
694
695    assert_eq!(self::ceil(remainder_len, 8), remainder_bytes.len());
696
697    // 2. Process complete u64 chunks
698    complete_u64_chunks.apply_unary_op(&mut op);
699
700    // Handle remainder bits if any
701    if remainder_len > 0 {
702        handle_mutable_buffer_remainder_unary(&mut op, remainder_bytes, remainder_len)
703    }
704}
705
706/// Align to byte boundary by applying operation to bits before the next byte boundary.
707///
708/// This function handles non-byte-aligned operations by processing bits from the current
709/// position up to the next byte boundary, while preserving all other bits in the byte.
710///
711/// # Arguments
712///
713/// * `op` - Unary operation to apply
714/// * `buffer` - The mutable buffer to modify
715/// * `offset_in_bits` - Starting bit offset (not byte-aligned)
716/// * `remaining_len_in_bits` - Number of bits still to process starting at `offset_in_bits`.
717///   When this is smaller than the number of bits left in the byte, the trailing bits of
718///   the byte are left untouched.
719fn align_to_byte<F>(
720    buffer: &mut [u8],
721    op: &mut F,
722    offset_in_bits: usize,
723    remaining_len_in_bits: usize,
724) where
725    F: FnMut(u64) -> u64,
726{
727    let byte_offset = offset_in_bits / 8;
728    let bit_offset = offset_in_bits % 8;
729
730    // Byte aligned offsets must take the byte aligned path instead
731    debug_assert_ne!(bit_offset, 0, "offset_in_bits must not be byte aligned");
732
733    // 1. read the first byte from the buffer
734    let first_byte: u8 = buffer[byte_offset];
735
736    // 2. Shift byte by the bit offset, keeping only the relevant bits
737    let relevant_first_byte = first_byte >> bit_offset;
738
739    // 3. run the op on the first byte only
740    let result_first_byte = op(relevant_first_byte as u64) as u8;
741
742    // 4. Shift back the result to the original position
743    let result_first_byte = result_first_byte << bit_offset;
744
745    // 5. Mask in only the bits the caller asked to process, i.e. the bits in
746    //    `bit_offset..bit_offset + bits_in_this_byte`. The request may end before the
747    //    byte boundary, in which case the trailing bits must be preserved as well.
748    //
749    //    `bit_offset` is in `1..=7` per the assert above, so `bits_in_this_byte` is at
750    //    most 7 and `bits_in_this_byte + bit_offset <= 8`, keeping the mask within a `u8`.
751    let bits_in_this_byte = (8 - bit_offset).min(remaining_len_in_bits);
752    let write_mask = ((1u8 << bits_in_this_byte) - 1) << bit_offset;
753
754    let result_first_byte = (first_byte & !write_mask) | (result_first_byte & write_mask);
755
756    // 6. write back the result to the buffer
757    buffer[byte_offset] = result_first_byte;
758}
759
760/// Centralized structure to handle a mutable u8 slice as a mutable u64 pointer.
761///
762/// Handle the following:
763/// 1. the lifetime is correct
764/// 2. we read/write within the bounds
765/// 3. We read and write using unaligned
766///
767/// This does not deallocate the underlying pointer when dropped
768///
769/// This is the only place that uses unsafe code to read and write unaligned
770///
771struct U64UnalignedSlice<'a> {
772    /// Pointer to the start of the u64 data
773    ///
774    /// We are using raw pointer as the data came from a u8 slice so we need to read and write unaligned
775    ptr: *mut u64,
776
777    /// Number of u64 elements
778    len: usize,
779
780    /// Marker to tie the lifetime of the pointer to the lifetime of the u8 slice
781    _marker: std::marker::PhantomData<&'a u8>,
782}
783
784impl<'a> U64UnalignedSlice<'a> {
785    /// Create a new [`U64UnalignedSlice`] from a `&mut [u8]` buffer
786    ///
787    /// return the [`U64UnalignedSlice`] and slice of bytes that are not part of the u64 chunks (guaranteed to be less than 8 bytes)
788    ///
789    fn split(
790        buffer: &'a mut [u8],
791        offset_in_bits: usize,
792        len_in_bits: usize,
793    ) -> (Self, &'a mut [u8]) {
794        // 1. Prepare the buffers
795        let left_buffer_mut: &mut [u8] = {
796            let last_offset = self::ceil(offset_in_bits + len_in_bits, 8);
797            assert!(last_offset <= buffer.len());
798
799            let byte_offset = offset_in_bits / 8;
800
801            &mut buffer[byte_offset..last_offset]
802        };
803
804        let number_of_u64_we_can_fit = len_in_bits / (u64::BITS as usize);
805
806        // 2. Split
807        let u64_len_in_bytes = number_of_u64_we_can_fit * size_of::<u64>();
808
809        assert!(u64_len_in_bytes <= left_buffer_mut.len());
810        let (bytes_for_u64, remainder) = left_buffer_mut.split_at_mut(u64_len_in_bytes);
811
812        #[expect(
813            clippy::cast_ptr_alignment,
814            reason = "`U64UnalignedSlice` only reads and writes through the unaligned methods"
815        )]
816        let ptr = bytes_for_u64.as_mut_ptr().cast::<u64>();
817
818        let this = Self {
819            ptr,
820            len: number_of_u64_we_can_fit,
821            _marker: std::marker::PhantomData,
822        };
823
824        (this, remainder)
825    }
826
827    fn len(&self) -> usize {
828        self.len
829    }
830
831    /// Modify the underlying u64 data in place using a binary operation
832    /// with another iterator.
833    fn zip_modify(
834        mut self,
835        mut zip_iter: impl ExactSizeIterator<Item = u64>,
836        mut map: impl FnMut(u64, u64) -> u64,
837    ) {
838        assert_eq!(self.len, zip_iter.len());
839
840        // In order to avoid advancing the pointer at the end of the loop which will
841        // make the last pointer invalid, we handle the first element outside the loop
842        // and then advance the pointer at the start of the loop
843        // making sure that the iterator is not empty
844        if let Some(right) = zip_iter.next() {
845            // SAFETY: We asserted that the iterator length and the current length are the same
846            // and the iterator is not empty, so the pointer is valid
847            unsafe {
848                self.apply_bin_op(right, &mut map);
849            }
850
851            // Because this consumes self we don't update the length
852        }
853
854        for right in zip_iter {
855            // Advance the pointer
856            //
857            // SAFETY: We asserted that the iterator length and the current length are the same
858            self.ptr = unsafe { self.ptr.add(1) };
859
860            // SAFETY: the pointer is valid as we are within the length
861            unsafe {
862                self.apply_bin_op(right, &mut map);
863            }
864
865            // Because this consumes self we don't update the length
866        }
867    }
868
869    /// Centralized function to correctly read the current u64 value and write back the result
870    ///
871    /// # SAFETY
872    /// the caller must ensure that the pointer is valid for reads and writes
873    ///
874    #[inline]
875    unsafe fn apply_bin_op(&mut self, right: u64, mut map: impl FnMut(u64, u64) -> u64) {
876        // SAFETY: The constructor ensures the pointer is valid,
877        // and as to all modifications in U64UnalignedSlice
878        let current_input = unsafe {
879            self.ptr
880                // Reading unaligned as we came from u8 slice
881                .read_unaligned()
882                // bit-packed buffers are stored starting with the least-significant byte first
883                // so when reading as u64 on a big-endian machine, the bytes need to be swapped
884                .to_le()
885        };
886
887        let combined = map(current_input, right);
888
889        // Write the result back
890        //
891        // The pointer came from mutable u8 slice so the pointer is valid for writes,
892        // and we need to write unaligned
893        unsafe { self.ptr.write_unaligned(combined) }
894    }
895
896    /// Modify the underlying u64 data in place using a unary operation.
897    fn apply_unary_op(mut self, mut map: impl FnMut(u64) -> u64) {
898        if self.len == 0 {
899            return;
900        }
901
902        // In order to avoid advancing the pointer at the end of the loop which will
903        // make the last pointer invalid, we handle the first element outside the loop
904        // and then advance the pointer at the start of the loop
905        // making sure that the iterator is not empty
906        // Safety: `self.len > 0` (checked above) and the pointer has not been advanced yet,
907        // so it is valid for reads and writes.
908        unsafe {
909            // I hope the function get inlined and the compiler remove the dead right parameter
910            self.apply_bin_op(0, &mut |left, _| map(left));
911
912            // Because this consumes self we don't update the length
913        }
914
915        for _ in 1..self.len {
916            // Advance the pointer
917            //
918            // SAFETY: we only advance the pointer within the length and not beyond
919            self.ptr = unsafe { self.ptr.add(1) };
920
921            // SAFETY: the pointer is valid as we are within the length
922            unsafe {
923                // I hope the function get inlined and the compiler remove the dead right parameter
924                self.apply_bin_op(0, &mut |left, _| map(left));
925            }
926
927            // Because this consumes self we don't update the length
928        }
929    }
930}
931
932/// Handle remainder bits (< 64 bits) for binary operations.
933///
934/// This function processes the bits that don't form a complete u64 chunk,
935/// ensuring that bits outside the operation range are preserved.
936///
937/// # Arguments
938///
939/// * `op` - Binary operation to apply
940/// * `start_remainder_mut_slice` - slice to the start of remainder bytes
941///   the length must be equal to `ceil(remainder_len, 8)`
942/// * `right_remainder_bits` - Right operand bits
943/// * `remainder_len` - Number of remainder bits
944#[inline]
945fn handle_mutable_buffer_remainder<F>(
946    op: &mut F,
947    start_remainder_mut_slice: &mut [u8],
948    right_remainder_bits: u64,
949    remainder_len: usize,
950) where
951    F: FnMut(u64, u64) -> u64,
952{
953    // Only read from slice the number of remainder bits
954    let left_remainder_bits = get_remainder_bits(start_remainder_mut_slice, remainder_len);
955
956    // Apply the operation
957    let rem = op(left_remainder_bits, right_remainder_bits);
958
959    // Write only the relevant bits back the result to the mutable slice
960    set_remainder_bits(start_remainder_mut_slice, rem, remainder_len);
961}
962
963/// Write remainder bits back to buffer while preserving bits outside the range.
964///
965/// This function carefully updates only the specified bits, leaving all other
966/// bits in the affected bytes unchanged.
967///
968/// # Arguments
969///
970/// * `start_remainder_mut_slice` - the slice of bytes to write the remainder bits to,
971///   the length must be equal to `ceil(remainder_len, 8)`
972/// * `rem` - The result bits to write
973/// * `remainder_len` - Number of bits to write
974#[inline]
975fn set_remainder_bits(start_remainder_mut_slice: &mut [u8], rem: u64, remainder_len: usize) {
976    assert_ne!(
977        start_remainder_mut_slice.len(),
978        0,
979        "start_remainder_mut_slice must not be empty"
980    );
981    assert!(remainder_len < 64, "remainder_len must be less than 64");
982
983    // This assertion is to make sure that the last byte in the slice is the boundary byte
984    // (i.e., the byte that contains both remainder bits and bits outside the remainder)
985    assert_eq!(
986        start_remainder_mut_slice.len(),
987        self::ceil(remainder_len, 8),
988        "start_remainder_mut_slice length must be equal to ceil(remainder_len, 8)"
989    );
990
991    // Need to update the remainder bytes in the mutable buffer
992    // but not override the bits outside the remainder
993
994    // Update `rem` end with the current bytes in the mutable buffer
995    // to preserve the bits outside the remainder
996    let rem = {
997        // 1. Read the byte that we will override
998        //    we only read the last byte as we verified that start_remainder_mut_slice length is
999        //    equal to ceil(remainder_len, 8), which means the last byte is the boundary byte
1000        //    containing both remainder bits and bits outside the remainder
1001        let current = start_remainder_mut_slice
1002            .last()
1003            // Unwrap as we already validated the slice is not empty
1004            .unwrap();
1005
1006        // Shift the boundary byte to the position it occupies within `rem`, otherwise
1007        // its bits would be compared against the wrong end of the mask below
1008        let current = (*current as u64) << ((start_remainder_mut_slice.len() - 1) * 8);
1009
1010        // Mask where the bits that are inside the remainder are 1
1011        // and the bits outside the remainder are 0
1012        let inside_remainder_mask = (1 << remainder_len) - 1;
1013        // Mask where the bits that are outside the remainder are 1
1014        // and the bits inside the remainder are 0
1015        let outside_remainder_mask = !inside_remainder_mask;
1016
1017        // 2. Only keep the bits that are outside the remainder for the value from the mutable buffer
1018        let current = current & outside_remainder_mask;
1019
1020        // 3. Only keep the bits that are inside the remainder for the value from the operation
1021        let rem = rem & inside_remainder_mask;
1022
1023        // 4. Combine the two values
1024        current | rem
1025    };
1026
1027    // Write back the result to the mutable slice
1028    {
1029        let remainder_bytes = start_remainder_mut_slice.len();
1030
1031        // we are counting starting from the least significant bit, so to_le_bytes should be correct
1032        let rem = &rem.to_le_bytes()[0..remainder_bytes];
1033
1034        // this assumes that `[ToByteSlice]` can be copied directly
1035        // without calling `to_byte_slice` for each element,
1036        // which is correct for all ArrowNativeType implementations including u64.
1037        let src = rem.as_ptr();
1038        // Safety: `rem` has length `remainder_bytes`, `start_remainder_mut_slice` has length
1039        // `remainder_bytes`, and the two slices are non-overlapping (rem is derived from a
1040        // local `to_le_bytes()` call; start_remainder_mut_slice is the caller's mutable buffer).
1041        unsafe {
1042            std::ptr::copy_nonoverlapping(
1043                src,
1044                start_remainder_mut_slice.as_mut_ptr(),
1045                remainder_bytes,
1046            )
1047        };
1048    }
1049}
1050
1051/// Read remainder bits from a slice.
1052///
1053/// Reads the specified number of bits from slice and returns them as a u64.
1054///
1055/// # Arguments
1056///
1057/// * `remainder` - slice to the start of the bits
1058/// * `remainder_len` - Number of bits to read (must be < 64)
1059///
1060/// # Returns
1061///
1062/// A u64 containing the bits in the least significant positions
1063#[inline]
1064fn get_remainder_bits(remainder: &[u8], remainder_len: usize) -> u64 {
1065    assert!(remainder.len() < 64, "remainder_len must be less than 64");
1066    assert_eq!(
1067        remainder.len(),
1068        self::ceil(remainder_len, 8),
1069        "remainder and remainder len ceil must be the same"
1070    );
1071
1072    let bits = remainder
1073        .iter()
1074        .enumerate()
1075        .fold(0_u64, |acc, (index, &byte)| {
1076            acc | ((byte as u64) << (index * 8))
1077        });
1078
1079    bits & ((1 << remainder_len) - 1)
1080}
1081
1082/// Handle remainder bits (< 64 bits) for unary operations.
1083///
1084/// This function processes the bits that don't form a complete u64 chunk,
1085/// ensuring that bits outside the operation range are preserved.
1086///
1087/// # Arguments
1088///
1089/// * `op` - Unary operation to apply
1090/// * `start_remainder_mut` - Slice of bytes to write the remainder bits to
1091/// * `remainder_len` - Number of remainder bits
1092#[inline]
1093fn handle_mutable_buffer_remainder_unary<F>(
1094    op: &mut F,
1095    start_remainder_mut: &mut [u8],
1096    remainder_len: usize,
1097) where
1098    F: FnMut(u64) -> u64,
1099{
1100    // Only read from the slice the number of remainder bits
1101    let left_remainder_bits = get_remainder_bits(start_remainder_mut, remainder_len);
1102
1103    // Apply the operation
1104    let rem = op(left_remainder_bits);
1105
1106    // Write only the relevant bits back the result to the slice
1107    set_remainder_bits(start_remainder_mut, rem, remainder_len);
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use std::collections::HashSet;
1113
1114    use super::*;
1115    use crate::bit_iterator::BitIterator;
1116    use crate::{BooleanBuffer, BooleanBufferBuilder, MutableBuffer};
1117    use rand::rngs::StdRng;
1118    use rand::{RngExt, SeedableRng};
1119
1120    /// Reference for [`compress`]: gather the `mask`-selected bits of `value`
1121    /// into contiguous low bits, least-significant first
1122    fn compress_reference(value: u64, mask: u64) -> u64 {
1123        (0..64)
1124            .filter(|&i| (mask >> i) & 1 == 1)
1125            .enumerate()
1126            .map(|(dest, i)| ((value >> i) & 1) << dest)
1127            .sum()
1128    }
1129
1130    #[test]
1131    fn test_compress() {
1132        assert_eq!(compress(0b1010, 0b1111), 0b1010);
1133        assert_eq!(compress(0b1010, 0b1010), 0b11);
1134        assert_eq!(compress(0b1010, 0b0101), 0);
1135        assert_eq!(compress(u64::MAX, 0), 0);
1136        assert_eq!(compress(0, u64::MAX), 0);
1137        assert_eq!(compress(u64::MAX, u64::MAX), u64::MAX);
1138
1139        // On a `bmi2` build this validates the hardware `pext` path,
1140        // otherwise the portable fallback
1141        let mut rng = StdRng::seed_from_u64(42);
1142        for _ in 0..1024 {
1143            let (value, mask): (u64, u64) = rng.random();
1144            assert_eq!(
1145                compress(value, mask),
1146                compress_reference(value, mask),
1147                "value={value:#x} mask={mask:#x}"
1148            );
1149        }
1150    }
1151
1152    #[test]
1153    fn test_compress_portable() {
1154        // Every path of the fallback, whatever this build compiles
1155        // `compress` to: uniform random masks alone have about 32 kept bits
1156        // and would only reach `compress_bytes`
1157        let check = |value: u64, mask: u64| {
1158            let want = compress_reference(value, mask);
1159            let kept = mask.count_ones();
1160            assert_eq!(
1161                compress_portable(value, mask),
1162                want,
1163                "value={value:#x} mask={mask:#x} kept={kept}"
1164            );
1165        };
1166        let mut rng = StdRng::seed_from_u64(42);
1167
1168        // Every mask with at most two kept or at most two dropped bits
1169        let value: u64 = rng.random();
1170        check(value, 0);
1171        check(value, u64::MAX);
1172        for i in 0..64 {
1173            check(value, 1 << i);
1174            check(value, !(1 << i));
1175            for j in i + 1..64 {
1176                let two = (1 << i) | (1 << j);
1177                check(value, two);
1178                check(value, !two);
1179            }
1180        }
1181
1182        // Random masks at every density: ANDing random words thins them out,
1183        // ORing fills them in
1184        for _ in 0..4096 {
1185            let value: u64 = rng.random();
1186            let mut sparse = u64::MAX;
1187            let mut dense = 0;
1188            for _ in 0..8 {
1189                let word: u64 = rng.random();
1190                sparse &= word;
1191                dense |= word;
1192                check(value, sparse);
1193                check(value, dense);
1194            }
1195            // every popcount from 0 to 64
1196            let kept = rng.random_range(0..=64);
1197            let mut mask = 0_u64;
1198            while mask.count_ones() < kept {
1199                mask |= 1 << rng.random_range(0..64);
1200            }
1201            check(value, mask);
1202        }
1203    }
1204
1205    #[test]
1206    fn test_expand() {
1207        fn reference(values: u64, mask: u64) -> u64 {
1208            let mut expected = 0;
1209            let mut input_idx = 0;
1210            for output_idx in 0..64 {
1211                if mask & (1 << output_idx) != 0 {
1212                    expected |= ((values >> input_idx) & 1) << output_idx;
1213                    input_idx += 1;
1214                }
1215            }
1216            expected
1217        }
1218
1219        assert_eq!(expand(0b1010, 0b1111), 0b1010);
1220        assert_eq!(expand(0b11, 0b1010), 0b1010);
1221        assert_eq!(expand(u64::MAX, 0), 0);
1222        assert_eq!(expand(0, u64::MAX), 0);
1223        assert_eq!(expand(0, 0x5555_5555_5555_5555), 0);
1224        assert_eq!(expand(u64::MAX, u64::MAX), u64::MAX);
1225
1226        let mut rng = StdRng::seed_from_u64(0x2b7e_1516_28ae_d2a6);
1227
1228        // Masks with at most one unset bit or at most one set bit
1229        for bit in 0..64 {
1230            for mask in [u64::MAX, !(1 << bit), 1 << bit, 0] {
1231                for _ in 0..16 {
1232                    let values = rng.random::<u64>();
1233                    assert_eq!(expand(values, mask), reference(values, mask), "{mask:#x}");
1234                }
1235            }
1236        }
1237
1238        // Masks across the full density range, exercising the hardware `pdep`
1239        // path on a `bmi2` build or both portable loops otherwise.
1240        for _ in 0..20_000 {
1241            let density = rng.random_range(0.0..=1.0);
1242            let mask = (0..64).fold(0_u64, |mask, bit| {
1243                mask | ((rng.random_bool(density) as u64) << bit)
1244            });
1245            let values = rng.random::<u64>();
1246            assert_eq!(expand(compress(values, mask), mask), values & mask);
1247            assert_eq!(expand(values, mask), reference(values, mask), "{mask:#x}");
1248        }
1249    }
1250
1251    #[test]
1252    fn test_round_upto_multiple_of_64() {
1253        assert_eq!(0, round_upto_multiple_of_64(0));
1254        assert_eq!(64, round_upto_multiple_of_64(1));
1255        assert_eq!(64, round_upto_multiple_of_64(63));
1256        assert_eq!(64, round_upto_multiple_of_64(64));
1257        assert_eq!(128, round_upto_multiple_of_64(65));
1258        assert_eq!(192, round_upto_multiple_of_64(129));
1259    }
1260
1261    #[test]
1262    #[should_panic(expected = "failed to round upto multiple of 64")]
1263    fn test_round_upto_multiple_of_64_panic() {
1264        let _ = round_upto_multiple_of_64(usize::MAX);
1265    }
1266
1267    #[test]
1268    #[should_panic(expected = "failed to round to next highest power of 2")]
1269    fn test_round_upto_panic() {
1270        let _ = round_upto_power_of_2(usize::MAX, 2);
1271    }
1272
1273    #[test]
1274    fn test_get_bit() {
1275        // 00001101
1276        assert!(get_bit(&[0b00001101], 0));
1277        assert!(!get_bit(&[0b00001101], 1));
1278        assert!(get_bit(&[0b00001101], 2));
1279        assert!(get_bit(&[0b00001101], 3));
1280
1281        // 01001001 01010010
1282        assert!(get_bit(&[0b01001001, 0b01010010], 0));
1283        assert!(!get_bit(&[0b01001001, 0b01010010], 1));
1284        assert!(!get_bit(&[0b01001001, 0b01010010], 2));
1285        assert!(get_bit(&[0b01001001, 0b01010010], 3));
1286        assert!(!get_bit(&[0b01001001, 0b01010010], 4));
1287        assert!(!get_bit(&[0b01001001, 0b01010010], 5));
1288        assert!(get_bit(&[0b01001001, 0b01010010], 6));
1289        assert!(!get_bit(&[0b01001001, 0b01010010], 7));
1290        assert!(!get_bit(&[0b01001001, 0b01010010], 8));
1291        assert!(get_bit(&[0b01001001, 0b01010010], 9));
1292        assert!(!get_bit(&[0b01001001, 0b01010010], 10));
1293        assert!(!get_bit(&[0b01001001, 0b01010010], 11));
1294        assert!(get_bit(&[0b01001001, 0b01010010], 12));
1295        assert!(!get_bit(&[0b01001001, 0b01010010], 13));
1296        assert!(get_bit(&[0b01001001, 0b01010010], 14));
1297        assert!(!get_bit(&[0b01001001, 0b01010010], 15));
1298    }
1299
1300    pub fn seedable_rng() -> StdRng {
1301        StdRng::seed_from_u64(42)
1302    }
1303
1304    #[test]
1305    fn test_get_bit_raw() {
1306        const NUM_BYTE: usize = 10;
1307        let mut buf = [0; NUM_BYTE];
1308        let mut expected = vec![];
1309        let mut rng = seedable_rng();
1310        for i in 0..8 * NUM_BYTE {
1311            let b = rng.random_bool(0.5);
1312            expected.push(b);
1313            if b {
1314                set_bit(&mut buf[..], i)
1315            }
1316        }
1317
1318        let raw_ptr = buf.as_ptr();
1319        for (i, b) in expected.iter().enumerate() {
1320            unsafe {
1321                assert_eq!(*b, get_bit_raw(raw_ptr, i));
1322            }
1323        }
1324    }
1325
1326    #[test]
1327    fn test_set_bit() {
1328        let mut b = [0b00000010];
1329        set_bit(&mut b, 0);
1330        assert_eq!([0b00000011], b);
1331        set_bit(&mut b, 1);
1332        assert_eq!([0b00000011], b);
1333        set_bit(&mut b, 7);
1334        assert_eq!([0b10000011], b);
1335    }
1336
1337    #[test]
1338    fn test_unset_bit() {
1339        let mut b = [0b11111101];
1340        unset_bit(&mut b, 0);
1341        assert_eq!([0b11111100], b);
1342        unset_bit(&mut b, 1);
1343        assert_eq!([0b11111100], b);
1344        unset_bit(&mut b, 7);
1345        assert_eq!([0b01111100], b);
1346    }
1347
1348    #[test]
1349    fn test_set_bit_raw() {
1350        const NUM_BYTE: usize = 10;
1351        let mut buf = vec![0; NUM_BYTE];
1352        let mut expected = vec![];
1353        let mut rng = seedable_rng();
1354        for i in 0..8 * NUM_BYTE {
1355            let b = rng.random_bool(0.5);
1356            expected.push(b);
1357            if b {
1358                unsafe {
1359                    set_bit_raw(buf.as_mut_ptr(), i);
1360                }
1361            }
1362        }
1363
1364        let raw_ptr = buf.as_ptr();
1365        for (i, b) in expected.iter().enumerate() {
1366            unsafe {
1367                assert_eq!(*b, get_bit_raw(raw_ptr, i));
1368            }
1369        }
1370    }
1371
1372    #[test]
1373    fn test_unset_bit_raw() {
1374        const NUM_BYTE: usize = 10;
1375        let mut buf = vec![255; NUM_BYTE];
1376        let mut expected = vec![];
1377        let mut rng = seedable_rng();
1378        for i in 0..8 * NUM_BYTE {
1379            let b = rng.random_bool(0.5);
1380            expected.push(b);
1381            if !b {
1382                unsafe {
1383                    unset_bit_raw(buf.as_mut_ptr(), i);
1384                }
1385            }
1386        }
1387
1388        let raw_ptr = buf.as_ptr();
1389        for (i, b) in expected.iter().enumerate() {
1390            unsafe {
1391                assert_eq!(*b, get_bit_raw(raw_ptr, i));
1392            }
1393        }
1394    }
1395
1396    #[test]
1397    fn test_get_set_bit_roundtrip() {
1398        const NUM_BYTES: usize = 10;
1399        const NUM_SETS: usize = 10;
1400
1401        let mut buffer: [u8; NUM_BYTES * 8] = [0; NUM_BYTES * 8];
1402        let mut v = HashSet::new();
1403        let mut rng = seedable_rng();
1404        for _ in 0..NUM_SETS {
1405            let offset = rng.random_range(0..8 * NUM_BYTES);
1406            v.insert(offset);
1407            set_bit(&mut buffer[..], offset);
1408        }
1409        for i in 0..NUM_BYTES * 8 {
1410            assert_eq!(v.contains(&i), get_bit(&buffer[..], i));
1411        }
1412    }
1413
1414    #[test]
1415    fn test_ceil() {
1416        assert_eq!(ceil(0, 1), 0);
1417        assert_eq!(ceil(1, 1), 1);
1418        assert_eq!(ceil(1, 2), 1);
1419        assert_eq!(ceil(1, 8), 1);
1420        assert_eq!(ceil(7, 8), 1);
1421        assert_eq!(ceil(8, 8), 1);
1422        assert_eq!(ceil(9, 8), 2);
1423        assert_eq!(ceil(9, 9), 1);
1424        assert_eq!(ceil(10000000000, 10), 1000000000);
1425        assert_eq!(ceil(10, 10000000000), 1);
1426        assert_eq!(ceil(10000000000, 1000000000), 10);
1427    }
1428
1429    #[test]
1430    fn test_read_up_to() {
1431        let all_ones = &[0b10111001, 0b10001100];
1432
1433        for (bit_offset, expected) in [
1434            (0, 0b00000001),
1435            (1, 0b00000000),
1436            (2, 0b00000000),
1437            (3, 0b00000001),
1438            (4, 0b00000001),
1439            (5, 0b00000001),
1440            (6, 0b00000000),
1441            (7, 0b00000001),
1442        ] {
1443            let result = read_up_to_byte_from_offset(all_ones, 1, bit_offset);
1444            assert_eq!(
1445                result, expected,
1446                "failed at bit_offset {bit_offset}. result, expected:\n{result:08b}\n{expected:08b}"
1447            );
1448        }
1449
1450        for (bit_offset, expected) in [
1451            (0, 0b00000001),
1452            (1, 0b00000000),
1453            (2, 0b00000010),
1454            (3, 0b00000011),
1455            (4, 0b00000011),
1456            (5, 0b00000001),
1457            (6, 0b00000010),
1458            (7, 0b00000001),
1459        ] {
1460            let result = read_up_to_byte_from_offset(all_ones, 2, bit_offset);
1461            assert_eq!(
1462                result, expected,
1463                "failed at bit_offset {bit_offset}. result, expected:\n{result:08b}\n{expected:08b}"
1464            );
1465        }
1466
1467        for (bit_offset, expected) in [
1468            (0, 0b00111001),
1469            (1, 0b00011100),
1470            (2, 0b00101110),
1471            (3, 0b00010111),
1472            (4, 0b00001011),
1473            (5, 0b00100101),
1474            (6, 0b00110010),
1475            (7, 0b00011001),
1476        ] {
1477            let result = read_up_to_byte_from_offset(all_ones, 6, bit_offset);
1478            assert_eq!(
1479                result, expected,
1480                "failed at bit_offset {bit_offset}. result, expected:\n{result:08b}\n{expected:08b}"
1481            );
1482        }
1483
1484        for (bit_offset, expected) in [
1485            (0, 0b00111001),
1486            (1, 0b01011100),
1487            (2, 0b00101110),
1488            (3, 0b00010111),
1489            (4, 0b01001011),
1490            (5, 0b01100101),
1491            (6, 0b00110010),
1492            (7, 0b00011001),
1493        ] {
1494            let result = read_up_to_byte_from_offset(all_ones, 7, bit_offset);
1495            assert_eq!(
1496                result, expected,
1497                "failed at bit_offset {bit_offset}. result, expected:\n{result:08b}\n{expected:08b}"
1498            );
1499        }
1500    }
1501
1502    /// Verifies that a unary operation applied to a buffer using u64 chunks
1503    /// is the same as applying the operation bit by bit.
1504    fn test_mutable_buffer_bin_op_helper<F, G>(
1505        left_data: &[bool],
1506        right_data: &[bool],
1507        left_offset_in_bits: usize,
1508        right_offset_in_bits: usize,
1509        len_in_bits: usize,
1510        op: F,
1511        mut expected_op: G,
1512    ) where
1513        F: FnMut(u64, u64) -> u64,
1514        G: FnMut(bool, bool) -> bool,
1515    {
1516        let mut left_buffer = BooleanBufferBuilder::new(len_in_bits);
1517        left_buffer.append_slice(left_data);
1518        let right_buffer = BooleanBuffer::from(right_data);
1519
1520        let expected: Vec<bool> = left_data
1521            .iter()
1522            .skip(left_offset_in_bits)
1523            .zip(right_data.iter().skip(right_offset_in_bits))
1524            .take(len_in_bits)
1525            .map(|(l, r)| expected_op(*l, *r))
1526            .collect();
1527
1528        let before = left_buffer.as_slice().to_vec();
1529
1530        apply_bitwise_binary_op(
1531            left_buffer.as_slice_mut(),
1532            left_offset_in_bits,
1533            right_buffer.inner(),
1534            right_offset_in_bits,
1535            len_in_bits,
1536            op,
1537        );
1538
1539        let result: Vec<bool> =
1540            BitIterator::new(left_buffer.as_slice(), left_offset_in_bits, len_in_bits).collect();
1541
1542        assert_eq!(
1543            result, expected,
1544            "Failed with left_offset={left_offset_in_bits}, right_offset={right_offset_in_bits}, len={len_in_bits}"
1545        );
1546
1547        assert_bits_outside_range_preserved(
1548            &before,
1549            left_buffer.as_slice(),
1550            left_offset_in_bits,
1551            len_in_bits,
1552            &format!(
1553                "left_offset={left_offset_in_bits}, right_offset={right_offset_in_bits}, len={len_in_bits}"
1554            ),
1555        );
1556    }
1557
1558    /// Asserts that every bit outside `offset_in_bits..offset_in_bits + len_in_bits`
1559    /// is identical in `before` and `after`.
1560    fn assert_bits_outside_range_preserved(
1561        before: &[u8],
1562        after: &[u8],
1563        offset_in_bits: usize,
1564        len_in_bits: usize,
1565        context: &str,
1566    ) {
1567        assert_eq!(before.len(), after.len());
1568        for i in 0..before.len() * 8 {
1569            if i >= offset_in_bits && i < offset_in_bits + len_in_bits {
1570                continue;
1571            }
1572            assert_eq!(
1573                get_bit(before, i),
1574                get_bit(after, i),
1575                "bit {i} outside the requested range was modified ({context})"
1576            );
1577        }
1578    }
1579
1580    /// Verifies that a unary operation applied to a buffer using u64 chunks
1581    /// is the same as applying the operation bit by bit.
1582    fn test_mutable_buffer_unary_op_helper<F, G>(
1583        data: &[bool],
1584        offset_in_bits: usize,
1585        len_in_bits: usize,
1586        op: F,
1587        mut expected_op: G,
1588    ) where
1589        F: FnMut(u64) -> u64,
1590        G: FnMut(bool) -> bool,
1591    {
1592        let mut buffer = BooleanBufferBuilder::new(len_in_bits);
1593        buffer.append_slice(data);
1594
1595        let expected: Vec<bool> = data
1596            .iter()
1597            .skip(offset_in_bits)
1598            .take(len_in_bits)
1599            .map(|b| expected_op(*b))
1600            .collect();
1601
1602        let before = buffer.as_slice().to_vec();
1603
1604        apply_bitwise_unary_op(buffer.as_slice_mut(), offset_in_bits, len_in_bits, op);
1605
1606        let result: Vec<bool> =
1607            BitIterator::new(buffer.as_slice(), offset_in_bits, len_in_bits).collect();
1608
1609        assert_eq!(
1610            result, expected,
1611            "Failed with offset={offset_in_bits}, len={len_in_bits}"
1612        );
1613
1614        assert_bits_outside_range_preserved(
1615            &before,
1616            buffer.as_slice(),
1617            offset_in_bits,
1618            len_in_bits,
1619            &format!("offset={offset_in_bits}, len={len_in_bits}"),
1620        );
1621    }
1622
1623    // Helper to create test data of specific length
1624    fn create_test_data(len: usize) -> (Vec<bool>, Vec<bool>) {
1625        let mut rng = rand::rng();
1626        let left: Vec<bool> = (0..len).map(|_| rng.random_bool(0.5)).collect();
1627        let right: Vec<bool> = (0..len).map(|_| rng.random_bool(0.5)).collect();
1628        (left, right)
1629    }
1630
1631    /// Test all binary operations (AND, OR, XOR) with the given parameters
1632    fn test_all_binary_ops(
1633        left_data: &[bool],
1634        right_data: &[bool],
1635        left_offset_in_bits: usize,
1636        right_offset_in_bits: usize,
1637        len_in_bits: usize,
1638    ) {
1639        // Test AND
1640        test_mutable_buffer_bin_op_helper(
1641            left_data,
1642            right_data,
1643            left_offset_in_bits,
1644            right_offset_in_bits,
1645            len_in_bits,
1646            |a, b| a & b,
1647            |a, b| a & b,
1648        );
1649
1650        // Test OR
1651        test_mutable_buffer_bin_op_helper(
1652            left_data,
1653            right_data,
1654            left_offset_in_bits,
1655            right_offset_in_bits,
1656            len_in_bits,
1657            |a, b| a | b,
1658            |a, b| a | b,
1659        );
1660
1661        // Test XOR
1662        test_mutable_buffer_bin_op_helper(
1663            left_data,
1664            right_data,
1665            left_offset_in_bits,
1666            right_offset_in_bits,
1667            len_in_bits,
1668            |a, b| a ^ b,
1669            |a, b| a ^ b,
1670        );
1671    }
1672
1673    // ===== Combined Binary Operation Tests =====
1674
1675    #[test]
1676    fn test_binary_ops_less_than_byte() {
1677        let (left, right) = create_test_data(4);
1678        test_all_binary_ops(&left, &right, 0, 0, 4);
1679    }
1680
1681    #[test]
1682    fn test_binary_ops_less_than_byte_across_boundary() {
1683        let (left, right) = create_test_data(16);
1684        test_all_binary_ops(&left, &right, 6, 6, 4);
1685    }
1686
1687    #[test]
1688    fn test_binary_ops_exactly_byte() {
1689        let (left, right) = create_test_data(16);
1690        test_all_binary_ops(&left, &right, 0, 0, 8);
1691    }
1692
1693    #[test]
1694    fn test_binary_ops_more_than_byte_less_than_u64() {
1695        let (left, right) = create_test_data(64);
1696        test_all_binary_ops(&left, &right, 0, 0, 32);
1697    }
1698
1699    #[test]
1700    fn test_binary_ops_exactly_u64() {
1701        let (left, right) = create_test_data(180);
1702        test_all_binary_ops(&left, &right, 0, 0, 64);
1703        test_all_binary_ops(&left, &right, 64, 9, 64);
1704        test_all_binary_ops(&left, &right, 8, 100, 64);
1705        test_all_binary_ops(&left, &right, 1, 15, 64);
1706        test_all_binary_ops(&left, &right, 12, 10, 64);
1707        test_all_binary_ops(&left, &right, 180 - 64, 2, 64);
1708    }
1709
1710    #[test]
1711    fn test_binary_ops_more_than_u64_not_multiple() {
1712        let (left, right) = create_test_data(200);
1713        test_all_binary_ops(&left, &right, 0, 0, 100);
1714    }
1715
1716    #[test]
1717    fn test_binary_ops_exactly_multiple_u64() {
1718        let (left, right) = create_test_data(256);
1719        test_all_binary_ops(&left, &right, 0, 0, 128);
1720    }
1721
1722    #[test]
1723    fn test_binary_ops_more_than_multiple_u64() {
1724        let (left, right) = create_test_data(300);
1725        test_all_binary_ops(&left, &right, 0, 0, 200);
1726    }
1727
1728    #[test]
1729    fn test_binary_ops_byte_aligned_no_remainder() {
1730        let (left, right) = create_test_data(200);
1731        test_all_binary_ops(&left, &right, 0, 0, 128);
1732    }
1733
1734    #[test]
1735    fn test_binary_ops_byte_aligned_with_remainder() {
1736        let (left, right) = create_test_data(200);
1737        test_all_binary_ops(&left, &right, 0, 0, 100);
1738    }
1739
1740    #[test]
1741    fn test_binary_ops_not_byte_aligned_no_remainder() {
1742        let (left, right) = create_test_data(200);
1743        test_all_binary_ops(&left, &right, 3, 3, 128);
1744    }
1745
1746    #[test]
1747    fn test_binary_ops_not_byte_aligned_with_remainder() {
1748        let (left, right) = create_test_data(200);
1749        test_all_binary_ops(&left, &right, 5, 5, 100);
1750    }
1751
1752    #[test]
1753    fn test_binary_ops_different_offsets() {
1754        let (left, right) = create_test_data(200);
1755        test_all_binary_ops(&left, &right, 3, 7, 50);
1756    }
1757
1758    #[test]
1759    fn test_binary_ops_offsets_greater_than_8_less_than_64() {
1760        let (left, right) = create_test_data(200);
1761        test_all_binary_ops(&left, &right, 13, 27, 100);
1762    }
1763
1764    // ===== NOT (Unary) Operation Tests =====
1765
1766    #[test]
1767    fn test_not_less_than_byte() {
1768        let data = vec![true, false, true, false];
1769        test_mutable_buffer_unary_op_helper(&data, 0, 4, |a| !a, |a| !a);
1770    }
1771
1772    #[test]
1773    fn test_not_less_than_byte_across_boundary() {
1774        let data: Vec<bool> = (0..16).map(|i| i % 2 == 0).collect();
1775        test_mutable_buffer_unary_op_helper(&data, 6, 4, |a| !a, |a| !a);
1776    }
1777
1778    #[test]
1779    fn test_not_exactly_byte() {
1780        let data: Vec<bool> = (0..16).map(|i| i % 2 == 0).collect();
1781        test_mutable_buffer_unary_op_helper(&data, 0, 8, |a| !a, |a| !a);
1782    }
1783
1784    #[test]
1785    fn test_not_more_than_byte_less_than_u64() {
1786        let data: Vec<bool> = (0..64).map(|i| i % 2 == 0).collect();
1787        test_mutable_buffer_unary_op_helper(&data, 0, 32, |a| !a, |a| !a);
1788    }
1789
1790    #[test]
1791    fn test_not_exactly_u64() {
1792        let data: Vec<bool> = (0..128).map(|i| i % 2 == 0).collect();
1793        test_mutable_buffer_unary_op_helper(&data, 0, 64, |a| !a, |a| !a);
1794    }
1795
1796    #[test]
1797    fn test_not_more_than_u64_not_multiple() {
1798        let data: Vec<bool> = (0..200).map(|i| i % 2 == 0).collect();
1799        test_mutable_buffer_unary_op_helper(&data, 0, 100, |a| !a, |a| !a);
1800    }
1801
1802    #[test]
1803    fn test_not_exactly_multiple_u64() {
1804        let data: Vec<bool> = (0..256).map(|i| i % 2 == 0).collect();
1805        test_mutable_buffer_unary_op_helper(&data, 0, 128, |a| !a, |a| !a);
1806    }
1807
1808    #[test]
1809    fn test_not_more_than_multiple_u64() {
1810        let data: Vec<bool> = (0..300).map(|i| i % 2 == 0).collect();
1811        test_mutable_buffer_unary_op_helper(&data, 0, 200, |a| !a, |a| !a);
1812    }
1813
1814    #[test]
1815    fn test_not_byte_aligned_no_remainder() {
1816        let data: Vec<bool> = (0..200).map(|i| i % 2 == 0).collect();
1817        test_mutable_buffer_unary_op_helper(&data, 0, 128, |a| !a, |a| !a);
1818    }
1819
1820    #[test]
1821    fn test_not_byte_aligned_with_remainder() {
1822        let data: Vec<bool> = (0..200).map(|i| i % 2 == 0).collect();
1823        test_mutable_buffer_unary_op_helper(&data, 0, 100, |a| !a, |a| !a);
1824    }
1825
1826    #[test]
1827    fn test_not_not_byte_aligned_no_remainder() {
1828        let data: Vec<bool> = (0..200).map(|i| i % 2 == 0).collect();
1829        test_mutable_buffer_unary_op_helper(&data, 3, 128, |a| !a, |a| !a);
1830    }
1831
1832    #[test]
1833    fn test_not_not_byte_aligned_with_remainder() {
1834        let data: Vec<bool> = (0..200).map(|i| i % 2 == 0).collect();
1835        test_mutable_buffer_unary_op_helper(&data, 5, 100, |a| !a, |a| !a);
1836    }
1837
1838    // ===== Edge Cases =====
1839
1840    #[test]
1841    fn test_empty_length() {
1842        let (left, right) = create_test_data(16);
1843        test_all_binary_ops(&left, &right, 0, 0, 0);
1844    }
1845
1846    #[test]
1847    fn test_single_bit() {
1848        let (left, right) = create_test_data(16);
1849        test_all_binary_ops(&left, &right, 0, 0, 1);
1850    }
1851
1852    #[test]
1853    fn test_single_bit_at_offset() {
1854        let (left, right) = create_test_data(16);
1855        test_all_binary_ops(&left, &right, 7, 7, 1);
1856    }
1857
1858    #[test]
1859    fn test_not_single_bit() {
1860        let data = vec![true, false, true, false];
1861        test_mutable_buffer_unary_op_helper(&data, 0, 1, |a| !a, |a| !a);
1862    }
1863
1864    #[test]
1865    fn test_not_empty_length() {
1866        let data = vec![true, false, true, false];
1867        test_mutable_buffer_unary_op_helper(&data, 0, 0, |a| !a, |a| !a);
1868    }
1869
1870    #[test]
1871    fn test_less_than_byte_unaligned_and_not_enough_bits() {
1872        let left_offset_in_bits = 2;
1873        let right_offset_in_bits = 4;
1874        let len_in_bits = 1;
1875
1876        // Single byte
1877        let right = (0..8).map(|i| (i / 2) % 2 == 0).collect::<Vec<_>>();
1878        // less than a byte
1879        let left = (0..3).map(|i| i % 2 == 0).collect::<Vec<_>>();
1880        test_all_binary_ops(
1881            &left,
1882            &right,
1883            left_offset_in_bits,
1884            right_offset_in_bits,
1885            len_in_bits,
1886        );
1887    }
1888
1889    /// Ranges that start and end inside the same non-byte-aligned byte must not
1890    /// touch the trailing bits of that byte.
1891    #[test]
1892    fn test_ops_ending_inside_the_first_partial_byte() {
1893        let (left, right) = create_test_data(32);
1894        for offset in 1..8 {
1895            // Inclusive so the range ending exactly on the byte boundary is covered too
1896            for len in 1..=(8 - offset) {
1897                test_all_binary_ops(&left, &right, offset, offset, len);
1898                test_all_binary_ops(&left, &right, offset, (offset + 3) % 8, len);
1899                test_mutable_buffer_unary_op_helper(&left, offset, len, |a| !a, |a| !a);
1900            }
1901        }
1902    }
1903
1904    #[test]
1905    fn test_and_within_first_partial_byte_preserves_trailing_bits() {
1906        let mut left = vec![0b11111111u8, 0b11111111u8];
1907        let right = vec![0b00000000u8, 0b00000000u8];
1908        // AND a single bit at bit offset 1: only bit 1 may be cleared
1909        apply_bitwise_binary_op(&mut left, 1, &right, 0, 1, |a, b| a & b);
1910        assert_eq!(left, vec![0b11111101u8, 0b11111111u8]);
1911    }
1912
1913    #[test]
1914    fn test_not_within_first_partial_byte_preserves_trailing_bits() {
1915        let mut buffer = vec![0b00000000u8];
1916        // NOT two bits at bit offset 3: only bits 3 and 4 may be flipped
1917        apply_bitwise_unary_op(&mut buffer, 3, 2, |a| !a);
1918        assert_eq!(buffer, vec![0b00011000u8]);
1919    }
1920
1921    /// When the remainder spans more than one byte, the byte holding the end of the
1922    /// range is the *last* byte of the remainder, not the first. Its bits above the
1923    /// remainder must survive.
1924    #[test]
1925    fn test_or_with_multi_byte_remainder_preserves_boundary_bits() {
1926        let mut left = vec![0b00000000u8, 0b00000000u8, 0b11110000u8];
1927        let right = vec![0b11111111u8, 0b11111111u8, 0b11111111u8];
1928        // OR over 20 bits: bits 20..24 of `left` are outside the range and must stay set
1929        apply_bitwise_binary_op(&mut left, 0, &right, 0, 20, |a, b| a | b);
1930        assert_eq!(
1931            left,
1932            vec![0b11111111u8, 0b11111111u8, 0b11111111u8],
1933            "the boundary byte lost its out-of-range bits"
1934        );
1935    }
1936
1937    #[test]
1938    fn test_not_with_multi_byte_remainder_preserves_boundary_bits() {
1939        let mut buffer = vec![0b00000000u8, 0b00000000u8, 0b11111111u8];
1940        // NOT over 20 bits: only bits 16..20 of the last byte may be flipped
1941        apply_bitwise_unary_op(&mut buffer, 0, 20, |a| !a);
1942        assert_eq!(
1943            buffer,
1944            vec![0b11111111u8, 0b11111111u8, 0b11110000u8],
1945            "the boundary byte lost its out-of-range bits"
1946        );
1947    }
1948
1949    #[test]
1950    fn test_bitwise_binary_op_offset_out_of_bounds() {
1951        let input = vec![0b10101010u8, 0b01010101u8];
1952        let mut buffer = MutableBuffer::new(2); // space for 16 bits
1953        buffer.extend_from_slice(&input); // only 2 bytes
1954        apply_bitwise_binary_op(
1955            buffer.as_slice_mut(),
1956            100, // exceeds buffer length, becomes a noop
1957            [0b11110000u8, 0b00001111u8],
1958            0,
1959            0,
1960            |a, b| a & b,
1961        );
1962        assert_eq!(buffer.as_slice(), &input);
1963    }
1964
1965    #[test]
1966    #[should_panic(expected = "assertion failed: last_offset <= buffer.len()")]
1967    fn test_bitwise_binary_op_length_out_of_bounds() {
1968        let mut buffer = MutableBuffer::new(2); // space for 16 bits
1969        buffer.extend_from_slice(&[0b10101010u8, 0b01010101u8]); // only 2 bytes
1970        apply_bitwise_binary_op(
1971            buffer.as_slice_mut(),
1972            0, // exceeds buffer length
1973            [0b11110000u8, 0b00001111u8],
1974            0,
1975            100,
1976            |a, b| a & b,
1977        );
1978        assert_eq!(buffer.as_slice(), &[0b10101010u8, 0b01010101u8]);
1979    }
1980
1981    #[test]
1982    #[should_panic(expected = "offset + len out of bounds")]
1983    fn test_bitwise_binary_op_right_len_out_of_bounds() {
1984        let mut buffer = MutableBuffer::new(2); // space for 16 bits
1985        buffer.extend_from_slice(&[0b10101010u8, 0b01010101u8]); // only 2 bytes
1986        apply_bitwise_binary_op(
1987            buffer.as_slice_mut(),
1988            0, // exceeds buffer length
1989            [0b11110000u8, 0b00001111u8],
1990            1000,
1991            16,
1992            |a, b| a & b,
1993        );
1994        assert_eq!(buffer.as_slice(), &[0b10101010u8, 0b01010101u8]);
1995    }
1996
1997    #[test]
1998    #[should_panic(expected = "the len is 2 but the index is 12")]
1999    fn test_bitwise_unary_op_offset_out_of_bounds() {
2000        let input = vec![0b10101010u8, 0b01010101u8];
2001        let mut buffer = MutableBuffer::new(2); // space for 16 bits
2002        buffer.extend_from_slice(&input); // only 2 bytes
2003        apply_bitwise_unary_op(
2004            buffer.as_slice_mut(),
2005            100, // exceeds buffer length, becomes a noop
2006            8,
2007            |a| !a,
2008        );
2009        assert_eq!(buffer.as_slice(), &input);
2010    }
2011
2012    #[test]
2013    #[should_panic(expected = "assertion failed: last_offset <= buffer.len()")]
2014    fn test_bitwise_unary_op_length_out_of_bounds2() {
2015        let input = vec![0b10101010u8, 0b01010101u8];
2016        let mut buffer = MutableBuffer::new(2); // space for 16 bits
2017        buffer.extend_from_slice(&input); // only 2 bytes
2018        apply_bitwise_unary_op(
2019            buffer.as_slice_mut(),
2020            3,   // start at bit 3, to exercise different path
2021            100, // exceeds buffer length
2022            |a| !a,
2023        );
2024        assert_eq!(buffer.as_slice(), &input);
2025    }
2026}