dsi_bitstream/codes/vbyte.rs
1/*
2 * SPDX-FileCopyrightText: 2023 Tommaso Fontana
3 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR MIT
6 */
7
8//! Variable-length byte codes.
9//!
10//! These codes represent a natural number as a sequence of bytes, the length of
11//! the sequence depends on the magnitude of the number. They are used in many
12//! contexts, and they are known under a plethora of different names such as
13//! “vbyte”, “varint”, “[variable-length quantity]”, “LEB”, and so on.
14//!
15//! There are several variants of their definition, but their implied
16//! distribution is always ≈ 1/*x*<sup>8/7</sup>
17//!
18//! The supported range is [0 . . 2⁶⁴).
19//!
20//! # Definition
21//!
22//! Since there are a few slightly different variants used in production code
23//! and in the literature, before going into the details of this implementation
24//! we will try to define a clear taxonomy by explaining in detail the
25//! three properties that define such variants.
26//!
27//! The simplest variable-length byte code encodes a number with a binary
28//! representation of *k* bits using ⌈*k* / 7⌉ bytes. The binary representation
29//! is left-padded with zeros so to obtain exactly ⌈*k* / 7⌉ blocks of 7 bits.
30//! Each block is stored in a byte, prefixed with a continuation bit which is
31//! one for all blocks except for the last one.
32//!
33//! ## Endianness
34//!
35//! The first property is the endianness of the bytes: in big-endian codes, the
36//! first byte contains the highest (most significant) bits, whereas in
37//! little-endian codes, the first byte contains the lowest (least significant)
38//! bits.
39//!
40//! In the big-endian variant the first byte holds the most significant bits.
41//! Note that the *complete* codes implemented here (which drop the redundant
42//! encodings) are not lexicographic across code-length boundaries: for example
43//! 16511 encodes to `[0xff, 0x7f]`, but the larger 16512 encodes to
44//! `[0x80, 0x80, 0x00]`, which compares byte-wise smaller.
45//!
46//! ## Completeness
47//!
48//! This basic representation discussed above is not *complete*, as there are
49//! sequences that are not used. For example, zero can be written in many ways
50//! (e.g., `0x00` or `0x80 0x00`), but we are using only the single-byte
51//! representation. Incompleteness leads to a (small) loss in compression.
52//!
53//! To have completeness, one can offset the representation in *k* bits by the
54//! maximum number representable using *k* − 1 bits. That is, we represent the
55//! interval [0 . . 2⁷) with one byte, then the interval [2⁷ . . 2⁷ + 2¹⁴) with two
56//! bytes, the interval [2⁷ + 2¹⁴ . . 2⁷ + 2¹⁴ + 2²¹) with three bytes, and so on.
57//!
58//! ## Grouping
59//!
60//! In the basic representation, the continuation bit is the most significant
61//! bit of each byte. However, one can gather all continuation bits in the first
62//! byte ([as UTF-8 does]). This approach makes it possible to compute the
63//! length of the code using a call to [`u8::leading_ones`] on the first negated
64//! byte, which usually maps to a negation and a call to a fast instruction for
65//! the detection of the most significant bit, improving branch prediction.
66//!
67//! Note that if the code is grouped, choosing a code with the same endianness
68//! as your hardware can lead to a performance improvement, as after the first
69//! byte the rest of the code can be read with a [`read_exact`].
70//!
71//! ## Sign
72//!
73//! It is possible to extend the codes to represent signed integers. Two
74//! possible approaches are using a [bijective mapping] between the integers and
75//! the natural numbers, or defining a specialized code.
76//!
77//! # Implementations
78//!
79//! We provide two unsigned, ungrouped, complete representations, one
80//! [big-endian] and one [little-endian]. We recommend in general the big-endian
81//! version as it is lexicographical. However, the big-endian version needs a
82//! small buffer when writing, so on some hardware the little-endian version
83//! might be faster.
84//!
85//! Note that the endianness of the code is independent from the endianness of
86//! the underlying bit stream, as it just depends on the order in which bytes are
87//! written.
88//!
89//! Since this code is byte-aligned, we provide also convenient, fast methods
90//! [`vbyte_write`] and [`vbyte_read`] that can be used on types implementing
91//! [`std::io::Read`] and [`std::io::Write`]. There are also endianness-specific
92//! methods [`vbyte_write_be`], [`vbyte_write_le`], [`vbyte_read_be`], and
93//! [`vbyte_read_le`].
94//!
95//! # Examples
96//!
97//! - The [LEB128] code used by LLVM is a little-endian incomplete ungrouped
98//! representation. There is both a signed and an unsigned version; the signed
99//! version represents negative numbers using two's complement.
100//!
101//! - The [code used by git] is a big-endian complete ungrouped representation.
102//!
103//! - [This implementation in folly] is a little-endian incomplete ungrouped
104//! representation.
105//!
106//! [variable-length quantity]: https://en.wikipedia.org/wiki/Variable-length_quantity
107//! [as UTF-8 does]: https://en.wikipedia.org/wiki/UTF-8
108//! [`read_exact`]: std::io::Read::read_exact
109//! [bijective mapping]: crate::codes::ToInt
110//! [big-endian]: VByteBeRead
111//! [little-endian]: VByteLeRead
112//! [LEB128]: https://en.wikipedia.org/wiki/LEB128
113//! [code used by git]: https://github.com/git/git/blob/7fb6aefd2aaffe66e614f7f7b83e5b7ab16d4806/varint.c
114//! [This implementation in folly]: https://github.com/facebook/folly/blob/dd4a5eb057afbc3c7c7da71801df2ee3c61c47d1/folly/Varint.h#L109
115
116use crate::traits::*;
117
118/// Returns the length of the variable-length byte code for `n` in bytes.
119#[must_use]
120#[inline(always)]
121pub const fn byte_len_vbyte(mut n: u64) -> usize {
122 let mut len = 1;
123 loop {
124 n >>= 7;
125 if n == 0 {
126 return len;
127 }
128 n -= 1;
129 len += 1;
130 }
131}
132
133/// Returns the length of the variable-length byte code for `n` in bits.
134#[must_use]
135#[inline(always)]
136pub const fn bit_len_vbyte(n: u64) -> usize {
137 8 * byte_len_vbyte(n)
138}
139/// The number of bytes of the longest vbyte code for a `u64`; no
140/// well-formed code is longer, and no well-formed code of this length
141/// decodes to a value above [`u64::MAX`]. Used by the debug-only
142/// malformed-input guards in the decoders.
143const MAX_VBYTE_LEN: usize = byte_len_vbyte(u64::MAX);
144
145/// Trait for reading big-endian variable-length byte codes.
146///
147/// Note that the endianness of the code is independent
148/// from the endianness of the underlying bit stream.
149pub trait VByteBeRead<E: Endianness>: BitRead<E> {
150 fn read_vbyte_be(&mut self) -> Result<u64, Self::Error>;
151}
152
153/// Trait for reading little-endian variable-length byte codes.
154///
155/// Note that the endianness of the code is independent
156/// from the endianness of the underlying bit stream.
157pub trait VByteLeRead<E: Endianness>: BitRead<E> {
158 fn read_vbyte_le(&mut self) -> Result<u64, Self::Error>;
159}
160
161impl<E: Endianness, B: BitRead<E>> VByteBeRead<E> for B {
162 #[inline(always)]
163 fn read_vbyte_be(&mut self) -> Result<u64, Self::Error> {
164 let mut byte = self.read_bits(8)?;
165 let mut value = byte & 0x7F;
166 // Malformed-input guards, compiled out of release builds: a vbyte
167 // code for a u64 has at most MAX_VBYTE_LEN bytes and its value must
168 // fit in a u64. The value is mirrored in u128 shadow arithmetic
169 // because the u64 shift below drops overflowing bits silently.
170 #[cfg(debug_assertions)]
171 let (mut bytes, mut shadow) = (1_usize, u128::from(byte & 0x7F));
172 while (byte >> 7) != 0 {
173 value += 1;
174 byte = self.read_bits(8)?;
175 #[cfg(debug_assertions)]
176 {
177 bytes += 1;
178 debug_assert!(
179 bytes <= MAX_VBYTE_LEN,
180 "malformed vbyte code: more than {} bytes",
181 MAX_VBYTE_LEN
182 );
183 shadow = ((shadow + 1) << 7) | u128::from(byte & 0x7F);
184 debug_assert!(
185 shadow <= u128::from(u64::MAX),
186 "malformed vbyte code: the value does not fit in a u64"
187 );
188 }
189 value = (value << 7) | (byte & 0x7F);
190 }
191 Ok(value)
192 }
193}
194
195impl<E: Endianness, B: BitRead<E>> VByteLeRead<E> for B {
196 #[inline(always)]
197 fn read_vbyte_le(&mut self) -> Result<u64, Self::Error> {
198 let mut result = 0;
199 let mut shift = 0;
200 // Malformed-input guards, compiled out of release builds; see
201 // read_vbyte_be. The byte-count assertion also fires before the
202 // shift amount can reach 64.
203 #[cfg(debug_assertions)]
204 let (mut bytes, mut shadow) = (0_usize, 0_u128);
205 loop {
206 let byte = self.read_bits(8)?;
207 #[cfg(debug_assertions)]
208 {
209 bytes += 1;
210 debug_assert!(
211 bytes <= MAX_VBYTE_LEN,
212 "malformed vbyte code: more than {} bytes",
213 MAX_VBYTE_LEN
214 );
215 shadow += u128::from(byte & 0x7F) << shift;
216 if (byte >> 7) != 0 {
217 shadow += 1_u128 << (shift + 7);
218 }
219 debug_assert!(
220 shadow <= u128::from(u64::MAX),
221 "malformed vbyte code: the value does not fit in a u64"
222 );
223 }
224 result += (byte & 0x7F) << shift;
225 if (byte >> 7) == 0 {
226 break;
227 }
228 shift += 7;
229 result += 1 << shift;
230 }
231 Ok(result)
232 }
233}
234
235/// Trait for writing big-endian variable-length byte codes.
236///
237/// Note that the endianness of the code is independent
238/// from the endianness of the underlying bit stream.
239pub trait VByteBeWrite<E: Endianness>: BitWrite<E> {
240 fn write_vbyte_be(&mut self, n: u64) -> Result<usize, Self::Error>;
241}
242/// Trait for writing little-endian variable-length byte codes.
243///
244/// Note that the endianness of the code is independent
245/// from the endianness of the underlying bit stream.
246pub trait VByteLeWrite<E: Endianness>: BitWrite<E> {
247 fn write_vbyte_le(&mut self, n: u64) -> Result<usize, Self::Error>;
248}
249
250impl<E: Endianness, B: BitWrite<E>> VByteBeWrite<E> for B {
251 #[inline(always)]
252 fn write_vbyte_be(&mut self, mut n: u64) -> Result<usize, Self::Error> {
253 let mut buf = [0u8; 10];
254 let mut pos = buf.len() - 1;
255 buf[pos] = (n & 0x7F) as u8;
256 n >>= 7;
257 while n != 0 {
258 n -= 1;
259 pos -= 1;
260 buf[pos] = 0x80 | (n & 0x7F) as u8;
261 n >>= 7;
262 }
263 let bytes_to_write = buf.len() - pos;
264 for &byte in &buf[pos..] {
265 self.write_bits(byte as u64, 8)?;
266 }
267 Ok(bytes_to_write * 8)
268 }
269}
270
271impl<E: Endianness, B: BitWrite<E>> VByteLeWrite<E> for B {
272 #[inline(always)]
273 fn write_vbyte_le(&mut self, mut n: u64) -> Result<usize, Self::Error> {
274 let mut len = 1;
275 loop {
276 let byte = (n & 0x7F) as u8;
277 n >>= 7;
278 if n != 0 {
279 self.write_bits((byte | 0x80) as u64, 8)?;
280 } else {
281 self.write_bits(byte as u64, 8)?;
282 break;
283 }
284 n -= 1;
285 len += 1;
286 }
287 Ok(len * 8)
288 }
289}
290
291/// Writes a natural number to a byte stream using variable-length byte codes and
292/// returns the number of bytes written.
293///
294/// This method just delegates to the correct endianness-specific method.
295#[inline(always)]
296#[cfg(feature = "std")]
297pub fn vbyte_write<E: Endianness, W: std::io::Write>(
298 n: u64,
299 writer: &mut W,
300) -> std::io::Result<usize> {
301 if core::any::TypeId::of::<E>() == core::any::TypeId::of::<BigEndian>() {
302 vbyte_write_be(n, writer)
303 } else {
304 vbyte_write_le(n, writer)
305 }
306}
307
308/// Encode a natural number to a big-endian byte stream using variable-length
309/// byte codes and return the number of bytes written.
310#[inline(always)]
311#[cfg(feature = "std")]
312pub fn vbyte_write_be(mut n: u64, w: &mut impl std::io::Write) -> std::io::Result<usize> {
313 let mut buf = [0u8; 10];
314 let mut pos = buf.len() - 1;
315 buf[pos] = (n & 0x7F) as u8;
316 n >>= 7;
317 while n != 0 {
318 n -= 1;
319 pos -= 1;
320 buf[pos] = 0x80 | (n & 0x7F) as u8;
321 n >>= 7;
322 }
323 let bytes_to_write = buf.len() - pos;
324 w.write_all(&buf[pos..])?;
325 Ok(bytes_to_write)
326}
327
328/// Encode a natural number to a little-endian byte stream using variable-length
329/// byte codes and return the number of bytes written.
330#[inline(always)]
331#[cfg(feature = "std")]
332pub fn vbyte_write_le(mut n: u64, writer: &mut impl std::io::Write) -> std::io::Result<usize> {
333 let mut len = 1;
334 loop {
335 let byte = (n & 0x7F) as u8;
336 n >>= 7;
337 if n != 0 {
338 writer.write_all(&[byte | 0x80])?;
339 } else {
340 writer.write_all(&[byte])?;
341 break;
342 }
343 n -= 1;
344 len += 1;
345 }
346 Ok(len)
347}
348
349/// Decode a natural number from a byte stream using variable-length byte codes.
350///
351/// This method just delegates to the correct endianness-specific method.
352#[inline(always)]
353#[cfg(feature = "std")]
354pub fn vbyte_read<E: Endianness, R: std::io::Read>(reader: &mut R) -> std::io::Result<u64> {
355 if core::any::TypeId::of::<E>() == core::any::TypeId::of::<BigEndian>() {
356 vbyte_read_be(reader)
357 } else {
358 vbyte_read_le(reader)
359 }
360}
361
362/// Decode a natural number from a big-endian byte stream using variable-length
363/// byte codes.
364#[inline(always)]
365#[cfg(feature = "std")]
366pub fn vbyte_read_be(reader: &mut impl std::io::Read) -> std::io::Result<u64> {
367 let mut buf = [0u8; 1];
368 let mut value: u64;
369 reader.read_exact(&mut buf)?;
370 value = (buf[0] & 0x7F) as u64;
371 // Malformed-input guards, compiled out of release builds; see
372 // read_vbyte_be in VByteBeRead.
373 #[cfg(debug_assertions)]
374 let (mut bytes, mut shadow) = (1_usize, u128::from(buf[0] & 0x7F));
375 while (buf[0] >> 7) != 0 {
376 value += 1;
377 reader.read_exact(&mut buf)?;
378 #[cfg(debug_assertions)]
379 {
380 bytes += 1;
381 debug_assert!(
382 bytes <= MAX_VBYTE_LEN,
383 "malformed vbyte code: more than {} bytes",
384 MAX_VBYTE_LEN
385 );
386 shadow = ((shadow + 1) << 7) | u128::from(buf[0] & 0x7F);
387 debug_assert!(
388 shadow <= u128::from(u64::MAX),
389 "malformed vbyte code: the value does not fit in a u64"
390 );
391 }
392 value = (value << 7) | ((buf[0] & 0x7F) as u64);
393 }
394 Ok(value)
395}
396
397/// Decode a natural number from a little-endian byte stream using
398/// variable-length byte codes.
399#[inline(always)]
400#[cfg(feature = "std")]
401pub fn vbyte_read_le(reader: &mut impl std::io::Read) -> std::io::Result<u64> {
402 let mut result = 0;
403 let mut shift = 0;
404 let mut buffer = [0; 1];
405 // Malformed-input guards, compiled out of release builds; see
406 // read_vbyte_le in VByteLeRead.
407 #[cfg(debug_assertions)]
408 let (mut bytes, mut shadow) = (0_usize, 0_u128);
409 loop {
410 reader.read_exact(&mut buffer)?;
411 let byte = buffer[0];
412 #[cfg(debug_assertions)]
413 {
414 bytes += 1;
415 debug_assert!(
416 bytes <= MAX_VBYTE_LEN,
417 "malformed vbyte code: more than {} bytes",
418 MAX_VBYTE_LEN
419 );
420 shadow += u128::from(byte & 0x7F) << shift;
421 if (byte >> 7) != 0 {
422 shadow += 1_u128 << (shift + 7);
423 }
424 debug_assert!(
425 shadow <= u128::from(u64::MAX),
426 "malformed vbyte code: the value does not fit in a u64"
427 );
428 }
429 result += ((byte & 0x7F) as u64) << shift;
430 if (byte >> 7) == 0 {
431 break;
432 }
433 shift += 7;
434 result += 1 << shift;
435 }
436 Ok(result)
437}
438
439#[cfg(test)]
440#[cfg(feature = "std")]
441mod tests {
442 #[allow(unused_imports)]
443 use super::*;
444
445 /// An 11-byte run of continuation bytes: one byte longer than the
446 /// longest well-formed u64 code. Caught by the debug-only byte-count
447 /// guard; release builds keep the unchecked decode paths.
448 #[cfg(debug_assertions)]
449 const OVERLONG: [u8; 12] = [0xFF; 12];
450
451 /// A terminated ten-byte code whose payload exceeds u64::MAX. Caught by
452 /// the debug-only u128 shadow accumulation (the u64 arithmetic would
453 /// truncate silently).
454 #[cfg(debug_assertions)]
455 const HIGH_PAYLOAD: [u8; 10] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F];
456
457 macro_rules! malformed_tests {
458 ($bit_be:ident, $bit_le:ident, $io_be:ident, $io_le:ident, $data:ident) => {
459 #[test]
460 #[cfg(debug_assertions)]
461 #[should_panic(expected = "malformed vbyte code")]
462 fn $bit_be() {
463 use crate::prelude::{BufBitReader, MemWordReader};
464 let mut r = <BufBitReader<crate::traits::BE, _>>::new(MemWordReader::new(&$data));
465 let _ = r.read_vbyte_be();
466 }
467
468 #[test]
469 #[cfg(debug_assertions)]
470 #[should_panic(expected = "malformed vbyte code")]
471 fn $bit_le() {
472 use crate::prelude::{BufBitReader, MemWordReader};
473 let mut r = <BufBitReader<crate::traits::LE, _>>::new(MemWordReader::new(&$data));
474 let _ = r.read_vbyte_le();
475 }
476
477 #[test]
478 #[cfg(debug_assertions)]
479 #[should_panic(expected = "malformed vbyte code")]
480 fn $io_be() {
481 let _ = vbyte_read_be(&mut std::io::Cursor::new(&$data));
482 }
483
484 #[test]
485 #[cfg(debug_assertions)]
486 #[should_panic(expected = "malformed vbyte code")]
487 fn $io_le() {
488 let _ = vbyte_read_le(&mut std::io::Cursor::new(&$data));
489 }
490 };
491 }
492
493 malformed_tests!(
494 test_overlong_bit_be,
495 test_overlong_bit_le,
496 test_overlong_io_be,
497 test_overlong_io_le,
498 OVERLONG
499 );
500 malformed_tests!(
501 test_high_payload_bit_be,
502 test_high_payload_bit_le,
503 test_high_payload_io_be,
504 test_high_payload_io_le,
505 HIGH_PAYLOAD
506 );
507
508 const UPPER_BOUND_1: u64 = 128;
509 const UPPER_BOUND_2: u64 = 128_u64.pow(2) + UPPER_BOUND_1;
510 const UPPER_BOUND_3: u64 = 128_u64.pow(3) + UPPER_BOUND_2;
511 const UPPER_BOUND_4: u64 = 128_u64.pow(4) + UPPER_BOUND_3;
512 const UPPER_BOUND_5: u64 = 128_u64.pow(5) + UPPER_BOUND_4;
513 const UPPER_BOUND_6: u64 = 128_u64.pow(6) + UPPER_BOUND_5;
514 const UPPER_BOUND_7: u64 = 128_u64.pow(7) + UPPER_BOUND_6;
515 const UPPER_BOUND_8: u64 = 128_u64.pow(8) + UPPER_BOUND_7;
516
517 macro_rules! impl_tests {
518 ($test_name:ident, $E:ty) => {
519 #[test]
520 fn $test_name() -> std::io::Result<()> {
521 const MAX: usize = 1 << 20;
522 const MIN: usize = 0;
523 let mut buffer = std::io::Cursor::new(Vec::with_capacity(128));
524 let mut lens = Vec::new();
525
526 for i in MIN..MAX {
527 lens.push(vbyte_write::<$E, _>(i as _, &mut buffer)?);
528 }
529 buffer.set_position(0);
530 for (i, l) in (MIN..MAX).zip(lens.iter()) {
531 let j = vbyte_read::<$E, _>(&mut buffer)?;
532 assert_eq!(byte_len_vbyte(i as _), *l);
533 assert_eq!(j, i as u64);
534 }
535
536 let values = [
537 0,
538 UPPER_BOUND_1 - 1,
539 UPPER_BOUND_1 + 1,
540 UPPER_BOUND_2 - 1,
541 UPPER_BOUND_2 + 1,
542 UPPER_BOUND_3 - 1,
543 UPPER_BOUND_3 + 1,
544 UPPER_BOUND_4 - 1,
545 UPPER_BOUND_4 + 1,
546 UPPER_BOUND_5 - 1,
547 UPPER_BOUND_5 + 1,
548 UPPER_BOUND_6 - 1,
549 UPPER_BOUND_6 + 1,
550 UPPER_BOUND_7 - 1,
551 UPPER_BOUND_7 + 1,
552 UPPER_BOUND_8 - 1,
553 UPPER_BOUND_8 + 1,
554 u64::MAX,
555 ];
556
557 let tell: u64 = buffer.position();
558 for &i in values.iter() {
559 vbyte_write::<$E, _>(i, &mut buffer)?;
560 }
561 buffer.set_position(tell);
562 for &i in values.iter() {
563 assert_eq!(i, vbyte_read::<$E, _>(&mut buffer)?);
564 }
565 Ok(())
566 }
567 };
568 }
569
570 impl_tests!(test_vbytes_be, BE);
571 impl_tests!(test_vbytes_le, LE);
572}