Skip to main content

molframe_core/column/
bits.rs

1//! Bit-level primitives: a compact bit set, and packing narrow integers.
2//!
3//! Both exist for the same reason. A per-atom flag stored as a `bool` costs a
4//! byte, eight times what it needs; a per-atom value with a range of a hundred
5//! costs four bytes as a `u32`, four times what it needs. At a million atoms
6//! those factors are the difference between a column that stays in cache during
7//! a scan and one that does not.
8//!
9//! Neither is used for positions. Unpacking costs a shift and a mask per value,
10//! which is worth paying for a column read once per atom and not worth paying
11//! for one a kernel reads on every iteration.
12
13use crate::limits::CapacityError;
14use std::sync::Arc;
15
16/// A compact set of bits.
17///
18/// Iteration yields set positions rather than every position, so walking a
19/// sparse mask costs one step per member instead of one per candidate.
20#[derive(Clone, PartialEq, Eq, Debug, Default)]
21pub struct BitVec {
22    words: Arc<Vec<u64>>,
23    len: u32,
24}
25
26const BITS: u32 = u64::BITS;
27
28impl BitVec {
29    /// Creates an empty set.
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            words: Arc::new(Vec::new()),
34            len: 0,
35        }
36    }
37
38    /// Creates a set of `len` bits, all equal to `value`.
39    #[must_use]
40    pub fn repeat(value: bool, len: u32) -> Self {
41        let word = if value { u64::MAX } else { 0 };
42        let words = Arc::new(vec![word; word_count(len)]);
43        let mut bits = Self { words, len };
44
45        bits.clear_tail();
46        bits
47    }
48
49    /// Creates a set with room for `len` bits without reallocating.
50    #[must_use]
51    pub fn with_capacity(len: u32) -> Self {
52        Self {
53            words: Arc::new(Vec::with_capacity(word_count(len))),
54            len: 0,
55        }
56    }
57
58    /// The number of bits held.
59    #[must_use]
60    pub const fn len(&self) -> u32 {
61        self.len
62    }
63
64    /// Returns true when no bit is held.
65    #[must_use]
66    pub const fn is_empty(&self) -> bool {
67        self.len == 0
68    }
69
70    /// Appends a bit.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`CapacityError`] without mutation when the `u32` position space is full.
75    pub fn try_push(&mut self, value: bool) -> Result<(), CapacityError> {
76        let position = self.len;
77        let Some(next_len) = self.len.checked_add(1) else {
78            return Err(CapacityError::new("bit vector"));
79        };
80
81        if position.is_multiple_of(BITS) {
82            Arc::make_mut(&mut self.words).push(0);
83        }
84
85        self.len = next_len;
86
87        if !value {
88            return Ok(());
89        }
90
91        if let Some(word) = Arc::make_mut(&mut self.words).last_mut() {
92            *word |= bit_mask(position);
93        }
94        Ok(())
95    }
96
97    /// Returns the bit at `position`, or `None` if it is past the end.
98    #[must_use]
99    pub fn get(&self, position: u32) -> Option<bool> {
100        if position >= self.len {
101            return None;
102        }
103
104        self.words
105            .get(word_index(position))
106            .map(|word| *word & bit_mask(position) != 0)
107    }
108
109    /// Returns the bit at `position`, treating a position past the end as unset.
110    #[must_use]
111    pub fn test(&self, position: u32) -> bool {
112        self.get(position) == Some(true)
113    }
114
115    /// Sets the bit at `position`, ignoring a position past the end.
116    pub fn set(&mut self, position: u32, value: bool) {
117        if position >= self.len {
118            return;
119        }
120        let Some(word) = Arc::make_mut(&mut self.words).get_mut(word_index(position)) else {
121            return;
122        };
123
124        let mask = bit_mask(position);
125
126        if value {
127            *word |= mask;
128        } else {
129            *word &= !mask;
130        }
131    }
132
133    /// The number of set bits.
134    #[must_use]
135    pub fn count_ones(&self) -> u32 {
136        self.words.iter().map(|word| word.count_ones()).sum()
137    }
138
139    /// Returns true when every bit is set.
140    #[must_use]
141    pub fn all(&self) -> bool {
142        let complete_words = (self.len / BITS) as usize;
143
144        if !self
145            .words
146            .iter()
147            .take(complete_words)
148            .all(|word| *word == u64::MAX)
149        {
150            return false;
151        }
152
153        let tail_bits = self.len % BITS;
154
155        tail_bits == 0 || self.words.get(complete_words).copied() == Some(low_mask(tail_bits))
156    }
157
158    /// Returns true when no bit is set.
159    #[must_use]
160    pub fn none(&self) -> bool {
161        self.words.iter().all(|word| *word == 0)
162    }
163
164    /// The set positions, ascending.
165    ///
166    /// Skips whole words that hold nothing, so a mask with one member in a
167    /// million bits costs a scan of sixteen thousand words rather than a
168    /// million tests.
169    ///
170    /// A position space beyond `u32` ends the walk rather than panicking.
171    #[must_use]
172    pub fn ones(&self) -> Ones<'_> {
173        Ones {
174            words: &self.words,
175            word: 0,
176            remaining: 0,
177            started: false,
178        }
179    }
180
181    /// Keeps only the bits also set in `other`.
182    pub fn intersect_with(&mut self, other: &Self) {
183        let shared = self.words.len().min(other.words.len());
184        let words = Arc::make_mut(&mut self.words);
185
186        for (word, other_word) in words[..shared].iter_mut().zip(&other.words[..shared]) {
187            *word &= *other_word;
188        }
189
190        for word in &mut words[shared..] {
191            *word = 0;
192        }
193    }
194
195    /// Adds every bit set in `other`.
196    ///
197    /// Bits of `other` beyond this set's length are ignored; the length is a
198    /// property of the structure, not of the operation.
199    pub fn union_with(&mut self, other: &Self) {
200        for (word, other_word) in Arc::make_mut(&mut self.words)
201            .iter_mut()
202            .zip(other.words.iter())
203        {
204            *word |= *other_word;
205        }
206
207        self.clear_tail();
208    }
209
210    /// Inverts every bit.
211    pub fn invert(&mut self) {
212        for word in Arc::make_mut(&mut self.words) {
213            *word = !*word;
214        }
215
216        self.clear_tail();
217    }
218
219    /// Zeroes the bits past the end that live in the final word, so that
220    /// counting and comparison never see them.
221    fn clear_tail(&mut self) {
222        let used = self.len % BITS;
223
224        if used == 0 {
225            return;
226        }
227        if let Some(word) = Arc::make_mut(&mut self.words).last_mut() {
228            *word &= low_mask(used);
229        }
230    }
231}
232
233impl BitVec {
234    /// Builds a bit vector, returning `None` if its position space is exhausted.
235    pub fn try_from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Option<Self> {
236        let iter = iter.into_iter();
237        let capacity = u32::try_from(iter.size_hint().0).ok()?;
238        let mut bits = Self::with_capacity(capacity);
239
240        for value in iter {
241            bits.try_push(value).ok()?;
242        }
243
244        Some(bits)
245    }
246}
247
248/// The number of bits needed to represent every value up to `max`.
249///
250/// # Panics
251/// The checked conversion is mathematically bounded to 64 bits.
252#[must_use]
253pub fn bit_width(max: u64) -> u8 {
254    match max {
255        0 => 1,
256        _ => narrow_width(u64::BITS - max.leading_zeros()),
257    }
258}
259
260/// Mask covering the low `width` bits.
261const fn low_mask(width: u32) -> u64 {
262    if width >= u64::BITS {
263        u64::MAX
264    } else {
265        (1u64 << width) - 1
266    }
267}
268
269fn word_count(len: u32) -> usize {
270    len.div_ceil(BITS) as usize
271}
272
273const fn word_index(position: u32) -> usize {
274    (position / BITS) as usize
275}
276
277const fn bit_offset(position: u32) -> u32 {
278    position % BITS
279}
280
281const fn bit_mask(position: u32) -> u64 {
282    1u64 << bit_offset(position)
283}
284
285/// Where a packed value sits: which byte it starts in, how far into that byte,
286/// and how many bytes it touches.
287const fn placement(width: u32, index: u32) -> (usize, u32, usize) {
288    let start_bit = index as u64 * width as u64;
289    let byte = (start_bit / 8) as usize;
290    let offset = (start_bit % 8) as u32;
291    let bytes = (offset as usize + width as usize).div_ceil(8);
292
293    (byte, offset, bytes)
294}
295
296/// Packs `values` into `width` bits each.
297///
298/// Values wider than `width` are truncated, which is why the caller derives the
299/// width from the data rather than choosing one. Each value is written with a
300/// single shifted accumulation rather than a loop over its bits.
301#[must_use]
302pub fn pack(values: &[u64], width: u8) -> Option<Vec<u8>> {
303    pack_mapped(values, width, |value| *value)
304}
305
306pub(super) fn pack_mapped<T, F>(values: &[T], width: u8, to_bits: F) -> Option<Vec<u8>>
307where
308    F: Fn(&T) -> u64,
309{
310    let width = u32::from(width.clamp(1, 64));
311    let total_bits = u64::try_from(values.len())
312        .ok()?
313        .checked_mul(u64::from(width))?;
314    let packed_len = usize::try_from(total_bits.div_ceil(8)).ok()?;
315    let mut packed = vec![0u8; packed_len];
316
317    for (index, value) in values.iter().enumerate() {
318        write_packed_value(
319            &mut packed,
320            width,
321            u32::try_from(index).ok()?,
322            to_bits(value),
323        );
324    }
325
326    Some(packed)
327}
328
329fn write_packed_value(packed: &mut [u8], width: u32, index: u32, value: u64) {
330    let (byte, offset, bytes) = placement(width, index);
331    let Some(end) = byte.checked_add(bytes) else {
332        return;
333    };
334    let Some(targets) = packed.get_mut(byte..end) else {
335        return;
336    };
337
338    let shifted = u128::from(value & low_mask(width)) << offset;
339
340    for (step, target) in targets.iter_mut().enumerate() {
341        *target |= ((shifted >> (8 * step)) & u128::from(u8::MAX)).to_le_bytes()[0];
342    }
343}
344
345/// Reads the value at `index` from a packed buffer.
346///
347/// Returns `None` when the value would run past the end of the buffer, which is
348/// the only way a malformed input can reach this code.
349///
350/// A value is at most 64 bits and starts at most 7 bits into a byte, so it
351/// spans at most nine bytes. Reading those as one little-endian word plus, when
352/// the value straddles the ninth byte, that byte's low bits, replaces a
353/// nine-step 128-bit fold with two loads and a shift.
354#[must_use]
355pub fn unpack_one(packed: &[u8], width: u8, index: u32) -> Option<u64> {
356    let width = u32::from(width.clamp(1, 64));
357    let (byte, offset, bytes) = placement(width, index);
358    let end = byte.checked_add(bytes)?;
359    let source = packed.get(byte..end)?;
360
361    let mut window = [0u8; 8];
362    let take = source.len().min(8);
363    let (Some(head), Some(front)) = (window.get_mut(..take), source.get(..take)) else {
364        return None;
365    };
366    head.copy_from_slice(front);
367    let low = u64::from_le_bytes(window) >> offset;
368
369    // A ninth byte exists only when the value straddles it, which requires a
370    // non-zero bit offset, so the complementary shift below is never 64.
371    let high = match source.get(8) {
372        Some(spill) if offset > 0 => u64::from(*spill) << (64 - offset),
373        _ => 0,
374    };
375
376    Some((low | high) & low_mask(width))
377}
378
379fn narrow_width(width: u32) -> u8 {
380    width.to_le_bytes()[0]
381}
382
383#[cfg(test)]
384#[path = "bits_tests.rs"]
385mod tests;
386
387/// The set positions of a [`BitVec`], ascending.
388///
389/// A concrete type rather than an opaque one so callers can store it in their
390/// own iterators without boxing, which is what keeps a selection walk free of
391/// a heap allocation and open to inlining.
392#[derive(Clone, Debug)]
393pub struct Ones<'a> {
394    /// The mask's backing words.
395    words: &'a [u64],
396    /// The word currently being drained.
397    word: usize,
398    /// The undelivered bits of that word.
399    remaining: u64,
400    /// Whether `remaining` has been loaded from `word` yet.
401    started: bool,
402}
403
404impl Iterator for Ones<'_> {
405    type Item = u32;
406
407    fn next(&mut self) -> Option<u32> {
408        loop {
409            if self.remaining != 0 {
410                let bit = self.remaining.trailing_zeros();
411                self.remaining &= self.remaining - 1;
412                let base = u32::try_from(self.word).ok()?.checked_mul(BITS)?;
413                return Some(base + bit);
414            }
415
416            // Whole empty words are skipped a word at a time, so a mask with
417            // one member in a million bits costs sixteen thousand loads rather
418            // than a million tests.
419            if self.started {
420                self.word = self.word.checked_add(1)?;
421            }
422            self.started = true;
423            self.remaining = *self.words.get(self.word)?;
424        }
425    }
426
427    fn size_hint(&self) -> (usize, Option<usize>) {
428        // Before the first `next`, the current word has not been loaded into
429        // `remaining` yet, so it is still part of the untouched tail.
430        let counted = self.remaining.count_ones() as usize;
431        let from = if self.started {
432            self.word.saturating_add(1)
433        } else {
434            self.word
435        };
436        let rest: usize = match self.words.get(from..) {
437            Some(tail) => tail.iter().map(|word| word.count_ones() as usize).sum(),
438            None => 0,
439        };
440        let total = counted + rest;
441        (total, Some(total))
442    }
443}
444
445impl std::iter::FusedIterator for Ones<'_> {}