Skip to main content

arrow_buffer/util/
bit_mask.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 packed bit masks
19
20use crate::bit_util::ceil;
21
22/// Util function to set bits in a slice of bytes.
23///
24/// This will sets all bits on `write_data` in the range `[offset_write..offset_write+len]`
25/// to be equal to the bits in `data` in the range `[offset_read..offset_read+len]`
26/// returns the number of `0` bits `data[offset_read..offset_read+len]`
27/// `offset_write`, `offset_read`, and `len` are in terms of bits
28///
29/// # Panics
30///
31/// Panics if `offset_write + len` exceeds `write_data.len() * 8`, or if
32/// `offset_read + len` exceeds `data.len() * 8`
33pub fn set_bits(
34    write_data: &mut [u8],
35    data: &[u8],
36    offset_write: usize,
37    offset_read: usize,
38    len: usize,
39) -> usize {
40    assert!(
41        offset_write
42            .checked_add(len)
43            .expect("operation will overflow write buffer")
44            <= write_data.len() * 8
45    );
46    assert!(
47        offset_read
48            .checked_add(len)
49            .expect("operation will overflow read buffer")
50            <= data.len() * 8
51    );
52    let mut null_count = 0;
53    let mut acc = 0;
54    while len > acc {
55        // SAFETY: the arguments to `set_upto_64bits` are within the valid range because
56        // (offset_write + acc) + (len - acc) == offset_write + len <= write_data.len() * 8
57        // (offset_read + acc) + (len - acc) == offset_read + len <= data.len() * 8
58        let (n, len_set) = unsafe {
59            set_upto_64bits(
60                write_data,
61                data,
62                offset_write + acc,
63                offset_read + acc,
64                len - acc,
65            )
66        };
67        null_count += n;
68        acc += len_set;
69    }
70
71    null_count
72}
73
74/// Similar to `set_bits` but sets only upto 64 bits, actual number of bits set may vary.
75/// Returns a pair of the number of `0` bits and the number of bits set
76///
77/// # Safety
78/// The caller must ensure all arguments are within the valid range.
79#[inline]
80unsafe fn set_upto_64bits(
81    write_data: &mut [u8],
82    data: &[u8],
83    offset_write: usize,
84    offset_read: usize,
85    len: usize,
86) -> (usize, usize) {
87    let read_byte = offset_read / 8;
88    let read_shift = offset_read % 8;
89    let write_byte = offset_write / 8;
90    let write_shift = offset_write % 8;
91
92    if len >= 64 {
93        let chunk = unsafe { data.as_ptr().add(read_byte).cast::<u64>().read_unaligned() };
94        if read_shift == 0 {
95            if write_shift == 0 {
96                // no shifting necessary
97                let len = 64;
98                let null_count = chunk.count_zeros() as usize;
99                unsafe { write_u64_bytes(write_data, write_byte, chunk) };
100                (null_count, len)
101            } else {
102                // only write shifting necessary
103                let len = 64 - write_shift;
104                let chunk = chunk << write_shift;
105                let null_count = len - chunk.count_ones() as usize;
106                unsafe { or_write_u64_bytes(write_data, write_byte, chunk) };
107                (null_count, len)
108            }
109        } else if write_shift == 0 {
110            // only read shifting necessary
111            let len = 64 - 8; // 56 bits so the next set_upto_64bits call will see write_shift == 0
112            let chunk = (chunk >> read_shift) & 0x00FFFFFFFFFFFFFF; // 56 bits mask
113            let null_count = len - chunk.count_ones() as usize;
114            unsafe { write_u64_bytes(write_data, write_byte, chunk) };
115            (null_count, len)
116        } else {
117            let len = 64 - std::cmp::max(read_shift, write_shift);
118            let chunk = (chunk >> read_shift) << write_shift;
119            let null_count = len - chunk.count_ones() as usize;
120            unsafe { or_write_u64_bytes(write_data, write_byte, chunk) };
121            (null_count, len)
122        }
123    } else if len == 1 {
124        let byte_chunk = (unsafe { data.get_unchecked(read_byte) } >> read_shift) & 1;
125        unsafe { *write_data.get_unchecked_mut(write_byte) |= byte_chunk << write_shift };
126        ((byte_chunk ^ 1) as usize, 1)
127    } else {
128        let len = std::cmp::min(len, 64 - std::cmp::max(read_shift, write_shift));
129        let bytes = ceil(len + read_shift, 8);
130        // SAFETY: the args of `read_bytes_to_u64` are valid as read_byte + bytes <= data.len()
131        let chunk = unsafe { read_bytes_to_u64(data, read_byte, bytes) };
132        let mask = u64::MAX >> (64 - len);
133        let chunk = (chunk >> read_shift) & mask; // masking to read `len` bits only
134        let chunk = chunk << write_shift; // shifting back to align with `write_data`
135        let null_count = len - chunk.count_ones() as usize;
136        let bytes = ceil(len + write_shift, 8);
137        for (i, c) in chunk.to_le_bytes().iter().enumerate().take(bytes) {
138            unsafe { *write_data.get_unchecked_mut(write_byte + i) |= c };
139        }
140        (null_count, len)
141    }
142}
143
144/// # Safety
145/// The caller must ensure `data` has `offset..(offset + 8)` range, and `count <= 8`.
146#[inline]
147unsafe fn read_bytes_to_u64(data: &[u8], offset: usize, count: usize) -> u64 {
148    debug_assert!(count <= 8);
149    let mut tmp: u64 = 0;
150    let src = unsafe { data.as_ptr().add(offset) };
151    unsafe { std::ptr::copy_nonoverlapping(src, std::ptr::from_mut(&mut tmp).cast::<u8>(), count) };
152    tmp
153}
154
155/// # Safety
156/// The caller must ensure `data` has `offset..(offset + 8)` range
157#[inline]
158unsafe fn write_u64_bytes(data: &mut [u8], offset: usize, chunk: u64) {
159    #[expect(
160        clippy::cast_ptr_alignment,
161        reason = "the pointer is only written through `write_unaligned`"
162    )]
163    let ptr = unsafe { data.as_mut_ptr().add(offset) }.cast::<u64>();
164    unsafe { ptr.write_unaligned(chunk) };
165}
166
167/// Similar to `write_u64_bytes`, but this method ORs the offset addressed `data` and `chunk`
168/// instead of overwriting
169///
170/// # Safety
171/// The caller must ensure `data` has `offset..(offset + 8)` range
172#[inline]
173unsafe fn or_write_u64_bytes(data: &mut [u8], offset: usize, chunk: u64) {
174    let ptr = unsafe { data.as_mut_ptr().add(offset) };
175    let chunk = chunk | (unsafe { *ptr }) as u64;
176    unsafe { ptr.cast::<u64>().write_unaligned(chunk) };
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::bit_util::{get_bit, set_bit, unset_bit};
183    use rand::prelude::StdRng;
184    use rand::{RngExt, SeedableRng, TryRng};
185    use std::fmt::Display;
186
187    #[test]
188    fn test_set_bits_aligned() {
189        SetBitsTest {
190            write_data: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
191            data: vec![
192                0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011, 0b11100111,
193                0b10100101,
194            ],
195            offset_write: 8,
196            offset_read: 0,
197            len: 64,
198            expected_data: vec![
199                0, 0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011,
200                0b11100111, 0b10100101, 0,
201            ],
202            expected_null_count: 24,
203        }
204        .verify();
205    }
206
207    #[test]
208    fn test_set_bits_unaligned_destination_start() {
209        SetBitsTest {
210            write_data: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
211            data: vec![
212                0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011, 0b11100111,
213                0b10100101,
214            ],
215            offset_write: 3,
216            offset_read: 0,
217            len: 64,
218            expected_data: vec![
219                0b00111000, 0b00101111, 0b11001101, 0b11011100, 0b01011110, 0b00011111, 0b00111110,
220                0b00101111, 0b00000101, 0b00000000,
221            ],
222            expected_null_count: 24,
223        }
224        .verify();
225    }
226
227    #[test]
228    fn test_set_bits_unaligned_destination_end() {
229        SetBitsTest {
230            write_data: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
231            data: vec![
232                0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011, 0b11100111,
233                0b10100101,
234            ],
235            offset_write: 8,
236            offset_read: 0,
237            len: 62,
238            expected_data: vec![
239                0, 0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011,
240                0b11100111, 0b00100101, 0,
241            ],
242            expected_null_count: 23,
243        }
244        .verify();
245    }
246
247    #[test]
248    fn test_set_bits_unaligned() {
249        SetBitsTest {
250            write_data: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
251            data: vec![
252                0b11100111, 0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011, 0b11100111,
253                0b10100101, 0b10011001, 0b11011011, 0b11101011, 0b11000011, 0b11100111, 0b10100101,
254                0b10011001, 0b11011011, 0b11101011, 0b11000011,
255            ],
256            offset_write: 3,
257            offset_read: 5,
258            len: 95,
259            expected_data: vec![
260                0b01111000, 0b01101001, 0b11100110, 0b11110110, 0b11111010, 0b11110000, 0b01111001,
261                0b01101001, 0b11100110, 0b11110110, 0b11111010, 0b11110000, 0b00000001,
262            ],
263            expected_null_count: 35,
264        }
265        .verify();
266    }
267
268    #[test]
269    fn set_bits_fuzz() {
270        let mut rng = StdRng::seed_from_u64(42);
271        let mut data = SetBitsTest::new();
272        for _ in 0..100 {
273            data.regen(&mut rng);
274            data.verify();
275        }
276    }
277
278    #[derive(Debug, Default)]
279    struct SetBitsTest {
280        /// target write data
281        write_data: Vec<u8>,
282        /// source data
283        data: Vec<u8>,
284        offset_write: usize,
285        offset_read: usize,
286        len: usize,
287        /// the expected contents of write_data after the test
288        expected_data: Vec<u8>,
289        /// the expected number of nulls copied at the end of the test
290        expected_null_count: usize,
291    }
292
293    /// prints a byte slice as a binary string like "01010101 10101010"
294    struct BinaryFormatter<'a>(&'a [u8]);
295    impl Display for BinaryFormatter<'_> {
296        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297            for byte in self.0 {
298                write!(f, "{byte:08b} ")?;
299            }
300            write!(f, " ")?;
301            Ok(())
302        }
303    }
304
305    impl Display for SetBitsTest {
306        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307            writeln!(f, "SetBitsTest {{")?;
308            writeln!(f, "  write_data:    {}", BinaryFormatter(&self.write_data))?;
309            writeln!(f, "  data:          {}", BinaryFormatter(&self.data))?;
310            writeln!(
311                f,
312                "  expected_data: {}",
313                BinaryFormatter(&self.expected_data)
314            )?;
315            writeln!(f, "  offset_write: {}", self.offset_write)?;
316            writeln!(f, "  offset_read: {}", self.offset_read)?;
317            writeln!(f, "  len: {}", self.len)?;
318            writeln!(f, "  expected_null_count: {}", self.expected_null_count)?;
319            writeln!(f, "}}")
320        }
321    }
322
323    impl SetBitsTest {
324        /// create a new instance of FuzzData
325        fn new() -> Self {
326            Self::default()
327        }
328
329        /// Update this instance's fields with randomly selected values and expected data
330        fn regen(&mut self, rng: &mut StdRng) {
331            //  (read) data
332            // ------------------+-----------------+-------
333            // .. offset_read .. | data            | ...
334            // ------------------+-----------------+-------
335
336            // Write data
337            // -------------------+-----------------+-------
338            // .. offset_write .. | (data to write) | ...
339            // -------------------+-----------------+-------
340
341            // length of data to copy
342            let len = rng.random_range(0..=200);
343
344            // randomly pick where we will write to
345            let offset_write_bits = rng.random_range(0..=200);
346            let offset_write_bytes = if offset_write_bits % 8 == 0 {
347                offset_write_bits / 8
348            } else {
349                (offset_write_bits / 8) + 1
350            };
351            let extra_write_data_bytes = rng.random_range(0..=5); // ensure 0 shows up often
352
353            // randomly decide where we will read from
354            let extra_read_data_bytes = rng.random_range(0..=5); // make sure 0 shows up often
355            let offset_read_bits = rng.random_range(0..=200);
356            let offset_read_bytes = if offset_read_bits % 8 != 0 {
357                (offset_read_bits / 8) + 1
358            } else {
359                offset_read_bits / 8
360            };
361
362            // create space for writing
363            self.write_data.clear();
364            self.write_data
365                .resize(offset_write_bytes + len + extra_write_data_bytes, 0);
366
367            // interestingly set_bits seems to assume the output is already zeroed
368            // the fuzz tests fail when this is uncommented
369            //self.write_data.try_fill(rng).unwrap();
370            self.offset_write = offset_write_bits;
371
372            // make source data
373            self.data
374                .resize(offset_read_bytes + len + extra_read_data_bytes, 0);
375            // fill source data with random bytes
376            rng.try_fill_bytes(self.data.as_mut_slice()).unwrap();
377            self.offset_read = offset_read_bits;
378
379            self.len = len;
380
381            // generated expectated output (not efficient)
382            self.expected_data.resize(self.write_data.len(), 0);
383            self.expected_data.copy_from_slice(&self.write_data);
384
385            self.expected_null_count = 0;
386            for i in 0..self.len {
387                let bit = get_bit(&self.data, self.offset_read + i);
388                if bit {
389                    set_bit(&mut self.expected_data, self.offset_write + i);
390                } else {
391                    unset_bit(&mut self.expected_data, self.offset_write + i);
392                    self.expected_null_count += 1;
393                }
394            }
395        }
396
397        /// call set_bits with the given parameters and compare with the expected output
398        fn verify(&self) {
399            // call set_bits and compare
400            let mut actual = self.write_data.clone();
401            let null_count = set_bits(
402                &mut actual,
403                &self.data,
404                self.offset_write,
405                self.offset_read,
406                self.len,
407            );
408
409            assert_eq!(actual, self.expected_data, "self: {self}");
410            assert_eq!(null_count, self.expected_null_count, "self: {self}");
411        }
412    }
413
414    #[test]
415    fn test_set_upto_64bits() {
416        // len >= 64
417        let write_data: &mut [u8] = &mut [0; 9];
418        let data: &[u8] = &[
419            0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000001,
420            0b00000001, 0b00000001,
421        ];
422        let offset_write = 1;
423        let offset_read = 0;
424        let len = 65;
425        let (n, len_set) =
426            unsafe { set_upto_64bits(write_data, data, offset_write, offset_read, len) };
427        assert_eq!(n, 55);
428        assert_eq!(len_set, 63);
429        assert_eq!(
430            write_data,
431            &[
432                0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b00000010,
433                0b00000010, 0b00000000
434            ]
435        );
436
437        // len = 1
438        let write_data: &mut [u8] = &mut [0b00000000];
439        let data: &[u8] = &[0b00000001];
440        let offset_write = 1;
441        let offset_read = 0;
442        let len = 1;
443        let (n, len_set) =
444            unsafe { set_upto_64bits(write_data, data, offset_write, offset_read, len) };
445        assert_eq!(n, 0);
446        assert_eq!(len_set, 1);
447        assert_eq!(write_data, &[0b00000010]);
448    }
449
450    #[test]
451    #[should_panic(expected = "operation will overflow read buffer")]
452    fn test_overflow_read_buffer_bounds() {
453        // Tiny buffers so any huge computed index is out-of-bounds.
454        let data = [0u8; 1];
455        let mut write_data = [0u8; 1];
456
457        // Choose values so (offset_read + len) wraps to a small number in release builds.
458        // offset_read = usize::MAX - 7, len = 8 => wraps to 0.
459        // This can bypass `assert!(offset_read + len <= data.len() * 8)`.
460        let offset_write: usize = 0;
461        let offset_read: usize = usize::MAX - 7;
462        let len: usize = 8;
463
464        // should panic on bounds check overflow
465        let _nulls = set_bits(&mut write_data, &data, offset_write, offset_read, len);
466    }
467
468    #[test]
469    #[should_panic(expected = "operation will overflow write buffer")]
470    fn test_overflow_write_buffer_bounds() {
471        // Tiny buffers so any huge computed index is out-of-bounds.
472        let data = [0u8; 1];
473        let mut write_data = [0u8; 1];
474
475        // Choose values so (offset_write + len) wraps to a small number in release builds.
476        // offset_write = usize::MAX - 7, len = 8 => wraps to 0.
477        // This can bypass `assert!(offset_write + len <= write_data.len() * 8)`.
478        let offset_write: usize = usize::MAX - 7;
479        let offset_read: usize = 0;
480        let len: usize = 8;
481
482        // should panic on bounds check overflow
483        let _nulls = set_bits(&mut write_data, &data, offset_write, offset_read, len);
484    }
485}