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