Skip to main content

p3_binary_field/bitslice/
underlier.rs

1//! Fixed-width blocks of bits, 8 to 512 wide, with no field structure attached.
2//!
3//! A block is addressed as an array of machine words, lowest bits in word `0`.
4//! Nothing above this layer names a register: it asks for words, and the compiler chooses.
5//!
6//! # Why an aligned word array, not a vector intrinsic
7//!
8//! The wide blocks hold plain 64-bit words, aligned to the register that fits them.
9//! That is the convention the crate's packed `GF(2^128)` already follows.
10//!
11//! It keeps a block constructible in a constant, and derivable for equality and hashing.
12//! It is also one representation on every target, with no per-feature `cfg` to get wrong.
13//!
14//! Exclusive or, conjunction and shifts still reach the widest register the build enables.
15//!
16//! # Divisibility
17//!
18//! A wide block is laid out as narrow ones side by side, so reading it narrow moves no bits:
19//!
20//! ```text
21//!     M512  ->  [M128; 4]  ->  [u64; 8]  ->  [u8; 64]
22//! ```
23//!
24//! That is what makes a width change a borrow, and what lets the transpose sweep flat.
25
26use core::fmt::Debug;
27use core::hash::Hash;
28use core::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr};
29use core::slice;
30
31use rand::{Rng, RngExt};
32
33/// Keeps the storage traits closed to this crate.
34///
35/// Their safety rests on the exact layout of the few types below.
36/// No outside implementation could be held to it.
37pub(crate) mod private {
38    pub trait Sealed {}
39}
40
41use private::Sealed;
42
43impl Sealed for u8 {}
44impl Sealed for u16 {}
45impl Sealed for u32 {}
46impl Sealed for u64 {}
47
48/// The machine integer a bit kernel shifts and masks.
49///
50/// Every operation inside a word goes through this, so a kernel is written once per shape.
51pub trait Word:
52    Sealed
53    + Copy
54    + Default
55    + Eq
56    + Ord
57    + Hash
58    + Debug
59    + Send
60    + Sync
61    + 'static
62    + BitAnd<Output = Self>
63    + BitOr<Output = Self>
64    + BitXor<Output = Self>
65    + Not<Output = Self>
66    + Shl<usize, Output = Self>
67    + Shr<usize, Output = Self>
68{
69    /// Bits in the word.
70    const BITS: usize;
71
72    /// Every bit clear.
73    const ZERO: Self;
74
75    /// Every bit set.
76    const ONES: Self;
77
78    /// How many bits are set.
79    fn count_ones(self) -> u32;
80
81    /// The word widened to 64 bits, zero extended.
82    fn to_u64(self) -> u64;
83
84    /// The low bits of a 64-bit value, with anything above the word's width dropped.
85    fn from_u64(value: u64) -> Self;
86}
87
88/// Implement the word trait for one primitive integer.
89macro_rules! impl_word {
90    ($word:ty) => {
91        impl Word for $word {
92            const BITS: usize = <$word>::BITS as usize;
93            const ZERO: Self = 0;
94            const ONES: Self = <$word>::MAX;
95
96            #[inline]
97            fn count_ones(self) -> u32 {
98                Self::count_ones(self)
99            }
100
101            #[inline]
102            fn to_u64(self) -> u64 {
103                u64::from(self)
104            }
105
106            #[inline]
107            fn from_u64(value: u64) -> Self {
108                value as Self
109            }
110        }
111    };
112}
113
114impl_word!(u8);
115impl_word!(u16);
116impl_word!(u32);
117impl_word!(u64);
118
119/// A fixed-width block of bits, addressed as an array of words.
120///
121/// Word `0` holds the lowest bits of the block, and bit `0` of a word is the lowest of all.
122///
123/// # Safety
124/// A value must be exactly its words and nothing else:
125/// - its size must equal the word count times the size of one word,
126/// - its alignment must be at least a word's.
127///
128/// Reinterpreting a run of values as a run of words relies on both.
129pub unsafe trait Underlier:
130    Copy + Default + Eq + Hash + Debug + Send + Sync + 'static
131{
132    /// The word the block is addressed in.
133    type Word: Word;
134
135    /// How many words make up the block.
136    const WORDS: usize;
137
138    /// Bits in the whole block.
139    const BITS: usize = Self::WORDS * <Self::Word as Word>::BITS;
140
141    /// Every bit clear.
142    const ZERO: Self;
143
144    /// Every bit set.
145    const ONES: Self;
146
147    /// The words of the block, lowest bits first.
148    fn words(&self) -> &[Self::Word];
149
150    /// The words of the block, lowest bits first.
151    fn words_mut(&mut self) -> &mut [Self::Word];
152
153    /// Build a block word by word, lowest bits first.
154    fn from_words_fn(f: impl FnMut(usize) -> Self::Word) -> Self;
155
156    /// A block whose bits are uniformly random and independent.
157    fn random<R: Rng + ?Sized>(rng: &mut R) -> Self;
158
159    /// Combine two blocks word by word.
160    #[inline]
161    fn zip(self, other: Self, op: impl Fn(Self::Word, Self::Word) -> Self::Word) -> Self {
162        Self::from_words_fn(|i| op(self.words()[i], other.words()[i]))
163    }
164}
165
166/// Implement the underlier trait for a primitive integer, which is a block of one word.
167macro_rules! impl_scalar_underlier {
168    ($word:ty) => {
169        // SAFETY: a value is its own single word, so the size and alignment match exactly.
170        unsafe impl Underlier for $word {
171            type Word = Self;
172
173            const WORDS: usize = 1;
174            const ZERO: Self = 0;
175            const ONES: Self = <$word>::MAX;
176
177            #[inline]
178            fn words(&self) -> &[Self::Word] {
179                slice::from_ref(self)
180            }
181
182            #[inline]
183            fn words_mut(&mut self) -> &mut [Self::Word] {
184                slice::from_mut(self)
185            }
186
187            #[inline]
188            fn from_words_fn(mut f: impl FnMut(usize) -> Self::Word) -> Self {
189                f(0)
190            }
191
192            #[inline]
193            fn random<R: Rng + ?Sized>(rng: &mut R) -> Self {
194                rng.random()
195            }
196        }
197    };
198}
199
200impl_scalar_underlier!(u8);
201impl_scalar_underlier!(u16);
202impl_scalar_underlier!(u32);
203impl_scalar_underlier!(u64);
204
205/// Define a wide block of 64-bit words, aligned to the register that holds all of it.
206///
207/// That alignment also starts the block on the boundary every narrower block inside needs.
208macro_rules! wide_underlier {
209    ($name:ident, $words:literal, $align:literal, $doc:literal) => {
210        #[doc = $doc]
211        ///
212        /// Word `0` holds the lowest bits.
213        #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
214        #[repr(C, align($align))]
215        pub struct $name([u64; $words]);
216
217        impl Sealed for $name {}
218
219        impl $name {
220            /// A block from its words, lowest bits first.
221            #[inline]
222            #[must_use]
223            pub const fn from_words(words: [u64; $words]) -> Self {
224                Self(words)
225            }
226
227            /// The words of the block, lowest bits first.
228            #[inline]
229            #[must_use]
230            pub const fn to_words(self) -> [u64; $words] {
231                self.0
232            }
233        }
234
235        // SAFETY: `repr(C)` over the word array gives the array's size, `align` only raises.
236        unsafe impl Underlier for $name {
237            type Word = u64;
238
239            const WORDS: usize = $words;
240            const ZERO: Self = Self([0; $words]);
241            const ONES: Self = Self([u64::MAX; $words]);
242
243            #[inline]
244            fn words(&self) -> &[Self::Word] {
245                &self.0
246            }
247
248            #[inline]
249            fn words_mut(&mut self) -> &mut [Self::Word] {
250                &mut self.0
251            }
252
253            #[inline]
254            fn from_words_fn(f: impl FnMut(usize) -> Self::Word) -> Self {
255                Self(core::array::from_fn(f))
256            }
257
258            #[inline]
259            fn random<R: Rng + ?Sized>(rng: &mut R) -> Self {
260                Self(rng.random())
261            }
262        }
263    };
264}
265
266wide_underlier!(M128, 2, 16, "A 128-bit block of bits.");
267wide_underlier!(M256, 4, 32, "A 256-bit block of bits.");
268wide_underlier!(M512, 8, 64, "A 512-bit block of bits.");
269
270/// A block that is exactly several narrower blocks laid end to end, part `0` lowest.
271///
272/// # Safety
273/// The trait is sealed, and every implementation is checked below at compile time:
274/// - the wide size is the ratio times the narrow size,
275/// - the wide alignment is at least the narrow alignment,
276/// - the target is little-endian whenever the narrow word is the shorter one.
277///
278/// The three together are what make the reinterpretations sound.
279pub unsafe trait Divisible<Narrow: Underlier>: Underlier {
280    /// How many narrow blocks fit in one wide block.
281    const RATIO: usize = Self::BITS / Narrow::BITS;
282
283    /// The narrow blocks this one is made of, lowest bits first.
284    #[inline]
285    fn parts(&self) -> &[Narrow] {
286        const { Self::CHECK_LAYOUT };
287
288        // SAFETY: the check pins one wide block at exactly `RATIO` narrow ones.
289        // They are suitably aligned, and in the order the lane map promises.
290        unsafe { slice::from_raw_parts(core::ptr::from_ref(self).cast(), Self::RATIO) }
291    }
292
293    /// The narrow blocks this one is made of, lowest bits first.
294    #[inline]
295    fn parts_mut(&mut self) -> &mut [Narrow] {
296        const { Self::CHECK_LAYOUT };
297
298        // SAFETY: as above, and the exclusive borrow is not duplicated.
299        unsafe { slice::from_raw_parts_mut(core::ptr::from_mut(self).cast(), Self::RATIO) }
300    }
301
302    /// A run of wide blocks read as one run of narrow ones, lowest bits first.
303    #[inline]
304    fn split_slice(slice: &[Self]) -> &[Narrow] {
305        const { Self::CHECK_LAYOUT };
306
307        // SAFETY: wide blocks are contiguous and each covers exactly `RATIO` narrow ones.
308        unsafe { slice::from_raw_parts(slice.as_ptr().cast(), slice.len() * Self::RATIO) }
309    }
310
311    /// A run of wide blocks read as one run of narrow ones, lowest bits first.
312    #[inline]
313    fn split_slice_mut(slice: &mut [Self]) -> &mut [Narrow] {
314        const { Self::CHECK_LAYOUT };
315
316        // SAFETY: as above, and the exclusive borrow is not duplicated.
317        let len = slice.len() * Self::RATIO;
318        unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), len) }
319    }
320
321    /// What every reinterpretation above rests on, rejected at compile time if it fails.
322    ///
323    /// Reading a block at a narrower word is a memory-order view.
324    /// A lane is defined on word values, so the two agree only on a little-endian target.
325    ///
326    /// Views that keep the word width are pure reindexing, and hold either way.
327    const CHECK_LAYOUT: () = {
328        assert!(size_of::<Self>() == Self::RATIO * size_of::<Narrow>());
329        assert!(align_of::<Self>() >= align_of::<Narrow>());
330        assert!(
331            cfg!(target_endian = "little") || size_of::<Narrow::Word>() == size_of::<Self::Word>(),
332            "reading a block at a narrower word needs a little-endian target"
333        );
334    };
335}
336
337/// Declare that one block is an array of another.
338///
339/// The layout is checked by the trait itself, at every reinterpretation.
340macro_rules! divisible {
341    ($wide:ty, $narrow:ty) => {
342        // SAFETY: the trait's own layout check rejects a pair that does not line up.
343        unsafe impl Divisible<$narrow> for $wide {
344            const RATIO: usize = <$wide as Underlier>::BITS / <$narrow as Underlier>::BITS;
345        }
346    };
347}
348
349divisible!(M512, M256);
350divisible!(M512, M128);
351divisible!(M512, u64);
352divisible!(M512, u32);
353divisible!(M512, u16);
354divisible!(M512, u8);
355
356divisible!(M256, M128);
357divisible!(M256, u64);
358divisible!(M256, u32);
359divisible!(M256, u16);
360divisible!(M256, u8);
361
362divisible!(M128, u64);
363divisible!(M128, u32);
364divisible!(M128, u16);
365divisible!(M128, u8);
366
367divisible!(u64, u32);
368divisible!(u64, u16);
369divisible!(u64, u8);
370
371divisible!(u32, u16);
372divisible!(u32, u8);
373
374divisible!(u16, u8);
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn a_block_is_its_words() {
382        // Invariant: word 0 holds the lowest bits, and the constants are all-clear, all-set.
383        assert_eq!(M512::ZERO.words(), &[0u64; 8]);
384        assert_eq!(M512::ONES.words(), &[u64::MAX; 8]);
385        assert_eq!(M512::BITS, 512);
386        assert_eq!(M256::BITS, 256);
387        assert_eq!(M128::BITS, 128);
388        assert_eq!(<u8 as Underlier>::BITS, 8);
389
390        // Building word by word and reading back is a round trip.
391        let block = M256::from_words_fn(|i| i as u64);
392        assert_eq!(block.words(), &[0u64, 1, 2, 3]);
393        assert_eq!(block.to_words(), [0u64, 1, 2, 3]);
394        assert_eq!(M256::from_words([0, 1, 2, 3]), block);
395    }
396
397    #[test]
398    fn a_wide_block_is_an_array_of_narrow_ones() {
399        // Fixture: the 512-bit block whose word `i` is `i`.
400        let block = M512::from_words_fn(|i| i as u64);
401
402        // As 64-bit words, the parts are the words themselves.
403        let words: &[u64] = block.parts();
404        assert_eq!(words, &[0u64, 1, 2, 3, 4, 5, 6, 7]);
405
406        // As 128-bit blocks, part `k` holds words `2k` and `2k + 1`.
407        let halves: &[M128] = block.parts();
408        assert_eq!(halves.len(), 4);
409        assert_eq!(halves[0].to_words(), [0, 1]);
410        assert_eq!(halves[3].to_words(), [6, 7]);
411
412        // As 256-bit blocks, part 0 holds the low four words.
413        let quarters: &[M256] = block.parts();
414        assert_eq!(quarters.len(), 2);
415        assert_eq!(quarters[0].to_words(), [0, 1, 2, 3]);
416        assert_eq!(quarters[1].to_words(), [4, 5, 6, 7]);
417
418        // As bytes, byte 0 is the lowest byte of word 0, so word 1 lands at byte 8.
419        let bytes: &[u8] = block.parts();
420        assert_eq!(bytes.len(), 64);
421        assert_eq!(bytes[0], 0);
422        assert_eq!(bytes[8], 1);
423        assert_eq!(bytes[16], 2);
424    }
425
426    #[test]
427    fn writing_through_a_narrow_view_changes_the_wide_block() {
428        // The view borrows the same bytes, so a write through it is a write to the block.
429        let mut block = M128::ZERO;
430        {
431            let bytes: &mut [u8] = block.parts_mut();
432            bytes[0] = 0xff;
433            bytes[15] = 1;
434        }
435
436        // Byte 0 is the lowest byte of word 0, and byte 15 the highest of word 1.
437        assert_eq!(block.to_words(), [0xff, 1 << 56]);
438    }
439
440    #[test]
441    fn a_word_widens_and_truncates() {
442        // Widening is zero extension, narrowing keeps the low bits.
443        assert_eq!(<u8 as Word>::to_u64(0xff), 255);
444        assert_eq!(<u8 as Word>::from_u64(0x1234), 0x34);
445        assert_eq!(<u16 as Word>::from_u64(0x1_2345), 0x2345);
446        assert_eq!(<u64 as Word>::ONES, u64::MAX);
447        assert_eq!(<u32 as Word>::count_ones(0b1011), 3);
448    }
449
450    #[test]
451    fn combining_runs_word_by_word() {
452        // Fixture: two blocks whose words differ in every position.
453        let a = M128::from_words([0b1100, 0b1010]);
454        let b = M128::from_words([0b1010, 0b0110]);
455
456        assert_eq!(
457            a.zip(b, |x, y| x ^ y).to_words(),
458            [0b1100 ^ 0b1010, 0b1010 ^ 0b0110]
459        );
460        assert_eq!(
461            a.zip(b, |x, y| x & y).to_words(),
462            [0b1100 & 0b1010, 0b1010 & 0b0110]
463        );
464    }
465}