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}