• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16992591828

15 Aug 2025 02:51PM UTC coverage: 87.203% (-0.5%) from 87.72%
16992591828

Pull #2456

github

web-flow
Merge fe7e226a7 into 4a23f65b3
Pull Request #2456: feat: basic BoolBuffer / BoolBufferMut

476 of 1230 new or added lines in 107 files covered. (38.7%)

74 existing lines in 19 files now uncovered.

56525 of 64820 relevant lines covered (87.2%)

623751.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

23.58
/vortex-buffer/src/bit/buf.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::ops::{BitAnd, BitOr, BitXor, Not};
5

6
use crate::bit::aligned::BitChunks;
7
use crate::bit::ops::{bitwise_and, bitwise_not, bitwise_or, bitwise_unary_op, bitwise_xor};
8
use crate::bit::unaligned::{BitIndexIterator, BitIterator, BitSliceIterator, UnalignedBitChunks};
9
use crate::{
10
    Alignment, BitBufferMut, Buffer, BufferMut, ByteBuffer, buffer, get_bit, get_bit_unchecked,
11
};
12

13
/// An immutable bitset stored as a packed byte buffer.
14
#[derive(Clone, Debug, Eq)]
15
pub struct BitBuffer {
16
    buffer: ByteBuffer,
17
    len: usize,
18
    offset: usize,
19
}
20

21
impl PartialEq for BitBuffer {
NEW
22
    fn eq(&self, other: &Self) -> bool {
×
NEW
23
        if self.len != other.len {
×
NEW
24
            return false;
×
NEW
25
        }
×
26

NEW
27
        self.chunks()
×
NEW
28
            .iter()
×
NEW
29
            .zip(other.chunks())
×
NEW
30
            .all(|(a, b)| a == b)
×
NEW
31
    }
×
32
}
33

34
impl BitBuffer {
35
    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view.
36
    ///
37
    /// Panics if the buffer is not large enough to hold `len` bits.
NEW
38
    pub fn new(buffer: ByteBuffer, len: usize) -> Self {
×
NEW
39
        assert!(
×
NEW
40
            buffer.len() * 8 >= len,
×
NEW
41
            "provided ByteBuffer not large enough to back BoolBuffer with len {len}"
×
42
        );
43

NEW
44
        Self {
×
NEW
45
            buffer,
×
NEW
46
            len,
×
NEW
47
            offset: 0,
×
NEW
48
        }
×
NEW
49
    }
×
50

51
    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view, starting at the
52
    /// given `offset` (in bits).
53
    ///
54
    /// Panics if the buffer is not large enough to hold `len` bits or if the offset is greater than
55
    pub fn new_with_offset(buffer: ByteBuffer, len: usize, offset: usize) -> Self {
8✔
56
        assert!(
8✔
57
            len.saturating_add(offset) <= buffer.len().saturating_mul(8),
8✔
NEW
58
            "provided ByteBuffer (len={}) not large enough to back BoolBuffer with offset {offset} len {len}",
×
NEW
59
            buffer.len()
×
60
        );
61

62
        Self {
8✔
63
            buffer,
8✔
64
            len,
8✔
65
            offset,
8✔
66
        }
8✔
67
    }
8✔
68

69
    /// Create a new `BoolBuffer` of length `len` where all bits are set (true).
70
    pub fn new_set(len: usize) -> Self {
54✔
71
        let words = len.div_ceil(8);
54✔
72
        let buffer = buffer![0xFF; words];
54✔
73

74
        Self {
54✔
75
            buffer,
54✔
76
            len,
54✔
77
            offset: 0,
54✔
78
        }
54✔
79
    }
54✔
80

81
    /// Create a new `BoolBuffer` of length `len` where all bits are unset (false).
82
    pub fn new_unset(len: usize) -> Self {
8✔
83
        let words = len.div_ceil(8);
8✔
84
        let buffer = Buffer::zeroed(words);
8✔
85

86
        Self {
8✔
87
            buffer,
8✔
88
            len,
8✔
89
            offset: 0,
8✔
90
        }
8✔
91
    }
8✔
92

93
    /// Invokes `f` with indexes `0..len` collecting the boolean results into a new `BitBuffer`
NEW
94
    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, mut f: F) -> Self {
×
NEW
95
        let mut buffer = BufferMut::with_capacity(len.div_ceil(64) * 8);
×
96

NEW
97
        let chunks = len / 64;
×
NEW
98
        let remainder = len % 64;
×
NEW
99
        for chunk in 0..chunks {
×
NEW
100
            let mut packed = 0;
×
NEW
101
            for bit_idx in 0..64 {
×
NEW
102
                let i = bit_idx + chunk * 64;
×
NEW
103
                packed |= (f(i) as u64) << bit_idx;
×
NEW
104
            }
×
105

106
            // SAFETY: Already allocated sufficient capacity
NEW
107
            unsafe { buffer.push_unchecked(packed) }
×
108
        }
109

NEW
110
        if remainder != 0 {
×
NEW
111
            let mut packed = 0;
×
NEW
112
            for bit_idx in 0..remainder {
×
NEW
113
                let i = bit_idx + chunks * 64;
×
NEW
114
                packed |= (f(i) as u64) << bit_idx;
×
NEW
115
            }
×
116

117
            // SAFETY: Already allocated sufficient capacity
NEW
118
            unsafe { buffer.push_unchecked(packed) }
×
NEW
119
        }
×
120

NEW
121
        buffer.truncate(len.div_ceil(8));
×
NEW
122
        Self::new(
×
NEW
123
            Buffer::from_byte_buffer_aligned(
×
NEW
124
                buffer.freeze().into_byte_buffer(),
×
NEW
125
                Alignment::of::<u8>(),
×
126
            ),
NEW
127
            len,
×
128
        )
NEW
129
    }
×
130

131
    /// Get the logical length of this `BoolBuffer`.
132
    ///
133
    /// This may differ from the physical length of the backing buffer, for example if it was
134
    /// created using the `new_with_offset` constructor, or if it was sliced.
135
    #[inline]
136
    pub fn len(&self) -> usize {
48✔
137
        self.len
48✔
138
    }
48✔
139

140
    /// Returns `true` if the `BoolBuffer` is empty.
141
    #[inline]
NEW
142
    pub fn is_empty(&self) -> bool {
×
NEW
143
        self.len() == 0
×
NEW
144
    }
×
145

146
    /// Offset of the start of the buffer in bits.
147
    #[inline]
148
    pub fn offset(&self) -> usize {
56✔
149
        self.offset
56✔
150
    }
56✔
151

152
    /// Get a reference to the underlying buffer.
153
    #[inline]
154
    pub fn inner(&self) -> &ByteBuffer {
24✔
155
        &self.buffer
24✔
156
    }
24✔
157

158
    /// Retrieve the value at the given index.
159
    ///
160
    /// Panics if the index is out of bounds.
161
    pub fn value(&self, index: usize) -> bool {
478✔
162
        get_bit(&self.buffer, index + self.offset)
478✔
163
    }
478✔
164

165
    /// Retrieve the value at the given index without bounds checking
166
    ///
167
    /// # SAFETY
168
    /// Caller must ensure that index is within the range of the buffer
NEW
169
    pub unsafe fn value_unchecked(&self, index: usize) -> bool {
×
NEW
170
        unsafe { get_bit_unchecked(&self.buffer, index + self.offset) }
×
NEW
171
    }
×
172

173
    /// Create a new zero-copy slice of this BoolBuffer that begins at the `start` index and extends
174
    /// for `len` bits.
175
    ///
176
    /// Panics if the slice would extend beyond the end of the buffer.
NEW
177
    pub fn slice(&self, start: usize, len: usize) -> Self {
×
NEW
178
        assert!(
×
NEW
179
            start + len <= self.len,
×
NEW
180
            "slice of len {len} starting at {start} exceeds len {}",
×
181
            self.len
182
        );
183

NEW
184
        Self::new_with_offset(self.buffer.clone(), len, self.offset + start)
×
NEW
185
    }
×
186

187
    /// Slice any full bytes from the buffer, leaving the offset < 8.
188
    pub fn shrink_offset(self) -> Self {
8✔
189
        let bit_offset = self.offset % 8;
8✔
190
        let len = self.len;
8✔
191
        let buffer = self.into_inner();
8✔
192
        BitBuffer::new_with_offset(buffer, len, bit_offset)
8✔
193
    }
8✔
194

195
    /// Access chunks of the buffer aligned to 8 byte boundary as [prefix, \<full chunks\>, suffix]
NEW
196
    pub fn unaligned_chunks(&self) -> UnalignedBitChunks {
×
NEW
197
        UnalignedBitChunks::new(self.buffer.clone(), self.offset, self.len)
×
NEW
198
    }
×
199

200
    /// Access chunks of the underlying buffer as 8 byte chunks with a final trailer
201
    ///
202
    /// If you're performing operations on a single buffer, prefer [BitBuffer::unaligned_chunks]
NEW
203
    pub fn chunks(&self) -> BitChunks {
×
NEW
204
        BitChunks::new(self.buffer.clone(), self.offset, self.len)
×
NEW
205
    }
×
206

207
    /// Get the number of set bits in the buffer.
NEW
208
    pub fn true_count(&self) -> usize {
×
NEW
209
        self.unaligned_chunks().count_ones()
×
NEW
210
    }
×
211

212
    /// Get the number of unset bits in the buffer.
NEW
213
    pub fn false_count(&self) -> usize {
×
NEW
214
        self.len - self.true_count()
×
NEW
215
    }
×
216

217
    /// Iterator over bits in the buffer
NEW
218
    pub fn iter(&self) -> BitIterator {
×
NEW
219
        BitIterator::new(self.buffer.clone(), self.offset, self.len)
×
NEW
220
    }
×
221

222
    /// Iterator over set indices of the underlying buffer
NEW
223
    pub fn set_indices(&self) -> BitIndexIterator {
×
NEW
224
        BitIndexIterator::new(self.buffer.clone(), self.offset, self.len)
×
NEW
225
    }
×
226

227
    /// Iterator over set slices of the underlying buffer
NEW
228
    pub fn set_slices(&self) -> BitSliceIterator {
×
NEW
229
        BitSliceIterator::new(self.buffer.clone(), self.offset, self.len)
×
NEW
230
    }
×
231

232
    /// Created a new BitBuffer with offset reset to 0
NEW
233
    pub fn sliced(&self) -> Self {
×
NEW
234
        if self.offset % 8 == 0 {
×
NEW
235
            return Self::new(
×
NEW
236
                self.buffer.slice(self.offset / 8..self.len.div_ceil(8)),
×
NEW
237
                self.len,
×
238
            );
NEW
239
        }
×
240

NEW
241
        Self::new(
×
NEW
242
            bitwise_unary_op(self.buffer.clone(), self.offset, self.len, |a| a),
×
NEW
243
            self.len,
×
244
        )
NEW
245
    }
×
246
}
247

248
// Conversions
249

250
impl BitBuffer {
251
    /// Consumes this `BoolBuffer` and returns the backing `Buffer<u8>` with any offset
252
    /// and length information applied.
253
    pub fn into_inner(self) -> ByteBuffer {
8✔
254
        let word_start = self.offset / 8;
8✔
255
        let word_end = (self.offset + self.len).div_ceil(8);
8✔
256

257
        self.buffer.slice(word_start..word_end)
8✔
258
    }
8✔
259

260
    /// Get a mutable version of this `BitBuffer` along with bit offset in the first byte.
261
    ///
262
    /// If the caller doesn't hold only reference to the underlying buffer, a copy is created.
263
    /// The second value of the tuple is a bit_offset of the first value in the first byte
NEW
264
    pub fn into_mut(self) -> (BitBufferMut, usize) {
×
NEW
265
        let bit_offset = self.offset % 8;
×
NEW
266
        let len = self.len;
×
267
        // TODO(robert): if we are copying here we can strip offset bits
NEW
268
        let shrunk = self.into_inner().into_mut();
×
NEW
269
        (BitBufferMut::from_buffer(shrunk, len), bit_offset)
×
NEW
270
    }
×
271
}
272

273
impl From<&[bool]> for BitBuffer {
NEW
274
    fn from(value: &[bool]) -> Self {
×
NEW
275
        let mut buf = BitBufferMut::new_unset(value.len());
×
NEW
276
        for (i, &v) in value.iter().enumerate() {
×
NEW
277
            if v {
×
278
                // SAFETY: i is in bounds
NEW
279
                unsafe { buf.set_unchecked(i) }
×
NEW
280
            }
×
281
        }
NEW
282
        buf.freeze()
×
NEW
283
    }
×
284
}
285

286
impl From<Vec<bool>> for BitBuffer {
NEW
287
    fn from(value: Vec<bool>) -> Self {
×
NEW
288
        value.as_slice().into()
×
NEW
289
    }
×
290
}
291

292
impl FromIterator<bool> for BitBuffer {
NEW
293
    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
×
NEW
294
        let iter = iter.into_iter();
×
NEW
295
        let (low, high) = iter.size_hint();
×
NEW
296
        if let Some(len) = high {
×
NEW
297
            let mut buf = BitBufferMut::new_unset(len);
×
NEW
298
            for (i, v) in iter.enumerate() {
×
NEW
299
                if v {
×
300
                    // SAFETY: i is in bounds
NEW
301
                    unsafe { buf.set_unchecked(i) }
×
NEW
302
                }
×
303
            }
NEW
304
            buf.freeze()
×
305
        } else {
NEW
306
            let mut buf = BitBufferMut::new(low);
×
NEW
307
            for v in iter {
×
NEW
308
                buf.append(v);
×
NEW
309
            }
×
NEW
310
            buf.freeze()
×
311
        }
NEW
312
    }
×
313
}
314

315
impl BitOr for &BitBuffer {
316
    type Output = BitBuffer;
317

NEW
318
    fn bitor(self, rhs: Self) -> Self::Output {
×
NEW
319
        self.clone() | rhs.clone()
×
NEW
320
    }
×
321
}
322

323
impl BitOr for BitBuffer {
324
    type Output = BitBuffer;
325

NEW
326
    fn bitor(self, rhs: Self) -> Self::Output {
×
NEW
327
        assert_eq!(self.len, rhs.len);
×
NEW
328
        BitBuffer::new_with_offset(
×
NEW
329
            bitwise_or(self.buffer, self.offset, rhs.buffer, rhs.offset, self.len),
×
NEW
330
            self.len,
×
331
            0,
332
        )
NEW
333
    }
×
334
}
335

336
impl BitAnd for &BitBuffer {
337
    type Output = BitBuffer;
338

NEW
339
    fn bitand(self, rhs: Self) -> Self::Output {
×
NEW
340
        self.clone() & rhs.clone()
×
NEW
341
    }
×
342
}
343

344
impl BitAnd for BitBuffer {
345
    type Output = BitBuffer;
346

NEW
347
    fn bitand(self, rhs: Self) -> Self::Output {
×
NEW
348
        assert_eq!(self.len, rhs.len);
×
NEW
349
        BitBuffer::new_with_offset(
×
NEW
350
            bitwise_and(self.buffer, self.offset, rhs.buffer, rhs.offset, self.len),
×
NEW
351
            self.len,
×
352
            0,
353
        )
NEW
354
    }
×
355
}
356

357
impl Not for &BitBuffer {
358
    type Output = BitBuffer;
359

NEW
360
    fn not(self) -> Self::Output {
×
NEW
361
        !self.clone()
×
NEW
362
    }
×
363
}
364

365
impl Not for BitBuffer {
366
    type Output = BitBuffer;
367

NEW
368
    fn not(self) -> Self::Output {
×
NEW
369
        BitBuffer::new_with_offset(bitwise_not(self.buffer, self.offset, self.len), self.len, 0)
×
NEW
370
    }
×
371
}
372

373
impl BitXor for &BitBuffer {
374
    type Output = BitBuffer;
375

NEW
376
    fn bitxor(self, rhs: Self) -> Self::Output {
×
NEW
377
        self.clone() ^ rhs.clone()
×
NEW
378
    }
×
379
}
380

381
impl BitXor for BitBuffer {
382
    type Output = BitBuffer;
383

NEW
384
    fn bitxor(self, rhs: Self) -> Self::Output {
×
NEW
385
        assert_eq!(self.len, rhs.len);
×
NEW
386
        BitBuffer::new_with_offset(
×
NEW
387
            bitwise_xor(self.buffer, self.offset, rhs.buffer, rhs.offset, self.len),
×
NEW
388
            self.len,
×
389
            0,
390
        )
NEW
391
    }
×
392
}
393

394
impl IntoIterator for &BitBuffer {
395
    type Item = bool;
396
    type IntoIter = BitIterator;
397

NEW
398
    fn into_iter(self) -> Self::IntoIter {
×
NEW
399
        self.iter()
×
NEW
400
    }
×
401
}
402

403
impl IntoIterator for BitBuffer {
404
    type Item = bool;
405
    type IntoIter = BitIterator;
406

NEW
407
    fn into_iter(self) -> Self::IntoIter {
×
NEW
408
        self.iter()
×
NEW
409
    }
×
410
}
411

412
#[cfg(test)]
413
mod tests {
414
    use crate::bit::BitBuffer;
415
    use crate::{ByteBuffer, buffer};
416

417
    #[test]
418
    fn test_bool() {
419
        // Create a new Buffer<u64> of length 1024 where the 8th bit is set.
420
        let buffer: ByteBuffer = buffer![1 << 7; 1024];
421
        let bools = BitBuffer::new(buffer, 1024 * 8);
422

423
        // sanity checks
424
        assert_eq!(bools.len(), 1024 * 8);
425
        assert!(!bools.is_empty());
426
        assert_eq!(bools.true_count(), 1024);
427
        assert_eq!(bools.false_count(), 1024 * 7);
428

429
        // Check all the values
430
        for word in 0..1024 {
431
            for bit in 0..8 {
432
                if bit == 7 {
433
                    assert!(bools.value(word * 8 + bit));
434
                } else {
435
                    assert!(!bools.value(word * 8 + bit));
436
                }
437
            }
438
        }
439

440
        // Slice the buffer to create a new subset view.
441
        let sliced = bools.slice(64, 8);
442

443
        // sanity checks
444
        assert_eq!(sliced.len(), 8);
445
        assert!(!sliced.is_empty());
446
        assert_eq!(sliced.true_count(), 1);
447
        assert_eq!(sliced.false_count(), 7);
448

449
        // Check all of the values like before
450
        for bit in 0..8 {
451
            if bit == 7 {
452
                assert!(sliced.value(bit));
453
            } else {
454
                assert!(!sliced.value(bit));
455
            }
456
        }
457
    }
458
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc