Skip to main content

fsst/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg(target_endian = "little")]
3
4/// Throw a compiler error if a type isn't guaranteed to have a specific size in bytes.
5macro_rules! assert_sizeof {
6    ($typ:ty => $size_in_bytes:expr) => {
7        const _: [u8; $size_in_bytes] = [0; std::mem::size_of::<$typ>()];
8    };
9}
10
11use lossy_pht::LossyPHT;
12use std::fmt::{Debug, Formatter};
13use std::mem::MaybeUninit;
14
15mod builder;
16mod lossy_pht;
17
18pub use builder::*;
19
20/// `Symbol`s are small (up to 8-byte) segments of strings, stored in a [`Compressor`][`crate::Compressor`] and
21/// identified by an 8-bit code.
22#[derive(Copy, Clone, PartialEq, Eq, Hash)]
23pub struct Symbol(u64);
24
25assert_sizeof!(Symbol => 8);
26
27impl Symbol {
28    /// Zero value for `Symbol`.
29    pub const ZERO: Self = Self::zero();
30
31    /// Constructor for a `Symbol` from an 8-element byte slice.
32    pub fn from_slice(slice: &[u8; 8]) -> Self {
33        let num: u64 = u64::from_le_bytes(*slice);
34
35        Self(num)
36    }
37
38    /// Return a zero symbol
39    const fn zero() -> Self {
40        Self(0)
41    }
42
43    /// Create a new single-byte symbol
44    pub fn from_u8(value: u8) -> Self {
45        Self(value as u64)
46    }
47}
48
49impl Symbol {
50    /// Calculate the length of the symbol in bytes. Always a value between 1 and 8.
51    ///
52    /// Each symbol has the capacity to hold up to 8 bytes of data, but the symbols
53    /// can contain fewer bytes, padded with 0x00. There is a special case of a symbol
54    /// that holds the byte 0x00. In that case, the symbol contains `0x0000000000000000`
55    /// but we want to interpret that as a one-byte symbol containing `0x00`.
56    #[allow(clippy::len_without_is_empty)]
57    pub fn len(self) -> usize {
58        let numeric = self.0;
59        // For little-endian platforms, this counts the number of *trailing* zeros
60        let null_bytes = (numeric.leading_zeros() >> 3) as usize;
61
62        // Special case handling of a symbol with all-zeros. This is actually
63        // a 1-byte symbol containing 0x00.
64        let len = size_of::<Self>() - null_bytes;
65        if len == 0 { 1 } else { len }
66    }
67
68    /// Returns the Symbol's inner representation.
69    #[inline]
70    pub fn to_u64(self) -> u64 {
71        self.0
72    }
73
74    /// Get the first byte of the symbol as a `u8`.
75    ///
76    /// If the symbol is empty, this will return the zero byte.
77    #[inline]
78    pub fn first_byte(self) -> u8 {
79        self.0 as u8
80    }
81
82    /// Get the first two bytes of the symbol as a `u16`.
83    ///
84    /// If the Symbol is one or zero bytes, this will return `0u16`.
85    #[inline]
86    pub fn first2(self) -> u16 {
87        self.0 as u16
88    }
89
90    /// Get the first three bytes of the symbol as a `u64`.
91    ///
92    /// If the Symbol is one or zero bytes, this will return `0u64`.
93    #[inline]
94    pub fn first3(self) -> u64 {
95        self.0 & 0xFF_FF_FF
96    }
97
98    /// Return a new `Symbol` by logically concatenating ourselves with another `Symbol`.
99    pub fn concat(self, other: Self) -> Self {
100        assert!(
101            self.len() + other.len() <= 8,
102            "cannot build symbol with length > 8"
103        );
104
105        let self_len = self.len();
106
107        Self((other.0 << (8 * self_len)) | self.0)
108    }
109}
110
111impl Debug for Symbol {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        write!(f, "[")?;
114
115        let slice = &self.0.to_le_bytes()[0..self.len()];
116        for c in slice.iter().map(|c| *c as char) {
117            if ('!'..='~').contains(&c) {
118                write!(f, "{c}")?;
119            } else if c == '\n' {
120                write!(f, " \\n ")?;
121            } else if c == '\t' {
122                write!(f, " \\t ")?;
123            } else if c == ' ' {
124                write!(f, " SPACE ")?;
125            } else {
126                write!(f, " 0x{:X?} ", c as u8)?
127            }
128        }
129
130        write!(f, "]")
131    }
132}
133
134/// A packed type containing a code value, as well as metadata about the symbol referred to by
135/// the code.
136///
137/// Logically, codes can range from 0-255 inclusive. This type holds both the 8-bit code as well as
138/// other metadata bit-packed into a `u16`.
139///
140/// The bottom 8 bits contain EITHER a code for a symbol stored in the table, OR a raw byte.
141///
142/// The interpretation depends on the 9th bit: when toggled off, the value stores a raw byte, and when
143/// toggled on, it stores a code. Thus if you examine the bottom 9 bits of the `u16`, you have an extended
144/// code range, where the values 0-255 are raw bytes, and the values 256-510 represent codes 0-254. 511 is
145/// a placeholder for the invalid code here.
146///
147/// Bits 12-15 store the length of the symbol (values ranging from 0-8).
148#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149struct Code(u16);
150
151/// Code used to indicate bytes that are not in the symbol table.
152///
153/// When compressing a string that cannot fully be expressed with the symbol table, the compressed
154/// output will contain an `ESCAPE` byte followed by a raw byte. At decompression time, the presence
155/// of `ESCAPE` indicates that the next byte should be appended directly to the result instead of
156/// being looked up in the symbol table.
157pub const ESCAPE_CODE: u8 = 255;
158
159/// Number of bits in the `ExtendedCode` that are used to dictate a code value.
160pub const FSST_CODE_BITS: usize = 9;
161
162/// First bit of the "length" portion of an extended code.
163pub const FSST_LEN_BITS: usize = 12;
164
165/// Maximum code value in the extended code range.
166pub const FSST_CODE_MAX: u16 = 1 << FSST_CODE_BITS;
167
168/// Maximum value for the extended code range.
169///
170/// When truncated to u8 this is code 255, which is equivalent to [`ESCAPE_CODE`].
171pub const FSST_CODE_MASK: u16 = FSST_CODE_MAX - 1;
172
173/// First code in the symbol table that corresponds to a non-escape symbol.
174pub const FSST_CODE_BASE: u16 = 256;
175
176#[allow(clippy::len_without_is_empty)]
177impl Code {
178    /// Code for an unused slot in a symbol table or index.
179    ///
180    /// This corresponds to the maximum code with a length of 1.
181    pub const UNUSED: Self = Code(FSST_CODE_MASK + (1 << 12));
182
183    /// Create a new code for a symbol of given length.
184    fn new_symbol(code: u8, len: usize) -> Self {
185        Self(code as u16 + ((len as u16) << FSST_LEN_BITS))
186    }
187
188    /// Code for a new symbol during the building phase.
189    ///
190    /// The code is remapped from 0..254 to 256...510.
191    fn new_symbol_building(code: u8, len: usize) -> Self {
192        Self(code as u16 + 256 + ((len as u16) << FSST_LEN_BITS))
193    }
194
195    /// Create a new code corresponding for an escaped byte.
196    fn new_escape(byte: u8) -> Self {
197        Self((byte as u16) + (1 << FSST_LEN_BITS))
198    }
199
200    #[inline]
201    fn code(self) -> u8 {
202        self.0 as u8
203    }
204
205    #[inline]
206    fn extended_code(self) -> u16 {
207        self.0 & 0b111_111_111
208    }
209
210    #[inline]
211    fn len(self) -> u16 {
212        self.0 >> FSST_LEN_BITS
213    }
214}
215
216impl Debug for Code {
217    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
218        f.debug_struct("TrainingCode")
219            .field("code", &(self.0 as u8))
220            .field("is_escape", &(self.0 < 256))
221            .field("len", &(self.0 >> 12))
222            .finish()
223    }
224}
225
226/// Decompressor uses a symbol table to take a stream of 8-bit codes into a string.
227#[derive(Clone)]
228pub struct Decompressor<'a> {
229    /// Slice mapping codes to symbols.
230    pub(crate) symbols: &'a [Symbol; 255],
231
232    /// Slice containing the length of each symbol in the `symbols` slice.
233    pub(crate) lengths: &'a [u8; 255],
234}
235
236impl<'a> Decompressor<'a> {
237    /// Returns a new decompressor that uses the provided symbol table.
238    pub fn new(symbols: &'a [Symbol; 255], lengths: &'a [u8; 255]) -> Self {
239        Self { symbols, lengths }
240    }
241
242    /// Returns an upper bound on the size of the decompressed data.
243    pub fn max_decompression_capacity(&self, compressed: &[u8]) -> usize {
244        size_of::<Symbol>() * (compressed.len() + 1)
245    }
246
247    /// Decompress a slice of codes into a provided buffer.
248    ///
249    /// The provided `decoded` buffer must be at least the size of the decoded data.
250    ///
251    /// ## Panics
252    ///
253    /// If the caller fails to provide sufficient capacity in the decoded buffer.
254    /// An upper bound on the required capacity can be obtained by calling [`Self::max_decompression_capacity`].
255    ///
256    /// ## Example
257    ///
258    /// ```
259    /// use fsst::{Symbol, Compressor, CompressorBuilder};
260    /// let compressor = {
261    ///     let mut builder = CompressorBuilder::new();
262    ///     builder.insert(Symbol::from_slice(&[b'h', b'e', b'l', b'l', b'o', b'o', b'o', b'o']), 8);
263    ///     builder.build()
264    /// };
265    ///
266    /// let decompressor = compressor.decompressor();
267    ///
268    /// let mut decompressed = Vec::with_capacity(8);
269    ///
270    /// let len = decompressor.decompress_into(&[0], decompressed.spare_capacity_mut());
271    /// assert_eq!(len, 8);
272    /// unsafe { decompressed.set_len(len) };
273    /// assert_eq!(&decompressed, "helloooo".as_bytes());
274    /// ```
275    pub fn decompress_into(&self, compressed: &[u8], decoded: &mut [MaybeUninit<u8>]) -> usize {
276        // Ensure the target buffer is at least half the size of the input buffer.
277        // This is the theortical smallest a valid target can be, and occurs when
278        // every input code is an escape.
279        assert!(
280            decoded.len() >= compressed.len() / 2,
281            "decoded is smaller than lower-bound decompressed size"
282        );
283
284        unsafe {
285            let mut in_ptr = compressed.as_ptr();
286            let _in_begin = in_ptr;
287            let in_end = in_ptr.add(compressed.len());
288
289            let mut out_ptr: *mut u8 = decoded.as_mut_ptr().cast();
290            let out_begin = out_ptr.cast_const();
291            let out_end = decoded.as_ptr().add(decoded.len()).cast::<u8>();
292
293            macro_rules! store_next_symbol {
294                ($code:expr) => {{
295                    out_ptr
296                        .cast::<u64>()
297                        .write_unaligned(self.symbols.get_unchecked($code as usize).to_u64());
298                    out_ptr = out_ptr.add(*self.lengths.get_unchecked($code as usize) as usize);
299                }};
300            }
301
302            // First we try loading 8 bytes at a time.
303            if decoded.len() >= 8 * size_of::<Symbol>() && compressed.len() >= 8 {
304                // Extract the loop condition since the compiler fails to do so
305                let block_out_end = out_end.sub(8 * size_of::<Symbol>());
306                let block_in_end = in_end.sub(8);
307
308                while out_ptr.cast_const() <= block_out_end && in_ptr < block_in_end {
309                    // Note that we load a little-endian u64 here.
310                    let next_block = in_ptr.cast::<u64>().read_unaligned();
311                    let escape_mask = (next_block & 0x8080808080808080)
312                        & ((((!next_block) & 0x7F7F7F7F7F7F7F7F) + 0x7F7F7F7F7F7F7F7F)
313                            ^ 0x8080808080808080);
314
315                    // If there are no escape codes, we write each symbol one by one.
316                    if escape_mask == 0 {
317                        let code = (next_block & 0xFF) as u8;
318                        store_next_symbol!(code);
319                        let code = ((next_block >> 8) & 0xFF) as u8;
320                        store_next_symbol!(code);
321                        let code = ((next_block >> 16) & 0xFF) as u8;
322                        store_next_symbol!(code);
323                        let code = ((next_block >> 24) & 0xFF) as u8;
324                        store_next_symbol!(code);
325                        let code = ((next_block >> 32) & 0xFF) as u8;
326                        store_next_symbol!(code);
327                        let code = ((next_block >> 40) & 0xFF) as u8;
328                        store_next_symbol!(code);
329                        let code = ((next_block >> 48) & 0xFF) as u8;
330                        store_next_symbol!(code);
331                        let code = ((next_block >> 56) & 0xFF) as u8;
332                        store_next_symbol!(code);
333                        in_ptr = in_ptr.add(8);
334                    } else if (next_block & 0x00FF00FF00FF00FF) == 0x00FF00FF00FF00FF {
335                        // All 4 even-positioned bytes are ESCAPE_CODE.
336                        // Batch-extract the 4 raw bytes at odd positions.
337                        out_ptr.write(((next_block >> 8) & 0xFF) as u8);
338                        out_ptr.add(1).write(((next_block >> 24) & 0xFF) as u8);
339                        out_ptr.add(2).write(((next_block >> 40) & 0xFF) as u8);
340                        out_ptr.add(3).write(((next_block >> 56) & 0xFF) as u8);
341                        out_ptr = out_ptr.add(4);
342                        in_ptr = in_ptr.add(8);
343                    } else {
344                        // Otherwise, find the first escape code and write the symbols up to that point.
345                        let first_escape_pos = escape_mask.trailing_zeros() >> 3; // Divide bits to bytes
346                        debug_assert!(first_escape_pos < 8);
347                        match first_escape_pos {
348                            7 => {
349                                let code = (next_block & 0xFF) as u8;
350                                store_next_symbol!(code);
351                                let code = ((next_block >> 8) & 0xFF) as u8;
352                                store_next_symbol!(code);
353                                let code = ((next_block >> 16) & 0xFF) as u8;
354                                store_next_symbol!(code);
355                                let code = ((next_block >> 24) & 0xFF) as u8;
356                                store_next_symbol!(code);
357                                let code = ((next_block >> 32) & 0xFF) as u8;
358                                store_next_symbol!(code);
359                                let code = ((next_block >> 40) & 0xFF) as u8;
360                                store_next_symbol!(code);
361                                let code = ((next_block >> 48) & 0xFF) as u8;
362                                store_next_symbol!(code);
363
364                                in_ptr = in_ptr.add(7);
365                            }
366                            6 => {
367                                let code = (next_block & 0xFF) as u8;
368                                store_next_symbol!(code);
369                                let code = ((next_block >> 8) & 0xFF) as u8;
370                                store_next_symbol!(code);
371                                let code = ((next_block >> 16) & 0xFF) as u8;
372                                store_next_symbol!(code);
373                                let code = ((next_block >> 24) & 0xFF) as u8;
374                                store_next_symbol!(code);
375                                let code = ((next_block >> 32) & 0xFF) as u8;
376                                store_next_symbol!(code);
377                                let code = ((next_block >> 40) & 0xFF) as u8;
378                                store_next_symbol!(code);
379
380                                let escaped = ((next_block >> 56) & 0xFF) as u8;
381                                out_ptr.write(escaped);
382                                out_ptr = out_ptr.add(1);
383
384                                in_ptr = in_ptr.add(8);
385                            }
386                            5 => {
387                                let code = (next_block & 0xFF) as u8;
388                                store_next_symbol!(code);
389                                let code = ((next_block >> 8) & 0xFF) as u8;
390                                store_next_symbol!(code);
391                                let code = ((next_block >> 16) & 0xFF) as u8;
392                                store_next_symbol!(code);
393                                let code = ((next_block >> 24) & 0xFF) as u8;
394                                store_next_symbol!(code);
395                                let code = ((next_block >> 32) & 0xFF) as u8;
396                                store_next_symbol!(code);
397
398                                let escaped = ((next_block >> 48) & 0xFF) as u8;
399                                out_ptr.write(escaped);
400                                out_ptr = out_ptr.add(1);
401
402                                in_ptr = in_ptr.add(7);
403                            }
404                            4 => {
405                                let code = (next_block & 0xFF) as u8;
406                                store_next_symbol!(code);
407                                let code = ((next_block >> 8) & 0xFF) as u8;
408                                store_next_symbol!(code);
409                                let code = ((next_block >> 16) & 0xFF) as u8;
410                                store_next_symbol!(code);
411                                let code = ((next_block >> 24) & 0xFF) as u8;
412                                store_next_symbol!(code);
413
414                                let escaped = ((next_block >> 40) & 0xFF) as u8;
415                                out_ptr.write(escaped);
416                                out_ptr = out_ptr.add(1);
417
418                                in_ptr = in_ptr.add(6);
419                            }
420                            3 => {
421                                let code = (next_block & 0xFF) as u8;
422                                store_next_symbol!(code);
423                                let code = ((next_block >> 8) & 0xFF) as u8;
424                                store_next_symbol!(code);
425                                let code = ((next_block >> 16) & 0xFF) as u8;
426                                store_next_symbol!(code);
427
428                                let escaped = ((next_block >> 32) & 0xFF) as u8;
429                                out_ptr.write(escaped);
430                                out_ptr = out_ptr.add(1);
431
432                                in_ptr = in_ptr.add(5);
433                            }
434                            2 => {
435                                let code = (next_block & 0xFF) as u8;
436                                store_next_symbol!(code);
437                                let code = ((next_block >> 8) & 0xFF) as u8;
438                                store_next_symbol!(code);
439
440                                let escaped = ((next_block >> 24) & 0xFF) as u8;
441                                out_ptr.write(escaped);
442                                out_ptr = out_ptr.add(1);
443
444                                in_ptr = in_ptr.add(4);
445                            }
446                            1 => {
447                                let code = (next_block & 0xFF) as u8;
448                                store_next_symbol!(code);
449
450                                let escaped = ((next_block >> 16) & 0xFF) as u8;
451                                out_ptr.write(escaped);
452                                out_ptr = out_ptr.add(1);
453
454                                in_ptr = in_ptr.add(3);
455                            }
456                            0 => {
457                                // Otherwise, we actually need to decompress the next byte
458                                // Extract the second byte from the u32
459                                let escaped = ((next_block >> 8) & 0xFF) as u8;
460                                in_ptr = in_ptr.add(2);
461                                out_ptr.write(escaped);
462                                out_ptr = out_ptr.add(1);
463                            }
464                            _ => unreachable!(),
465                        }
466                    }
467                }
468            }
469
470            // Otherwise, fall back to 1-byte reads using 8-byte writes where safe.
471            while out_end.offset_from(out_ptr) >= size_of::<Symbol>() as isize && in_ptr < in_end {
472                let code = in_ptr.read();
473                in_ptr = in_ptr.add(1);
474
475                if code == ESCAPE_CODE {
476                    assert!(
477                        in_ptr < in_end,
478                        "truncated compressed string: escape code at end of input"
479                    );
480                    out_ptr.write(in_ptr.read());
481                    in_ptr = in_ptr.add(1);
482                    out_ptr = out_ptr.add(1);
483                } else {
484                    store_next_symbol!(code);
485                }
486            }
487
488            // For the last few bytes (if any) where we can't do an 8-byte unaligned write.
489            while in_ptr < in_end {
490                let code = in_ptr.read();
491                in_ptr = in_ptr.add(1);
492
493                if code == ESCAPE_CODE {
494                    assert!(
495                        in_ptr < in_end,
496                        "truncated compressed string: escape code at end of input"
497                    );
498                    assert!(
499                        out_ptr.cast_const() < out_end,
500                        "output buffer sized too small"
501                    );
502                    out_ptr.write(in_ptr.read());
503                    in_ptr = in_ptr.add(1);
504                    out_ptr = out_ptr.add(1);
505                } else {
506                    let len = *self.lengths.get_unchecked(code as usize) as usize;
507                    assert!(
508                        out_end.offset_from(out_ptr) >= len as isize,
509                        "output buffer sized too small"
510                    );
511                    let sym = self.symbols.get_unchecked(code as usize).to_u64();
512                    let sym_bytes = sym.to_le_bytes();
513                    std::ptr::copy_nonoverlapping(sym_bytes.as_ptr(), out_ptr, len);
514                    out_ptr = out_ptr.add(len);
515                }
516            }
517
518            assert_eq!(
519                in_ptr, in_end,
520                "decompression should exhaust input before output"
521            );
522
523            out_ptr.offset_from(out_begin) as usize
524        }
525    }
526
527    /// Decompress a byte slice that was previously returned by a compressor using the same symbol
528    /// table into a new vector of bytes.
529    pub fn decompress(&self, compressed: &[u8]) -> Vec<u8> {
530        let mut decoded = Vec::with_capacity(self.max_decompression_capacity(compressed) + 7);
531
532        let len = self.decompress_into(compressed, decoded.spare_capacity_mut());
533        // SAFETY: len bytes have now been initialized by the decompressor.
534        unsafe { decoded.set_len(len) };
535        decoded
536    }
537}
538
539/// Persistable components of a [`Compressor`].
540pub struct CompressorParts {
541    /// Symbols indexed by code.
542    pub symbols: [Symbol; 255],
543
544    /// Symbol lengths in bytes.
545    pub lengths: [u8; 255],
546
547    /// Number of populated symbols, excluding the escape code.
548    pub n_symbols: u8,
549}
550
551/// A compressor that uses a symbol table to greedily compress strings.
552///
553/// The `Compressor` is the central component of FSST. You can create a compressor either by
554/// default (i.e. an empty compressor), or by [training][`Self::train`] it on an input corpus of text.
555///
556/// Example usage:
557///
558/// ```
559/// use fsst::{Symbol, Compressor, CompressorBuilder};
560/// let compressor = {
561///     let mut builder = CompressorBuilder::new();
562///     builder.insert(Symbol::from_slice(&[b'h', b'e', b'l', b'l', b'o', 0, 0, 0]), 5);
563///     builder.build()
564/// };
565///
566/// let compressed = compressor.compress("hello".as_bytes());
567/// assert_eq!(compressed, vec![0u8]);
568/// ```
569#[derive(Clone)]
570pub struct Compressor {
571    /// Table mapping codes to symbols.
572    pub(crate) symbols: [Symbol; 255],
573
574    /// Length of each symbol, values range from 1-8.
575    pub(crate) lengths: [u8; 255],
576
577    /// The number of entries in the symbol table that have been populated, not counting
578    /// the escape values.
579    pub(crate) n_symbols: u8,
580
581    /// Inverted index mapping 2-byte symbols to codes
582    codes_two_byte: Vec<Code>,
583
584    /// Limit of no suffixes.
585    has_suffix_code: u8,
586
587    /// Lossy perfect hash table for looking up codes to symbols that are 3 bytes or more
588    lossy_pht: LossyPHT,
589}
590
591/// The core structure of the FSST codec, holding a mapping between `Symbol`s and `Code`s.
592///
593/// The symbol table is trained on a corpus of data in the form of a single byte array, building up
594/// a mapping of 1-byte "codes" to sequences of up to 8 plaintext bytes, or "symbols".
595impl Compressor {
596    /// Using the symbol table, runs a single cycle of compression on an input word, writing
597    /// the output into `out_ptr`.
598    ///
599    /// # Returns
600    ///
601    /// This function returns a tuple of (advance_in, advance_out) with the number of bytes
602    /// for the caller to advance the input and output pointers.
603    ///
604    /// `advance_in` is the number of bytes to advance the input pointer before the next call.
605    ///
606    /// `advance_out` is the number of bytes to advance `out_ptr` before the next call.
607    ///
608    /// # Safety
609    ///
610    /// `out_ptr` must never be NULL or otherwise point to invalid memory.
611    pub unsafe fn compress_word(&self, word: u64, out_ptr: *mut u8) -> (usize, usize) {
612        // Speculatively write the first byte of `word` at offset 1. This is necessary if it is an escape, and
613        // if it isn't, it will be overwritten anyway.
614        //
615        // SAFETY: caller ensures out_ptr is not null
616        let first_byte = word as u8;
617        // SAFETY: out_ptr is not null
618        unsafe { out_ptr.byte_add(1).write_unaligned(first_byte) };
619
620        // First, check the two_bytes table
621        // SAFETY: codes_two_byte has exactly 65536 entries and `word as u16` is always in [0, 65535].
622        let code_twobyte = unsafe { *self.codes_two_byte.get_unchecked(word as u16 as usize) };
623
624        if code_twobyte.code() < self.has_suffix_code {
625            // 2 byte code without having to worry about longer matches.
626            // SAFETY: out_ptr is not null.
627            unsafe { std::ptr::write(out_ptr, code_twobyte.code()) };
628
629            // Advance input by symbol length (2) and output by a single code byte
630            (2, 1)
631        } else {
632            // Probe the hash table
633            let entry = self.lossy_pht.lookup(word);
634
635            // Now, downshift the `word` and the `entry` to see if they align.
636            let ignored_bits = entry.ignored_bits;
637            if entry.code != Code::UNUSED
638                && compare_masked(word, entry.symbol.to_u64(), ignored_bits)
639            {
640                // Advance the input by the symbol length (variable) and the output by one code byte
641                // SAFETY: out_ptr is not null.
642                unsafe { std::ptr::write(out_ptr, entry.code.code()) };
643                (entry.code.len() as usize, 1)
644            } else {
645                // SAFETY: out_ptr is not null
646                unsafe { std::ptr::write(out_ptr, code_twobyte.code()) };
647
648                // Advance the input by the symbol length (variable) and the output by either 1
649                // byte (if was one-byte code) or two bytes (escape).
650                (
651                    code_twobyte.len() as usize,
652                    // Predicated version of:
653                    //
654                    // if entry.code >= 256 {
655                    //      2
656                    // } else {
657                    //      1
658                    // }
659                    1 + (code_twobyte.extended_code() >> 8) as usize,
660                )
661            }
662        }
663    }
664
665    /// Compress many lines in bulk.
666    pub fn compress_bulk(&self, lines: &Vec<&[u8]>) -> Vec<Vec<u8>> {
667        let mut res = Vec::new();
668
669        for line in lines {
670            res.push(self.compress(line));
671        }
672
673        res
674    }
675
676    /// Compress a string, writing its result into the target slice.
677    ///
678    /// The target buffer must have enough capacity to hold the encoded data.
679    ///
680    /// When this call returns, `values` will hold the compressed bytes up to
681    /// return value.
682    ///
683    /// ```
684    /// use fsst::{Compressor, CompressorBuilder, Symbol};
685    ///
686    /// let mut compressor = CompressorBuilder::new();
687    /// assert!(compressor.insert(Symbol::from_slice(b"aaaaaaaa"), 8));
688    ///
689    /// let compressor = compressor.build();
690    ///
691    /// let mut compressed_values = Vec::with_capacity(1_024);
692    ///
693    /// // SAFETY: we have over-sized compressed_values.
694    /// unsafe {
695    ///     let valid_length = compressor.compress_into(b"aaaaaaaa", compressed_values.spare_capacity_mut());
696    ///     compressed_values.set_len(valid_length);
697    /// };
698    ///
699    /// assert_eq!(compressed_values, vec![0u8]);
700    /// ```
701    ///
702    /// # Safety
703    ///
704    /// It is up to the caller to ensure the provided buffer is large enough to hold
705    /// all encoded data.
706    pub unsafe fn compress_into(&self, plaintext: &[u8], values: &mut [MaybeUninit<u8>]) -> usize {
707        // input is empty, we must set the output length's to 0.
708        if plaintext.is_empty() {
709            return 0;
710        }
711
712        let mut in_ptr = plaintext.as_ptr();
713        let mut out_ptr = values.as_mut_ptr().cast::<u8>();
714
715        // SAFETY: `end` will point just after the end of the `plaintext` slice.
716        let in_end = unsafe { in_ptr.byte_add(plaintext.len()) };
717        let in_end_sub8 = in_end as usize - 8;
718        // SAFETY: `end` will point just after the end of the `values` allocation.
719        let out_end = unsafe { out_ptr.byte_add(values.len()) };
720
721        while (in_ptr as usize) <= in_end_sub8 && unsafe { out_end.offset_from(out_ptr) } >= 2 {
722            // SAFETY: pointer ranges are checked in the loop condition
723            unsafe {
724                // Load a full 8-byte word of data from in_ptr.
725                // SAFETY: caller asserts in_ptr is not null. we may read past end of pointer though.
726                let word: u64 = std::ptr::read_unaligned(in_ptr as *const u64);
727                let (advance_in, advance_out) = self.compress_word(word, out_ptr);
728                in_ptr = in_ptr.byte_add(advance_in);
729                out_ptr = out_ptr.byte_add(advance_out);
730            };
731        }
732
733        let remaining_bytes = unsafe { in_end.byte_offset_from(in_ptr) };
734        assert!(
735            out_ptr < out_end || remaining_bytes == 0,
736            "output buffer sized too small"
737        );
738
739        let remaining_bytes = remaining_bytes as usize;
740
741        // Load the last `remaining_byte`s of data into a final world. We then replicate the loop above,
742        // but shift data out of this word rather than advancing an input pointer and potentially reading
743        // unowned memory.
744        let mut bytes = [0u8; 8];
745        // SAFETY: remaining_bytes <= 8
746        unsafe { std::ptr::copy_nonoverlapping(in_ptr, bytes.as_mut_ptr(), remaining_bytes) };
747        let mut last_word = u64::from_le_bytes(bytes);
748
749        while in_ptr < in_end && unsafe { out_end.offset_from(out_ptr) } >= 2 {
750            // Load a full 8-byte word of data from in_ptr.
751            // SAFETY: caller asserts in_ptr is not null
752            let (advance_in, advance_out) = unsafe { self.compress_word(last_word, out_ptr) };
753            // SAFETY: pointer ranges are checked in the loop condition
754            unsafe {
755                in_ptr = in_ptr.add(advance_in);
756                out_ptr = out_ptr.add(advance_out);
757            }
758
759            last_word = advance_8byte_word(last_word, advance_in);
760        }
761
762        if in_ptr < in_end && unsafe { out_end.offset_from(out_ptr) } == 1 {
763            // compress_word writes two bytes speculatively, so use scratch space for a one-byte tail.
764            let mut scratch = [0u8; 2];
765            let (advance_in, advance_out) =
766                unsafe { self.compress_word(last_word, scratch.as_mut_ptr()) };
767            assert_eq!(advance_out, 1, "output buffer sized too small");
768
769            unsafe {
770                out_ptr.write(scratch[0]);
771                out_ptr = out_ptr.add(1);
772                in_ptr = in_ptr.add(advance_in);
773            }
774        }
775
776        // in_ptr should have exceeded in_end
777        assert!(
778            in_ptr >= in_end,
779            "exhausted output buffer before exhausting input, there is a bug in SymbolTable::compress()"
780        );
781
782        assert!(out_ptr <= out_end, "output buffer sized too small");
783
784        // SAFETY: out_ptr is derived from the `values` allocation.
785        let bytes_written = unsafe { out_ptr.offset_from(values.as_ptr().cast()) };
786        assert!(
787            bytes_written >= 0,
788            "out_ptr ended before it started, not possible"
789        );
790
791        // SAFETY: we have initialized `bytes_written` values in the output buffer.
792        bytes_written as usize
793    }
794
795    /// Use the symbol table to compress the plaintext into a sequence of codes and escapes.
796    pub fn compress(&self, plaintext: &[u8]) -> Vec<u8> {
797        if plaintext.is_empty() {
798            return Vec::new();
799        }
800
801        let mut buffer = Vec::with_capacity(plaintext.len() * 2);
802
803        // SAFETY: the largest compressed size would be all escapes == 2*plaintext_len
804        unsafe {
805            let length = self.compress_into(plaintext, buffer.spare_capacity_mut());
806            buffer.set_len(length);
807        };
808
809        buffer
810    }
811
812    /// Access the decompressor that can be used to decompress strings emitted from this
813    /// `Compressor` instance.
814    pub fn decompressor(&self) -> Decompressor<'_> {
815        Decompressor::new(&self.symbols, &self.lengths)
816    }
817
818    /// Returns the populated symbols, indexed by code.
819    pub fn symbol_table(&self) -> &[Symbol] {
820        &self.symbols[..self.n_symbols()]
821    }
822
823    /// Returns the populated symbol lengths, indexed by code.
824    pub fn symbol_lengths(&self) -> &[u8] {
825        &self.lengths[..self.n_symbols()]
826    }
827
828    /// Consumes the compressor and returns its persistable parts.
829    pub fn into_parts(self) -> CompressorParts {
830        CompressorParts {
831            symbols: self.symbols,
832            lengths: self.lengths,
833            n_symbols: self.n_symbols,
834        }
835    }
836
837    /// Number of symbols present in the compressor's symbol table.
838    ///
839    /// Since the symbol table and length are padded to 255 elements, this value indicates the number of valid entries.
840    pub fn n_symbols(&self) -> usize {
841        self.n_symbols as usize
842    }
843
844    /// Rebuild a compressor from an existing symbol table.
845    ///
846    /// This will not attempt to optimize or re-order the codes.
847    pub fn rebuild_from(symbols: impl AsRef<[Symbol]>, symbol_lens: impl AsRef<[u8]>) -> Self {
848        let symbols_slice = symbols.as_ref();
849        let symbol_lens = symbol_lens.as_ref();
850
851        assert_eq!(
852            symbols_slice.len(),
853            symbol_lens.len(),
854            "symbols and lengths differ"
855        );
856        assert!(
857            symbols_slice.len() <= 255,
858            "symbol table len must be <= 255, was {}",
859            symbols_slice.len()
860        );
861        validate_symbol_order(symbol_lens);
862        let n_symbols = symbols_slice.len();
863
864        // Insert the symbols in their given order into the FSST lookup structures.
865        let mut symbols = [Symbol::ZERO; 255];
866        symbols[0..n_symbols].copy_from_slice(symbols_slice);
867        let mut lengths = [0u8; 255];
868        lengths[0..n_symbols].copy_from_slice(symbol_lens);
869        let mut lossy_pht = LossyPHT::new();
870
871        let mut codes_one_byte = [Code::UNUSED; 256];
872
873        // Insert all of the one byte symbols first.
874        for (code, (&symbol, &len)) in symbols.iter().zip(lengths.iter()).enumerate() {
875            if len == 1 {
876                codes_one_byte[symbol.first_byte() as usize] = Code::new_symbol(code as u8, 1);
877            }
878        }
879
880        let mut codes_two_byte = Vec::with_capacity(65_536);
881        for _ in 0..256 {
882            codes_two_byte.extend_from_slice(&codes_one_byte);
883        }
884
885        // Insert the two byte symbols, possibly overwriting slots for one-byte symbols and escapes.
886        for (code, (&symbol, &len)) in symbols.iter().zip(lengths.iter()).enumerate() {
887            match len {
888                2 => {
889                    codes_two_byte[symbol.first2() as usize] = Code::new_symbol(code as u8, 2);
890                }
891                3.. => {
892                    assert!(
893                        lossy_pht.insert(symbol, len as usize, code as u8),
894                        "rebuild symbol insertion into PHT must succeed"
895                    );
896                }
897                _ => { /* Covered by the 1-byte loop above. */ }
898            }
899        }
900
901        // Find the position of the first 2-byte code that has a suffix later in the table
902        let mut has_suffix_code = 0u8;
903        for (code, (&symbol, &len)) in symbols.iter().zip(lengths.iter()).enumerate() {
904            if len != 2 {
905                break;
906            }
907            let rest = &symbols[code..];
908            if rest
909                .iter()
910                .any(|&other| other.len() > 2 && symbol.first2() == other.first2())
911            {
912                has_suffix_code = code as u8;
913                break;
914            }
915        }
916
917        Compressor {
918            n_symbols: n_symbols as u8,
919            symbols,
920            lengths,
921            codes_two_byte,
922            lossy_pht,
923            has_suffix_code,
924        }
925    }
926}
927
928#[inline]
929pub(crate) fn advance_8byte_word(word: u64, bytes: usize) -> u64 {
930    // shift the word off the low-end, because little endian means the first
931    // char is stored in the LSB.
932    //
933    // Note that even though this looks like it branches, Rust compiles this to a
934    // conditional move instruction. See `<https://godbolt.org/z/Pbvre65Pq>`
935    if bytes == 8 { 0 } else { word >> (8 * bytes) }
936}
937
938fn validate_symbol_order(symbol_lens: &[u8]) {
939    // Ensure that the symbol table is ordered by length, 23456781
940    let mut expected = 2;
941    for (idx, &len) in symbol_lens.iter().enumerate() {
942        if expected == 1 {
943            assert_eq!(
944                len, 1,
945                "symbol code={idx} should be one byte, was {len} bytes"
946            );
947        } else {
948            if len == 1 {
949                expected = 1;
950            }
951
952            // we're in the non-zero portion.
953            assert!(
954                len >= expected,
955                "symbol code={idx} breaks violates FSST symbol table ordering"
956            );
957            expected = len;
958        }
959    }
960}
961
962#[inline]
963pub(crate) fn compare_masked(left: u64, right: u64, ignored_bits: u16) -> bool {
964    let mask = u64::MAX >> ignored_bits;
965    (left & mask) == right
966}
967
968#[cfg(test)]
969mod test {
970    use super::*;
971    use std::{iter, mem};
972    #[test]
973    fn test_stuff() {
974        let compressor = {
975            let mut builder = CompressorBuilder::new();
976            builder.insert(Symbol::from_slice(b"helloooo"), 8);
977            builder.build()
978        };
979
980        let decompressor = compressor.decompressor();
981
982        let mut decompressed = Vec::with_capacity(8 + 7);
983
984        let len = decompressor.decompress_into(&[0], decompressed.spare_capacity_mut());
985        assert_eq!(len, 8);
986        unsafe { decompressed.set_len(len) };
987        assert_eq!(&decompressed, "helloooo".as_bytes());
988    }
989
990    #[test]
991    fn test_symbols_good() {
992        let symbols_u64: &[u64] = &[
993            24931, 25698, 25442, 25699, 25186, 25444, 24932, 25188, 25185, 25441, 25697, 25700,
994            24929, 24930, 25443, 25187, 6513249, 6512995, 6578786, 6513761, 6513507, 6382434,
995            6579042, 6512994, 6447460, 6447969, 6382178, 6579041, 6512993, 6448226, 6513250,
996            6579297, 6513506, 6447459, 6513764, 6447458, 6578529, 6382180, 6513762, 6447714,
997            6579299, 6513508, 6382436, 6513763, 6578532, 6381924, 6448228, 6579300, 6381921,
998            6382690, 6382179, 6447713, 6447972, 6513505, 6447457, 6382692, 6513252, 6578785,
999            6578787, 6578531, 6448225, 6382177, 6382433, 6578530, 6448227, 6381922, 6578788,
1000            6579044, 6382691, 6512996, 6579043, 6579298, 6447970, 6447716, 6447971, 6381923,
1001            6447715, 97, 98, 100, 99, 97, 98, 99, 100,
1002        ];
1003        let symbols: &[Symbol] = unsafe { mem::transmute(symbols_u64) };
1004        let lens: Vec<u8> = iter::repeat_n(2u8, 16)
1005            .chain(iter::repeat_n(3u8, 61))
1006            .chain(iter::repeat_n(1u8, 8))
1007            .collect();
1008
1009        let compressor = Compressor::rebuild_from(symbols, lens);
1010        let built_symbols: &[u64] =
1011            unsafe { mem::transmute(&compressor.symbol_table()[0..compressor.n_symbols()]) };
1012        assert_eq!(built_symbols, symbols_u64);
1013    }
1014
1015    #[should_panic(expected = "assertion `left == right` failed")]
1016    #[test]
1017    fn test_symbols_bad() {
1018        let symbols: &[u64] = &[
1019            24931, 25698, 25442, 25699, 25186, 25444, 24932, 25188, 25185, 25441, 25697, 25700,
1020            24929, 24930, 25443, 25187, 6513249, 6512995, 6578786, 6513761, 6513507, 6382434,
1021            6579042, 6512994, 6447460, 6447969, 6382178, 6579041, 6512993, 6448226, 6513250,
1022            6579297, 6513506, 6447459, 6513764, 6447458, 6578529, 6382180, 6513762, 6447714,
1023            6579299, 6513508, 6382436, 6513763, 6578532, 6381924, 6448228, 6579300, 6381921,
1024            6382690, 6382179, 6447713, 6447972, 6513505, 6447457, 6382692, 6513252, 6578785,
1025            6578787, 6578531, 6448225, 6382177, 6382433, 6578530, 6448227, 6381922, 6578788,
1026            6579044, 6382691, 6512996, 6579043, 6579298, 6447970, 6447716, 6447971, 6381923,
1027            6447715, 97, 98, 100, 99, 97, 98, 99, 100,
1028        ];
1029        let lens: Vec<u8> = iter::repeat_n(2u8, 16)
1030            .chain(iter::repeat_n(3u8, 61))
1031            .chain(iter::repeat_n(1u8, 8))
1032            .collect();
1033
1034        let mut builder = CompressorBuilder::new();
1035        for (symbol, len) in symbols.iter().zip(lens.iter()) {
1036            let symbol = Symbol::from_slice(&symbol.to_le_bytes());
1037            builder.insert(symbol, *len as usize);
1038        }
1039        let compressor = builder.build();
1040        let built_symbols: &[u64] =
1041            unsafe { mem::transmute(&compressor.symbol_table()[0..compressor.n_symbols()]) };
1042        assert_eq!(built_symbols, symbols);
1043    }
1044}