Skip to main content

rudb_encoding/
bitpack.rs

1//! Bit packing in the FastLanes unified transposed layout.
2//!
3//! Packing N values of a fixed bit width into a dense buffer is the bottom of every integer
4//! encoding in `spec/06-compression.md` section 6.2. FOR subtracts a base and packs. DELTA
5//! differences and packs. DICT produces codes and packs them. So this is the one kernel that runs
6//! over more bytes than anything else in the system, and the layout it uses decides whether the
7//! decoder can be data parallel or has to walk a dependency chain.
8//!
9//! The obvious layout writes value 0 in the low bits of the first word, value 1 above it, and so
10//! on. Unpacking that requires knowing where the previous value ended, which is a sequential
11//! dependency, and a SIMD implementation has to fight it with shuffles that differ per width and
12//! per instruction set. FastLanes takes the other road. The 1024 values of a vector are seen as a
13//! matrix of `T` rows by `1024 / T` lanes, where `T` is the bit width of the type, and the packing
14//! runs down the rows of every lane at once. Every lane has the same bit schedule, so unpacking
15//! lane 0 and lane 31 is the same instruction sequence with no cross lane data movement at all.
16//! That is what makes one scalar reference implementation and one AVX-512 implementation and one
17//! NEON implementation agree bit for bit, and it is why the vector size is 1024 rather than a
18//! rounder number.
19//!
20//! The price is that the values come out permuted within the vector. Row `r` lane `l` is not the
21//! `r * lanes + l`th value of the input. Section 6.2 says why that is acceptable: an operator
22//! working inside one vector does not care what order the rows are in, so the permutation only
23//! has to be undone when a vector is materialized in row order. [`transpose`] and [`untranspose`]
24//! are that step, and they are deliberately separate from [`pack_transposed`] and
25//! [`unpack_transposed`] so that the engine can keep data permuted through a whole pipeline and
26//! pay for the reordering once at the end rather than twice per operator.
27//!
28//! There is a second layout in here, in [`pack_tail`], and it is the sequential one this module
29//! opens by arguing against. The transposed layout is all or nothing: a value lives at a row and a
30//! lane, the lanes are interleaved through the whole buffer, and no prefix of a packed unit holds a
31//! prefix of the values. So a unit holding 3 values costs exactly what a unit holding 1024 costs,
32//! and a cascade is full of short arrays. A five entry dictionary, a run length array, an exception
33//! list. Storing three numbers in 5 KB is not a compressed format. The tail packer handles anything
34//! shorter than a unit, it has the dependency chain the transposed layout exists to avoid, and that
35//! is affordable there and nowhere else, because a tail is at most 1023 values and is decoded once
36//! while a full unit is on the hot path of every scan in the system.
37//!
38//! The permutation itself is a fixed shuffle of the eight bit groups of a row index, in the order
39//! 0, 4, 2, 6, 1, 5, 3, 7. That order is not arbitrary. It is the one that makes an eight way
40//! interleave of the rows land back in sequence under the pairwise unpacking pattern the paper
41//! uses, and the important property for us is only that it is a bijection that both directions
42//! agree on.
43
44use rudb_common::{Error, Result};
45
46/// How many values a packed unit holds. One vector, per `spec/06-compression.md` section 6.2.
47pub const VALUES: usize = 1024;
48
49/// The interleaving order of the eight row groups. See the module documentation.
50const ORDER: [usize; 8] = [0, 4, 2, 6, 1, 5, 3, 7];
51
52mod sealed {
53    pub trait Sealed {}
54    impl Sealed for u8 {}
55    impl Sealed for u16 {}
56    impl Sealed for u32 {}
57    impl Sealed for u64 {}
58}
59
60/// An unsigned integer type that can be bit packed.
61///
62/// Sealed, because the layout constants are only correct for the four widths that divide 1024 into
63/// a whole number of lanes, and because every kernel here does its arithmetic in `u64` and relies
64/// on every implementor fitting in one.
65pub trait Packable: sealed::Sealed + Copy + Ord + std::fmt::Debug {
66    /// Width of the type in bits. `T` in the module documentation.
67    const WIDTH: usize;
68    /// How many of these fit in the 1024 bit virtual register, which is how many lanes there are.
69    const LANES: usize = VALUES / Self::WIDTH;
70
71    /// Widens to the type the packing arithmetic is done in.
72    fn to_u64(self) -> u64;
73    /// Narrows back. The high bits are already known to be zero.
74    fn from_u64(value: u64) -> Self;
75}
76
77macro_rules! impl_packable {
78    ($($ty:ty),*) => {$(
79        impl Packable for $ty {
80            const WIDTH: usize = <$ty>::BITS as usize;
81
82            #[inline]
83            fn to_u64(self) -> u64 {
84                u64::from(self)
85            }
86
87            #[inline]
88            fn from_u64(value: u64) -> Self {
89                value as $ty
90            }
91        }
92    )*};
93}
94
95impl_packable!(u8, u16, u32, u64);
96
97/// A mask of the low `bits` bits, correct at 0 and at 64 where the shift would overflow.
98#[inline]
99const fn low_mask(bits: usize) -> u64 {
100    if bits >= 64 { u64::MAX } else { (1u64 << bits) - 1 }
101}
102
103/// A right shift that saturates to zero at 64 rather than overflowing.
104#[inline]
105const fn shift_right(value: u64, bits: usize) -> u64 {
106    if bits >= 64 { 0 } else { value >> bits }
107}
108
109/// Where the value at row `row` lane `lane` of the transposed matrix came from in the input.
110///
111/// The row index is split into a group and an offset within the group, the group is permuted by
112/// the fixed order in the module documentation, and the two are recombined with the offset as the
113/// high part. The lane index is untouched, which is the property that makes the layout lane
114/// parallel.
115///
116/// # Panics
117///
118/// If `row` is not below `T::WIDTH` or `lane` is not below `T::LANES`.
119#[inline]
120#[must_use]
121pub fn source_index<T: Packable>(row: usize, lane: usize) -> usize {
122    assert!(row < T::WIDTH, "row {row} is outside a {} bit type", T::WIDTH);
123    assert!(lane < T::LANES, "lane {lane} is outside {} lanes", T::LANES);
124    let group_size = T::WIDTH / 8;
125    let group = row / group_size;
126    let offset = row % group_size;
127    ((offset * 8) + ORDER[group]) * T::LANES + lane
128}
129
130/// Rewrites 1024 values from row order into the transposed layout.
131///
132/// # Errors
133///
134/// If either slice is not exactly [`VALUES`] long.
135pub fn transpose<T: Packable>(input: &[T], output: &mut [T]) -> Result<()> {
136    check_vector_len(input.len(), "input")?;
137    check_vector_len(output.len(), "output")?;
138    for row in 0..T::WIDTH {
139        for lane in 0..T::LANES {
140            output[row * T::LANES + lane] = input[source_index::<T>(row, lane)];
141        }
142    }
143    Ok(())
144}
145
146/// Rewrites 1024 values from the transposed layout back into row order.
147///
148/// # Errors
149///
150/// If either slice is not exactly [`VALUES`] long.
151pub fn untranspose<T: Packable>(input: &[T], output: &mut [T]) -> Result<()> {
152    check_vector_len(input.len(), "input")?;
153    check_vector_len(output.len(), "output")?;
154    for row in 0..T::WIDTH {
155        for lane in 0..T::LANES {
156            output[source_index::<T>(row, lane)] = input[row * T::LANES + lane];
157        }
158    }
159    Ok(())
160}
161
162/// How many words of `T` a packed vector of the given width occupies.
163///
164/// Every lane contributes `width` words, which is the same `width * 1024` bits the naive layout
165/// would use. The layout costs nothing in space.
166#[must_use]
167pub fn packed_len<T: Packable>(width: usize) -> usize {
168    width * T::LANES
169}
170
171/// The smallest bit width that can hold every value in the slice. Zero for an empty slice or a
172/// slice of zeros, which [`pack_transposed`] handles as the degenerate case that stores nothing.
173#[must_use]
174pub fn required_width<T: Packable>(values: &[T]) -> usize {
175    let max = values.iter().copied().max().map_or(0, T::to_u64);
176    (64 - max.leading_zeros()) as usize
177}
178
179/// Packs a transposed vector at a fixed bit width.
180///
181/// The input is 1024 values already in the layout [`transpose`] produces, and the output is
182/// [`packed_len`] words. Every lane is packed independently and the loop over lanes is the one a
183/// SIMD implementation replaces with a single register.
184///
185/// # Errors
186///
187/// If the input is not [`VALUES`] long, if the output is not [`packed_len`] long, if `width`
188/// exceeds the width of the type, or if a value does not fit in `width` bits.
189pub fn pack_transposed<T: Packable>(input: &[T], width: usize, output: &mut [T]) -> Result<()> {
190    check_vector_len(input.len(), "input")?;
191    check_width::<T>(width)?;
192    if output.len() != packed_len::<T>(width) {
193        return Err(Error::internal(format!(
194            "a {width} bit packed vector is {} words, not {}",
195            packed_len::<T>(width),
196            output.len()
197        )));
198    }
199    if width == 0 {
200        // Nothing is stored. The caller has already established that every value is zero, either
201        // by asking for `required_width` or by being the CONSTANT encoding, and the check below
202        // enforces it rather than trusting it.
203        return check_all_zero(input);
204    }
205
206    let mask = low_mask(width);
207    let lanes = T::LANES;
208    for lane in 0..lanes {
209        // Bits already sitting in `accumulator`, always below `T::WIDTH` between iterations.
210        let mut filled = 0usize;
211        let mut accumulator = 0u64;
212        let mut word = 0usize;
213        for row in 0..T::WIDTH {
214            let value = input[row * lanes + lane].to_u64();
215            if value & !mask != 0 {
216                return Err(Error::internal(format!("value {value} does not fit in {width} bits")));
217            }
218            accumulator |= value << filled;
219            filled += width;
220            if filled >= T::WIDTH {
221                output[word * lanes + lane] = T::from_u64(accumulator & low_mask(T::WIDTH));
222                word += 1;
223                // The only value that can straddle the word boundary is the one just written, so
224                // the carry is a shift of it rather than anything kept from earlier rows.
225                let consumed = width - (filled - T::WIDTH);
226                filled -= T::WIDTH;
227                accumulator = shift_right(value, consumed);
228            }
229        }
230        debug_assert_eq!(filled, 0, "a packed lane always ends on a word boundary");
231    }
232    Ok(())
233}
234
235/// Unpacks into the transposed layout. The inverse of [`pack_transposed`].
236///
237/// # Errors
238///
239/// If the input is not [`packed_len`] long, if the output is not [`VALUES`] long, or if `width`
240/// exceeds the width of the type.
241pub fn unpack_transposed<T: Packable>(input: &[T], width: usize, output: &mut [T]) -> Result<()> {
242    check_width::<T>(width)?;
243    check_vector_len(output.len(), "output")?;
244    if input.len() != packed_len::<T>(width) {
245        return Err(Error::internal(format!(
246            "a {width} bit packed vector is {} words, not {}",
247            packed_len::<T>(width),
248            input.len()
249        )));
250    }
251    if width == 0 {
252        output.fill(T::from_u64(0));
253        return Ok(());
254    }
255
256    let mask = low_mask(width);
257    let lanes = T::LANES;
258    for lane in 0..lanes {
259        // Bits of the current word not yet handed out, right aligned in `buffer`.
260        let mut available = 0usize;
261        let mut buffer = 0u64;
262        let mut word = 0usize;
263        for row in 0..T::WIDTH {
264            let value = if available >= width {
265                let value = buffer & mask;
266                buffer = shift_right(buffer, width);
267                available -= width;
268                value
269            } else {
270                let next = input[word * lanes + lane].to_u64();
271                word += 1;
272                let taken = width - available;
273                let value = buffer | ((next & low_mask(taken)) << available);
274                buffer = shift_right(next, taken);
275                available = T::WIDTH - taken;
276                value
277            };
278            output[row * lanes + lane] = T::from_u64(value);
279        }
280    }
281    Ok(())
282}
283
284/// Packs a vector given in row order, transposing it first.
285///
286/// The engine does not use this. Data written by the storage layer is transposed once on the way
287/// in and stays that way, per the module documentation. This exists for tests, for the format lab,
288/// and for the one place that has to hand back a vector in the order the user gave it.
289///
290/// # Errors
291///
292/// As [`pack_transposed`].
293pub fn pack<T: Packable>(input: &[T], width: usize, output: &mut [T]) -> Result<()> {
294    check_vector_len(input.len(), "input")?;
295    let mut transposed = vec![T::from_u64(0); VALUES];
296    transpose(input, &mut transposed)?;
297    pack_transposed(&transposed, width, output)
298}
299
300/// Unpacks into row order. The inverse of [`pack`], and see its note about who should call it.
301///
302/// # Errors
303///
304/// As [`unpack_transposed`].
305pub fn unpack<T: Packable>(input: &[T], width: usize, output: &mut [T]) -> Result<()> {
306    check_vector_len(output.len(), "output")?;
307    let mut transposed = vec![T::from_u64(0); VALUES];
308    unpack_transposed(input, width, &mut transposed)?;
309    untranspose(&transposed, output)
310}
311
312/// How many bytes [`pack_tail`] writes for `count` values at `width` bits.
313#[must_use]
314pub fn tail_len(count: usize, width: usize) -> usize {
315    (count * width).div_ceil(8)
316}
317
318/// Packs fewer than [`VALUES`] values, sequentially and to a byte boundary.
319///
320/// The transposed layout is all or nothing. A value lives at a row and a lane, the lanes are
321/// interleaved through the whole buffer, and there is no prefix of a packed unit that holds a
322/// prefix of the values. So a unit holding 3 values costs the same as a unit holding 1024, which is
323/// 5 KB to store three numbers, and every nested array in a cascade is short: a dictionary of five
324/// entries, a run length array, an exception list.
325///
326/// This is the other layout for exactly those. It is the obvious sequential one, value 0 in the low
327/// bits, and it has the dependency chain the transposed layout was chosen to avoid. That is
328/// affordable here and only here: a tail is at most 1023 values and is decoded once, so the chain
329/// is bounded by a number that does not grow with the data, while a full unit is on the hot path of
330/// every scan in the system.
331///
332/// # Errors
333///
334/// If `count` is not below [`VALUES`], if `width` exceeds 64, or if a value does not fit.
335pub fn pack_tail(values: &[u64], width: usize, output: &mut Vec<u8>) -> Result<()> {
336    check_tail(values.len(), width)?;
337    if width == 0 {
338        return check_all_zero(values);
339    }
340    let mask = low_mask(width);
341    // 128 bits, because the accumulator holds up to 7 bits left over from the previous value plus a
342    // whole 64 bit one.
343    let mut accumulator: u128 = 0;
344    let mut filled = 0usize;
345    for value in values {
346        if value & !mask != 0 {
347            return Err(Error::internal(format!("value {value} does not fit in {width} bits")));
348        }
349        accumulator |= u128::from(*value) << filled;
350        filled += width;
351        while filled >= 8 {
352            output.push((accumulator & 0xff) as u8);
353            accumulator >>= 8;
354            filled -= 8;
355        }
356    }
357    if filled > 0 {
358        output.push((accumulator & 0xff) as u8);
359    }
360    Ok(())
361}
362
363/// Unpacks what [`pack_tail`] wrote.
364///
365/// # Errors
366///
367/// If `count` is not below [`VALUES`], if `width` exceeds 64, or if the input is shorter than
368/// [`tail_len`].
369pub fn unpack_tail(input: &[u8], width: usize, count: usize) -> Result<Vec<u64>> {
370    check_tail(count, width)?;
371    if width == 0 {
372        return Ok(vec![0; count]);
373    }
374    if input.len() < tail_len(count, width) {
375        return Err(Error::internal(format!(
376            "{count} values at {width} bits need {} bytes and there are {}",
377            tail_len(count, width),
378            input.len()
379        )));
380    }
381    let mask = u128::from(low_mask(width));
382    let mut values = Vec::with_capacity(count);
383    let mut accumulator: u128 = 0;
384    let mut available = 0usize;
385    let mut at = 0usize;
386    for _ in 0..count {
387        while available < width {
388            accumulator |= u128::from(input[at]) << available;
389            at += 1;
390            available += 8;
391        }
392        values.push((accumulator & mask) as u64);
393        accumulator >>= width;
394        available -= width;
395    }
396    Ok(values)
397}
398
399fn check_tail(count: usize, width: usize) -> Result<()> {
400    if count >= VALUES {
401        return Err(Error::internal(format!(
402            "{count} values is a whole unit and belongs in the transposed layout"
403        )));
404    }
405    if width > 64 {
406        return Err(Error::internal(format!("{width} bits does not fit in 64")));
407    }
408    Ok(())
409}
410
411fn check_vector_len(len: usize, what: &str) -> Result<()> {
412    if len == VALUES {
413        Ok(())
414    } else {
415        Err(Error::internal(format!("{what} is {len} values, and a packed unit is {VALUES}")))
416    }
417}
418
419fn check_width<T: Packable>(width: usize) -> Result<()> {
420    if width <= T::WIDTH {
421        Ok(())
422    } else {
423        Err(Error::internal(format!("{width} bits does not fit in a {} bit type", T::WIDTH)))
424    }
425}
426
427fn check_all_zero<T: Packable>(input: &[T]) -> Result<()> {
428    match input.iter().position(|value| value.to_u64() != 0) {
429        None => Ok(()),
430        Some(index) => Err(Error::internal(format!(
431            "a zero bit vector cannot hold {:?} at {index}",
432            input[index]
433        ))),
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    /// A xorshift, so that the test data is the same on every host and in every run without the
442    /// workspace growing a dependency for it.
443    struct Random(u64);
444
445    impl Random {
446        fn new() -> Self {
447            Self(0x2545_f491_4f6c_dd1d)
448        }
449
450        fn next(&mut self) -> u64 {
451            self.0 ^= self.0 << 13;
452            self.0 ^= self.0 >> 7;
453            self.0 ^= self.0 << 17;
454            self.0
455        }
456    }
457
458    fn sample<T: Packable>(width: usize) -> Vec<T> {
459        let mut random = Random::new();
460        (0..VALUES).map(|_| T::from_u64(random.next() & low_mask(width))).collect()
461    }
462
463    fn round_trip<T: Packable>(width: usize) {
464        let values = sample::<T>(width);
465        let mut packed = vec![T::from_u64(0); packed_len::<T>(width)];
466        pack(&values, width, &mut packed).unwrap();
467        let mut back = vec![T::from_u64(0); VALUES];
468        unpack(&packed, width, &mut back).unwrap();
469        assert_eq!(back, values, "{width} bits of a {} bit type", T::WIDTH);
470    }
471
472    #[test]
473    fn every_width_of_every_type_round_trips() {
474        for width in 0..=8 {
475            round_trip::<u8>(width);
476        }
477        for width in 0..=16 {
478            round_trip::<u16>(width);
479        }
480        for width in 0..=32 {
481            round_trip::<u32>(width);
482        }
483        for width in 0..=64 {
484            round_trip::<u64>(width);
485        }
486    }
487
488    #[test]
489    fn the_transposed_form_also_round_trips_without_being_reordered() {
490        // What the engine actually does: transpose once, then pack and unpack any number of times
491        // without ever going back to row order.
492        let values = sample::<u32>(19);
493        let mut transposed = vec![0u32; VALUES];
494        transpose(&values, &mut transposed).unwrap();
495        let mut packed = vec![0u32; packed_len::<u32>(19)];
496        pack_transposed(&transposed, 19, &mut packed).unwrap();
497        let mut back = vec![0u32; VALUES];
498        unpack_transposed(&packed, 19, &mut back).unwrap();
499        assert_eq!(back, transposed);
500    }
501
502    #[test]
503    fn the_permutation_is_a_bijection() {
504        // Every value has to land somewhere and no two may land in the same place, or a round trip
505        // would silently drop rows. Checked for all four widths because the group size changes.
506        fn check<T: Packable>() {
507            let mut seen = vec![false; VALUES];
508            for row in 0..T::WIDTH {
509                for lane in 0..T::LANES {
510                    let index = source_index::<T>(row, lane);
511                    assert!(!seen[index], "{index} is written twice for {} bits", T::WIDTH);
512                    seen[index] = true;
513                }
514            }
515            assert!(seen.into_iter().all(|hit| hit));
516        }
517        check::<u8>();
518        check::<u16>();
519        check::<u32>();
520        check::<u64>();
521    }
522
523    #[test]
524    fn transposing_is_not_the_identity() {
525        // If it were, the test above would be passing on a layout that is not the FastLanes one.
526        let values: Vec<u32> = (0..VALUES).map(|index| index as u32).collect();
527        let mut transposed = vec![0u32; VALUES];
528        transpose(&values, &mut transposed).unwrap();
529        assert_ne!(transposed, values);
530        let mut back = vec![0u32; VALUES];
531        untranspose(&transposed, &mut back).unwrap();
532        assert_eq!(back, values);
533    }
534
535    #[test]
536    fn a_full_width_pack_is_the_data_itself() {
537        // 64 bits of a 64 bit type has no packing to do, and the loop that handles the general case
538        // has to get the degenerate one right rather than shifting by 64 and wrapping.
539        let values = sample::<u64>(64);
540        let mut transposed = vec![0u64; VALUES];
541        transpose(&values, &mut transposed).unwrap();
542        let mut packed = vec![0u64; packed_len::<u64>(64)];
543        pack_transposed(&transposed, 64, &mut packed).unwrap();
544        assert_eq!(packed, transposed);
545    }
546
547    #[test]
548    fn a_zero_width_vector_stores_nothing_and_reads_back_as_zeros() {
549        let values = vec![0u32; VALUES];
550        assert_eq!(required_width(&values), 0);
551        let mut packed = Vec::new();
552        pack(&values, 0, &mut packed).unwrap();
553        let mut back = vec![7u32; VALUES];
554        unpack(&packed, 0, &mut back).unwrap();
555        assert_eq!(back, values);
556    }
557
558    #[test]
559    fn required_width_is_the_bits_of_the_largest_value() {
560        assert_eq!(required_width::<u32>(&[]), 0);
561        assert_eq!(required_width::<u32>(&[0, 0]), 0);
562        assert_eq!(required_width::<u32>(&[1]), 1);
563        assert_eq!(required_width::<u32>(&[255, 3]), 8);
564        assert_eq!(required_width::<u32>(&[256]), 9);
565        assert_eq!(required_width::<u64>(&[u64::MAX]), 64);
566    }
567
568    #[test]
569    fn a_value_too_wide_for_the_width_is_an_error_rather_than_silent_truncation() {
570        let mut values = vec![0u32; VALUES];
571        values[500] = 8;
572        let mut transposed = vec![0u32; VALUES];
573        transpose(&values, &mut transposed).unwrap();
574        let mut packed = vec![0u32; packed_len::<u32>(3)];
575        let error = pack_transposed(&transposed, 3, &mut packed).unwrap_err();
576        assert!(error.message().contains("does not fit in 3 bits"), "{error}");
577    }
578
579    #[test]
580    fn a_wrong_sized_buffer_is_an_error() {
581        let values = vec![0u32; VALUES];
582        let mut packed = vec![0u32; 3];
583        let error = pack(&values, 5, &mut packed).unwrap_err();
584        assert!(error.message().contains("words"), "{error}");
585
586        let short = vec![0u32; 7];
587        let mut output = vec![0u32; VALUES];
588        let error = unpack(&short, 5, &mut output).unwrap_err();
589        assert!(error.message().contains("words"), "{error}");
590    }
591
592    #[test]
593    fn a_nonzero_value_at_zero_width_is_an_error() {
594        let mut values = vec![0u32; VALUES];
595        values[9] = 1;
596        let mut packed = Vec::new();
597        let error = pack(&values, 0, &mut packed).unwrap_err();
598        assert!(error.message().contains("zero bit vector"), "{error}");
599    }
600
601    #[test]
602    fn packing_at_a_width_the_type_cannot_hold_is_an_error() {
603        let values = vec![0u16; VALUES];
604        let mut packed = vec![0u16; 17 * 64];
605        let error = pack(&values, 17, &mut packed).unwrap_err();
606        assert!(error.message().contains("16 bit type"), "{error}");
607    }
608
609    #[test]
610    fn a_tail_round_trips_at_every_width_and_every_length() {
611        let mut random = Random::new();
612        for width in [0usize, 1, 3, 7, 8, 13, 31, 32, 33, 63, 64] {
613            for count in [0usize, 1, 2, 7, 8, 9, 100, 1023] {
614                let values: Vec<u64> =
615                    (0..count).map(|_| random.next() & low_mask(width)).collect();
616                let mut bytes = Vec::new();
617                pack_tail(&values, width, &mut bytes).unwrap();
618                assert_eq!(bytes.len(), tail_len(count, width), "{count} at {width}");
619                assert_eq!(unpack_tail(&bytes, width, count).unwrap(), values);
620            }
621        }
622    }
623
624    #[test]
625    fn a_tail_costs_its_own_values_and_not_a_whole_unit() {
626        // The reason it exists. Three 40 bit values in the transposed layout is a 5 KB buffer.
627        let values = vec![(1u64 << 39) + 1; 3];
628        let mut bytes = Vec::new();
629        pack_tail(&values, 40, &mut bytes).unwrap();
630        assert_eq!(bytes.len(), 15);
631        assert_eq!(packed_len::<u64>(40) * 8, 5120);
632    }
633
634    #[test]
635    fn a_whole_unit_is_refused_by_the_tail_packer() {
636        let values = vec![0u64; VALUES];
637        let error = pack_tail(&values, 4, &mut Vec::new()).unwrap_err();
638        assert!(error.message().contains("whole unit"), "{error}");
639    }
640
641    #[test]
642    fn a_short_tail_buffer_is_an_error() {
643        let error = unpack_tail(&[0, 0], 8, 5).unwrap_err();
644        assert!(error.message().contains("need 5 bytes"), "{error}");
645    }
646
647    #[test]
648    fn the_packed_size_is_the_same_as_the_naive_layout() {
649        for width in 0..=32 {
650            assert_eq!(packed_len::<u32>(width) * 32, width * VALUES);
651        }
652    }
653}