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