Skip to main content

structured_zstd/encoding/
workspace.rs

1//! One allocation per compression context, carved into the context's tables
2//! and buffers (upstream zstd `ZSTD_cwksp`, `zstd_cwksp.h`).
3//!
4//! A context that holds each buffer as a separate `Vec` hands the allocator the
5//! decision of whether those pages survive between frames, and a
6//! general-purpose allocator gives them back once their sum outgrows its own
7//! trim heuristic; every frame of a fresh context then faults the whole working
8//! set in again. Carving everything out of one allocation keeps the pages the
9//! context's own, on any allocator.
10//!
11//! The layout follows upstream: the match finder's tables and its input history
12//! are carved from the front, the per-block buffers from the back, and every
13//! region starts on an [`ALIGN`]-byte boundary. A frame lays the workspace out in
14//! two parts: the match finder opens it with the bytes it needs and carves them,
15//! then the context carves its per-block buffers from what the opening reserved
16//! for it.
17
18use alloc::alloc::{Layout, alloc, alloc_zeroed, dealloc, handle_alloc_error};
19use alloc::vec::Vec;
20use core::mem::{align_of, size_of};
21use core::ptr::NonNull;
22
23/// Alignment of every region, and of the workspace itself (upstream
24/// `ZSTD_CWKSP_ALIGNMENT_BYTES`).
25pub(crate) const ALIGN: usize = 64;
26
27/// Bytes a region of `count` values of `T` occupies in a workspace, rounded
28/// up to [`ALIGN`] so the next region starts aligned.
29///
30/// # Panics
31///
32/// Panics when the size overflows `usize`, as a `Vec` of that length would.
33pub(crate) const fn region_bytes<T>(count: usize) -> usize {
34    let Some(bytes) = count.checked_mul(size_of::<T>()) else {
35        panic!("workspace region size overflows usize");
36    };
37    let Some(padded) = bytes.checked_add(ALIGN - 1) else {
38        panic!("workspace region size overflows usize");
39    };
40    padded & !(ALIGN - 1)
41}
42
43/// How a frame's input will reach the match finder, as the context knows it
44/// before the match finder resets. This is what sizes the match finder's input
45/// history and the block buffers; a length carried here is exact, an advisory
46/// one comes with the source-size hint.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub(crate) enum IngestPlan {
49    /// The frame writes its blocks raw and keeps no history.
50    Raw,
51    /// One contiguous slice of this many bytes, handed over whole: the match
52    /// finder may scan it in place and keep no copy of it.
53    Slice(usize),
54    /// A stream, read block by block into the history.
55    Stream,
56    /// A stream of this many pledged bytes, read like [`Self::Stream`]. The
57    /// context refuses input of any other length, so the size is exact.
58    PledgedStream(usize),
59}
60
61impl IngestPlan {
62    /// The frame's length when the plan knows it exactly.
63    pub(crate) fn exact_len(self) -> Option<usize> {
64        match self {
65            Self::Slice(len) | Self::PledgedStream(len) => Some(len),
66            Self::Raw | Self::Stream => None,
67        }
68    }
69}
70
71/// The single allocation a compression context carves its match-finder tables,
72/// input history and per-block buffers from.
73///
74/// It grows when a frame needs more than it holds and is kept otherwise, so a
75/// context allocates once for as long as its parameters do not grow. A region
76/// laid out at the same place in the same allocation as on the previous frame
77/// keeps its contents, which is what lets a match finder carry a table, or a
78/// dictionary at the head of its history, across frames.
79///
80/// `pub` only so it can appear in [`Matcher::reset_in_workspace`]; the module
81/// is private, so no code outside this crate can name, build or receive one.
82///
83/// [`Matcher::reset_in_workspace`]: crate::encoding::Matcher::reset_in_workspace
84pub struct Workspace {
85    /// Start of the usable bytes, aligned to [`ALIGN`] inside the allocation
86    /// that starts at `base`.
87    ptr: NonNull<u8>,
88    base: NonNull<u8>,
89    capacity: usize,
90    /// Counts allocations; a region is only the continuation of an earlier one
91    /// if both were carved from the same allocation.
92    generation: u64,
93    /// Carving cursors of the current layout: tables from the front up, the
94    /// history from the end of the leading part down, buffers from the back
95    /// down.
96    front: usize,
97    history_front: usize,
98    leading: usize,
99    back: usize,
100    /// The allocation a growth replaced (its base and usable bytes), kept
101    /// until the history has been bound again so its bytes can be carried
102    /// over.
103    retired: Option<(NonNull<u8>, usize)>,
104    /// The block ceiling the context set before the window was known, and the
105    /// bytes it carves after the match finder for a given block size.
106    block_target: usize,
107    trailing_for: fn(usize) -> usize,
108    /// How the frame's input reaches the match finder, set with the layout.
109    ingest: IngestPlan,
110    /// The block size the open layout's trailing part was sized for.
111    block_capacity: usize,
112    open: bool,
113    /// Consecutive layouts that found the allocation far larger than they
114    /// need, for giving back one that has stayed so.
115    oversized_layouts: u32,
116    /// Bytes of zero-start tables in this layout when they are larger than the
117    /// input expected to write them, else 0 (see [`Self::open_for_match_finder`]).
118    sparse_table_bytes: usize,
119    /// The open layout made the allocation, zeroed, so its regions hold
120    /// zeros until their holders write them.
121    zeroed: bool,
122    /// See [`Self::on_fresh_pages`].
123    fresh_pages: bool,
124}
125
126/// Upstream `ZSTD_WORKSPACETOOLARGE_FACTOR` / `_MAXDURATION`
127/// (`zstd_internal.h:258-265`): a workspace left with at least three times a
128/// frame's need unused for more than this many frames in a row is allocated
129/// again at the need (`ZSTD_resetCCtx_internal`, `zstd_compress.c:2154-2159`).
130const TOO_LARGE_FACTOR: usize = 3;
131const TOO_LARGE_MAX_LAYOUTS: u32 = 128;
132
133/// Size from which the target's system allocator serves every request with
134/// pages fresh from the kernel, so a zeroed allocation costs no memset and
135/// faults in only what is touched. Below it memory is recycled and zeroing
136/// it is a memset of all of it.
137///
138/// musl's mallocng maps anything above `MMAP_THRESHOLD` (131052 bytes,
139/// `src/malloc/mallocng/meta.h`) on its own. glibc raises its mmap
140/// threshold toward whatever it last freed, so a context rebuilt per frame
141/// lands on recycled heap, but never past `DEFAULT_MMAP_THRESHOLD_MAX`
142/// (`malloc/malloc.c`): 512 KiB on 32-bit targets, 32 MiB on 64-bit ones.
143/// Other allocators take the glibc 64-bit ceiling, the one that assumes least.
144#[cfg(target_env = "musl")]
145const FRESH_PAGES_FROM: usize = 128 * 1024;
146#[cfg(all(not(target_env = "musl"), target_pointer_width = "32"))]
147const FRESH_PAGES_FROM: usize = 512 * 1024;
148#[cfg(all(not(target_env = "musl"), not(target_pointer_width = "32")))]
149const FRESH_PAGES_FROM: usize = 32 * 1024 * 1024;
150
151/// Whether the workspace allocates from the target's system allocator, the only
152/// kind [`FRESH_PAGES_FROM`] describes. A `no_std` build always runs on one the
153/// application supplies, often a fixed heap that clears recycled memory to
154/// answer a zeroed request, so it takes a zeroed allocation only where the
155/// zero tables alone justify it. With `std` an application may still install
156/// its own `#[global_allocator]`, which nothing here can detect; the assumption
157/// then costs one memset of the bytes a frame leaves untouched when the
158/// workspace grows, never a wrong result.
159const SYSTEM_ALLOCATOR: bool = cfg!(feature = "std");
160
161// SAFETY: the workspace owns its allocation outright; the raw pointer is what
162// makes the type `!Send`/`!Sync` by default, not any shared state.
163unsafe impl Send for Workspace {}
164// SAFETY: as above; `&Workspace` exposes no access to the bytes.
165unsafe impl Sync for Workspace {}
166
167impl Workspace {
168    /// An empty workspace. Allocates nothing until a layout opens it.
169    pub(crate) const fn new() -> Self {
170        Self {
171            ptr: NonNull::<Aligned>::dangling().cast(),
172            base: NonNull::dangling(),
173            capacity: 0,
174            generation: 0,
175            front: 0,
176            history_front: 0,
177            leading: 0,
178            back: 0,
179            retired: None,
180            block_target: 0,
181            trailing_for: no_trailing,
182            ingest: IngestPlan::Stream,
183            block_capacity: 0,
184            open: false,
185            oversized_layouts: 0,
186            sparse_table_bytes: 0,
187            zeroed: false,
188            fresh_pages: false,
189        }
190    }
191
192    /// Bytes the workspace holds, what the layouts carve from.
193    #[cfg(test)]
194    pub(crate) fn capacity(&self) -> usize {
195        self.capacity
196    }
197
198    /// Whether the open layout sits on pages the allocator mapped fresh for
199    /// it: a new allocation from [`FRESH_PAGES_FROM`] up. The first touch of
200    /// each of its pages is a fault, so work that sweeps a table costs more
201    /// than on a workspace kept from an earlier frame.
202    /// Settled when the layout opens, so the per-block gate reads one field.
203    pub(crate) fn on_fresh_pages(&self) -> bool {
204        self.fresh_pages
205    }
206
207    /// Bytes the workspace has allocated, for heap accounting: its capacity
208    /// with the padding that aligns its start, and an allocation a growth
209    /// retired that has not been freed yet.
210    pub(crate) fn heap_bytes(&self) -> usize {
211        let allocated = |capacity: usize| {
212            if capacity == 0 {
213                0
214            } else {
215                allocation_layout(capacity).size()
216            }
217        };
218        allocated(self.capacity) + self.retired.map_or(0, |(_, capacity)| allocated(capacity))
219    }
220
221    /// Starts a frame's layout. The context will carve `trailing_for(block)`
222    /// bytes after the match finder, where `block` is `block_target` capped by
223    /// the frame's window, and its input reaches the match finder as `ingest`
224    /// says; nothing may be carved until [`Self::open`].
225    pub(crate) fn begin_layout(
226        &mut self,
227        block_target: usize,
228        trailing_for: fn(usize) -> usize,
229        ingest: IngestPlan,
230    ) {
231        self.release_retired();
232        self.block_target = block_target;
233        self.trailing_for = trailing_for;
234        self.ingest = ingest;
235        self.sparse_table_bytes = 0;
236        self.open = false;
237    }
238
239    /// Opens the layout for a match finder, as [`Self::open`], whose tables
240    /// include `zero_tables` bytes of tables that start as zeros, for a frame
241    /// expected to bring `expected_input` bytes.
242    ///
243    /// Zero tables larger than that input keep most of their pages untouched,
244    /// so a new allocation that they make up most of is then taken zeroed and
245    /// those tables left as they are: only the pages the frame indexes are
246    /// ever faulted in. Otherwise a plain allocation that fills only the
247    /// tables costs less: the input writes dense tables anyway, and an
248    /// allocator zeroes a block it hands out again in full, the history and
249    /// buffers included, which for a small frame is several times its tables.
250    pub(crate) fn open_for_match_finder(
251        &mut self,
252        leading: usize,
253        zero_tables: usize,
254        window: usize,
255        expected_input: usize,
256    ) {
257        self.sparse_table_bytes = if zero_tables > expected_input {
258            zero_tables
259        } else {
260            0
261        };
262        self.open(leading, window);
263    }
264
265    /// Whether this layout has been opened.
266    pub(crate) fn is_open(&self) -> bool {
267        self.open
268    }
269
270    /// How the frame being laid out takes its input.
271    pub(crate) fn ingest(&self) -> IngestPlan {
272        self.ingest
273    }
274
275    /// The largest block a frame with a `window`-byte window carries: the
276    /// ceiling [`Self::begin_layout`] set, capped by the window (upstream
277    /// `blockSize = MIN(maxBlockSize, windowSize)`) and by a frame length the
278    /// plan knows exactly, since no block holds more than the frame; at least
279    /// one byte.
280    pub(crate) fn block_for_window(&self, window: usize) -> usize {
281        self.block_target
282            .min(window)
283            .min(self.ingest.exact_len().unwrap_or(usize::MAX))
284            .max(1)
285    }
286
287    /// The block size the open layout reserved the trailing part for.
288    pub(crate) fn block_capacity(&self) -> usize {
289        self.block_capacity
290    }
291
292    /// Opens the layout with `leading` bytes for the caller's tables and
293    /// history ahead of the trailing part sized for a frame with a
294    /// `window`-byte window, allocating anew when the two together do not fit,
295    /// or when the allocation has stayed far larger than the frames need. A new
296    /// allocation discards every table carved before, so a holder whose table
297    /// is not a continuation must reinitialise it; a history carries its bytes
298    /// over (see [`HistoryBuf::bind`]).
299    ///
300    /// # Panics
301    ///
302    /// Panics when the layout is already open, or its size overflows `usize`.
303    pub(crate) fn open(&mut self, leading: usize, window: usize) {
304        assert!(!self.open, "a workspace layout opens once per frame");
305        self.block_capacity = self.block_for_window(window);
306        let total = leading
307            .checked_add((self.trailing_for)(self.block_capacity))
308            .expect("workspace size overflows usize");
309        let reallocate = if total > self.capacity {
310            true
311        } else {
312            // What is LEFT over, not the whole allocation, is held against
313            // three needs, as upstream holds its free space against them
314            // (`ZSTD_cwksp_check_too_large` -> `ZSTD_cwksp_check_available(ws,
315            // needed * ZSTD_WORKSPACETOOLARGE_FACTOR)`). A need so large that
316            // three of it overflow cannot be exceeded three times over by what
317            // is left, so it is never too large.
318            let too_large = total
319                .checked_mul(TOO_LARGE_FACTOR)
320                .is_some_and(|wasted| self.capacity - total >= wasted);
321            // Counted only while it stays too large, and reset by any layout
322            // it fits (upstream `ZSTD_cwksp_bump_oversized_duration`). Only
323            // "more than the limit" is ever asked of the count, so pinning it
324            // at the top of its range keeps the answer right on a context that
325            // outlives four billion frames.
326            self.oversized_layouts = if too_large {
327                self.oversized_layouts.saturating_add(1)
328            } else {
329                0
330            };
331            self.oversized_layouts > TOO_LARGE_MAX_LAYOUTS
332        };
333        // A zeroed allocation costs nothing on pages the kernel hands out
334        // fresh and a memset of all of it on memory the allocator recycles.
335        // At `FRESH_PAGES_FROM` and above the pages are fresh, so it is taken
336        // whatever the tables: filling them there faults in pages the frame
337        // never indexes, 36-42% of the encode at 1 MiB and level 3 on musl
338        // and i686, and 70% at 10 KiB on musl. Below it, where a context
339        // rebuilt per frame lands on recycled memory, it is taken only when
340        // the rest is at most a third of the sparse tables, which caps that
341        // case at a third over filling them. A small frame's tables sit
342        // beside buffers of their own size: at 10 KiB and level 1 they were
343        // 56% of the workspace, and zeroing it all measured 23% slower than
344        // filling them on glibc. A small input at a high level, where the
345        // tables are most of it, keeps its untouched pages (78% at level 13).
346        //
347        // Carving the zero tables from a zeroed allocation of their own instead
348        // hands the allocator that choice per table, and measured well on musl
349        // and i686. It loses the one allocation this type exists for: two
350        // allocations of similar size keep glibc's heap on its trim threshold
351        // (twice the largest chunk freed), so a context rebuilt per frame gave
352        // its pages back and faulted them in again on every frame, 3.9x slower
353        // at 1 MiB and level 3.
354        debug_assert!(
355            self.sparse_table_bytes <= leading,
356            "zero tables are part of the leading bytes"
357        );
358        let zero_all = (SYSTEM_ALLOCATOR && total >= FRESH_PAGES_FROM)
359            || (self.sparse_table_bytes > 0
360                && total - self.sparse_table_bytes <= self.sparse_table_bytes / 3);
361        if reallocate {
362            self.grow(total, zero_all);
363        }
364        self.zeroed = reallocate && zero_all;
365        self.fresh_pages = SYSTEM_ALLOCATOR && self.zeroed && self.capacity >= FRESH_PAGES_FROM;
366        self.front = 0;
367        self.leading = leading;
368        self.history_front = leading;
369        self.back = self.capacity;
370        self.open = true;
371    }
372
373    /// A table of `count` values that continues `previous` when it lands on the
374    /// same bytes of the same allocation, returned with `true`; otherwise every
375    /// value is set to `empty` and it is returned with `false`.
376    ///
377    /// A continuation holds only what its holder wrote: the holder filled the
378    /// region when it was first carved and is the only one to have written it
379    /// since, because every holder is laid out anew with every layout and no
380    /// two regions of one layout share a byte.
381    ///
382    /// # Panics
383    ///
384    /// Panics when the layout is not open or was sized below what it carves.
385    pub(crate) fn table_after<T: TableValue>(
386        &mut self,
387        count: usize,
388        previous: &Region<T>,
389        empty: T,
390    ) -> (Region<T>, bool) {
391        let offset = self.carve_front(region_bytes::<T>(count));
392        let mut region = self.region_at::<T>(offset, count);
393        let kept = count > 0
394            && previous.generation == region.generation
395            && previous.ptr == region.ptr
396            && previous.len == count;
397        // An allocation this layout made is zeroed and no region of it has
398        // been written yet, so a table of zeros is already in place. Leaving
399        // it unwritten keeps its pages demand-zero: a large table a small
400        // frame barely touches faults in only the pages it indexes.
401        if !kept && !(self.zeroed && empty.is_zero()) {
402            region.fill(empty);
403        }
404        (region, kept)
405    }
406
407    /// A table of `count` values, every one set to `value`. Holders go
408    /// through [`Table::bind`], which also reports a continuation; this is the
409    /// bare carve the layout tests check.
410    ///
411    /// # Panics
412    ///
413    /// As [`Self::table_after`].
414    #[cfg(test)]
415    pub(crate) fn table<T: TableValue>(&mut self, count: usize, value: T) -> Region<T> {
416        self.table_after(count, &Region::empty(), value).0
417    }
418
419    /// An empty buffer with room for `capacity` values, filled by pushing.
420    ///
421    /// # Panics
422    ///
423    /// Panics when the layout is not open or was sized below what it carves.
424    pub(crate) fn buffer<T: Copy>(&mut self, capacity: usize) -> RegionVec<T> {
425        assert!(
426            self.open,
427            "carving from a workspace layout that is not open"
428        );
429        let bytes = region_bytes::<T>(capacity);
430        assert!(
431            bytes <= self.back - self.leading,
432            "workspace sized below its layout"
433        );
434        self.back -= bytes;
435        RegionVec {
436            region: self.region_at::<T>(self.back, capacity),
437            len: 0,
438        }
439    }
440
441    /// Room for `capacity` history bytes at the end of the leading part, after
442    /// the tables, left unwritten: a [`HistoryBuf`] only ever reads what it
443    /// wrote. Its place depends only on how much of the leading part the
444    /// tables take, so a history can be bound before the tables are.
445    fn history_room(&mut self, capacity: usize) -> Region<u8> {
446        assert!(
447            self.open,
448            "carving from a workspace layout that is not open"
449        );
450        let bytes = region_bytes::<u8>(capacity);
451        assert!(
452            bytes <= self.history_front - self.front,
453            "workspace sized below its layout"
454        );
455        self.history_front -= bytes;
456        self.region_at::<u8>(self.history_front, capacity)
457    }
458
459    /// Advances the front cursor past `bytes`, returning where they start.
460    fn carve_front(&mut self, bytes: usize) -> usize {
461        assert!(
462            self.open,
463            "carving from a workspace layout that is not open"
464        );
465        assert!(
466            bytes <= self.history_front - self.front,
467            "workspace sized below its layout"
468        );
469        let offset = self.front;
470        self.front += bytes;
471        offset
472    }
473
474    fn region_at<T>(&self, offset: usize, count: usize) -> Region<T> {
475        const {
476            assert!(
477                align_of::<T>() <= ALIGN,
478                "a region is aligned to ALIGN at most"
479            );
480        }
481        // SAFETY: the callers checked `offset + region_bytes::<T>(count)` against
482        // the carving cursors, which never leave `0..=capacity`.
483        let ptr = unsafe { self.ptr.as_ptr().add(offset) };
484        Region {
485            // SAFETY: an offset into a non-null allocation is non-null.
486            ptr: unsafe { NonNull::new_unchecked(ptr.cast::<T>()) },
487            len: count,
488            generation: self.generation,
489        }
490    }
491
492    /// Replaces the allocation with one of `bytes`, discarding every table:
493    /// the generation moves on, so no later region can pass for a continuation
494    /// of one carved before. The old allocation is retired rather than freed,
495    /// until the history has carried its bytes out of it.
496    fn grow(&mut self, bytes: usize, zeroed: bool) {
497        self.release_retired();
498        if self.capacity != 0 {
499            self.retired = Some((self.base, self.capacity));
500            self.base = NonNull::dangling();
501            self.ptr = NonNull::<Aligned>::dangling().cast();
502            self.capacity = 0;
503        }
504        self.generation += 1;
505        self.oversized_layouts = 0;
506        if bytes == 0 {
507            return;
508        }
509        let layout = allocation_layout(bytes);
510        // Zeroed for sparse tables (see `open`), and at byte alignment so the
511        // allocator can take it as a `calloc`: a large one comes back as fresh
512        // pages the kernel zeroes on first touch. An over-aligned zeroed
513        // request is a plain allocation followed by a memset of all of it,
514        // which faults in every page up front, so the start is aligned by hand
515        // instead.
516        // SAFETY: `layout` has a non-zero size.
517        let raw = unsafe {
518            if zeroed {
519                alloc_zeroed(layout)
520            } else {
521                alloc(layout)
522            }
523        };
524        let Some(base) = NonNull::new(raw) else {
525            handle_alloc_error(layout);
526        };
527        let offset = raw.align_offset(ALIGN);
528        debug_assert!(offset < ALIGN, "a byte pointer aligns within ALIGN bytes");
529        self.base = base;
530        // SAFETY: `offset < ALIGN`, and the allocation holds `bytes + ALIGN - 1`
531        // bytes, so the aligned start leaves `bytes` of them after it.
532        self.ptr = unsafe { NonNull::new_unchecked(raw.add(offset)) };
533        self.capacity = bytes;
534    }
535
536    /// Frees the allocation a growth retired. Every holder has been laid out
537    /// in the new one by the time this runs.
538    pub(crate) fn release_retired(&mut self) {
539        if let Some((base, capacity)) = self.retired.take() {
540            // SAFETY: `grow` retired this allocation and `take` dropped the
541            // only record of it.
542            unsafe { free(base, capacity) };
543        }
544    }
545
546    fn release(&mut self) {
547        self.release_retired();
548        if self.capacity == 0 {
549            return;
550        }
551        // SAFETY: the current allocation; `capacity` is reset below so it is
552        // never freed twice.
553        unsafe { free(self.base, self.capacity) };
554        self.base = NonNull::dangling();
555        self.ptr = NonNull::<Aligned>::dangling().cast();
556        self.capacity = 0;
557    }
558}
559
560/// The allocation behind a workspace of `capacity` usable bytes: byte-aligned,
561/// with room to align the start to [`ALIGN`] by hand.
562fn allocation_layout(capacity: usize) -> Layout {
563    let bytes = capacity
564        .checked_add(ALIGN - 1)
565        .expect("workspace size overflows usize");
566    Layout::from_size_align(bytes, 1).expect("workspace size overflows isize")
567}
568
569/// Frees a workspace allocation of `capacity` usable bytes.
570///
571/// # Safety
572///
573/// `base` must be the start of an allocation `grow` made for `capacity`
574/// bytes, not yet freed, and the caller must drop its handle to it.
575unsafe fn free(base: NonNull<u8>, capacity: usize) {
576    // SAFETY: the caller's contract: this layout, allocated and live.
577    unsafe { dealloc(base.as_ptr(), allocation_layout(capacity)) };
578}
579
580impl Default for Workspace {
581    fn default() -> Self {
582        Self::new()
583    }
584}
585
586impl Drop for Workspace {
587    fn drop(&mut self) {
588        self.release();
589    }
590}
591
592/// The trailing part of a layout with no block buffers behind the tables: a
593/// match finder laying out a workspace of its own, or one nobody began.
594pub(crate) fn no_trailing(_block: usize) -> usize {
595    0
596}
597
598/// A type whose alignment is [`ALIGN`], for the dangling pointer of an empty
599/// workspace.
600#[repr(align(64))]
601struct Aligned;
602
603const _: () = assert!(align_of::<Aligned>() == ALIGN);
604
605/// A value a workspace table holds: a plain integer, whose zero is all-zero
606/// bytes, so a zeroed allocation already holds a table of zeros.
607pub(crate) trait TableValue: Copy {
608    fn is_zero(self) -> bool;
609}
610
611macro_rules! table_value {
612    ($($int:ty),*) => {$(
613        impl TableValue for $int {
614            fn is_zero(self) -> bool {
615                self == 0
616            }
617        }
618    )*};
619}
620
621table_value!(u8, u32, u64);
622
623/// A fixed-length run of values in a workspace.
624///
625/// Holders keep it for as long as the layout it was carved in: every holder is
626/// bound again whenever the workspace is laid out anew, and the workspace
627/// outlives every holder because both live in the same context.
628pub(crate) struct Region<T> {
629    ptr: NonNull<T>,
630    len: usize,
631    /// The allocation it was carved from; `0` for no allocation.
632    generation: u64,
633}
634
635// SAFETY: a region is the sole handle to its values; a layout never hands the
636// same bytes to two holders.
637unsafe impl<T: Send> Send for Region<T> {}
638// SAFETY: `&Region` only reads.
639unsafe impl<T: Sync> Sync for Region<T> {}
640
641impl<T> Region<T> {
642    /// A region of no values, the state of a holder before its first bind.
643    pub(crate) const fn empty() -> Self {
644        Self {
645            ptr: NonNull::dangling(),
646            len: 0,
647            generation: 0,
648        }
649    }
650
651    #[cfg(test)]
652    pub(crate) fn len(&self) -> usize {
653        self.len
654    }
655
656    pub(crate) fn as_mut_ptr(&mut self) -> *mut T {
657        self.ptr.as_ptr()
658    }
659
660    pub(crate) fn as_ptr(&self) -> *const T {
661        self.ptr.as_ptr()
662    }
663}
664
665impl<T: Copy> Region<T> {
666    pub(crate) fn as_slice(&self) -> &[T] {
667        // SAFETY: the region is live (see the type's contract) and every value
668        // was written when it was carved or by its holder since; `&self`
669        // excludes a concurrent `&mut`.
670        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
671    }
672
673    pub(crate) fn as_mut_slice(&mut self) -> &mut [T] {
674        // SAFETY: as `as_slice`, with `&mut self` making the access exclusive.
675        unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
676    }
677
678    /// Sets every value, including ones never written, so the region is
679    /// initialised before any slice of it exists.
680    fn fill(&mut self, value: T) {
681        let base = self.ptr.as_ptr();
682        for i in 0..self.len {
683            // SAFETY: `i < len`, inside the region; `write` never reads the
684            // value it replaces.
685            unsafe { base.add(i).write(value) };
686        }
687    }
688}
689
690/// A match-finder table: a region of the context's workspace, or an allocation
691/// of its own for a copy that must outlive a layout (a dictionary snapshot, a
692/// table a caller built by hand).
693///
694/// Reads and writes go through one pointer either way, so the hot loops never
695/// branch on where the values live.
696pub(crate) struct Table<T: Copy> {
697    view: Region<T>,
698    /// Backs `view` when the table owns its values; empty for a workspace table.
699    own: Vec<T>,
700}
701
702impl<T: Copy> Table<T> {
703    /// A table with no values.
704    pub(crate) const fn empty() -> Self {
705        Self {
706            view: Region::empty(),
707            own: Vec::new(),
708        }
709    }
710
711    /// A table owning `values`.
712    pub(crate) fn owned(mut values: Vec<T>) -> Self {
713        let view = Region {
714            // SAFETY: a `Vec`'s buffer pointer is never null.
715            ptr: unsafe { NonNull::new_unchecked(values.as_mut_ptr()) },
716            len: values.len(),
717            generation: 0,
718        };
719        Self { view, own: values }
720    }
721
722    /// Lays the table out in `workspace` at `count` values. Returns `true` when
723    /// it continues the region it had, contents intact; otherwise every value
724    /// is `empty`.
725    pub(crate) fn bind(&mut self, workspace: &mut Workspace, count: usize, empty: T) -> bool
726    where
727        T: TableValue,
728    {
729        let (view, kept) = workspace.table_after(count, &self.view, empty);
730        self.view = view;
731        self.own = Vec::new();
732        kept
733    }
734
735    pub(crate) fn len(&self) -> usize {
736        self.view.len
737    }
738
739    pub(crate) fn is_empty(&self) -> bool {
740        self.view.len == 0
741    }
742
743    pub(crate) fn as_slice(&self) -> &[T] {
744        self.view.as_slice()
745    }
746
747    pub(crate) fn as_mut_slice(&mut self) -> &mut [T] {
748        self.view.as_mut_slice()
749    }
750
751    /// Heap bytes the table owns; a workspace table owns none.
752    pub(crate) fn owned_bytes(&self) -> usize {
753        self.own.capacity() * size_of::<T>()
754    }
755
756    /// Moves the values into an allocation of their own, for a matcher leaving
757    /// the context whose workspace holds them.
758    pub(crate) fn leave_workspace(&mut self) {
759        if self.own.is_empty() && !self.is_empty() {
760            *self = self.clone();
761        }
762    }
763}
764
765impl<T: Copy> Clone for Table<T> {
766    /// A copy owning its values, whatever the source's storage.
767    fn clone(&self) -> Self {
768        Self::owned(self.as_slice().to_vec())
769    }
770
771    /// Copies the values in place when the lengths match, so restoring a
772    /// snapshot into a workspace table keeps it in the workspace.
773    fn clone_from(&mut self, source: &Self) {
774        if self.len() == source.len() {
775            self.as_mut_slice().copy_from_slice(source.as_slice());
776        } else {
777            *self = source.clone();
778        }
779    }
780}
781
782impl<T: Copy> Default for Table<T> {
783    fn default() -> Self {
784        Self::empty()
785    }
786}
787
788impl<T: Copy> core::ops::Deref for Table<T> {
789    type Target = [T];
790
791    fn deref(&self) -> &[T] {
792        self.as_slice()
793    }
794}
795
796impl<T: Copy> core::ops::DerefMut for Table<T> {
797    fn deref_mut(&mut self) -> &mut [T] {
798        self.as_mut_slice()
799    }
800}
801
802/// A match finder's input history: bytes appended block by block, trimmed at
803/// the front as the window slides, used like a `Vec<u8>`.
804///
805/// The encoder reads input straight into it through
806/// [`Matcher::fill_in_place`], so a [`Matcher`] keeps its window in one. A
807/// matcher defined outside this crate owns its buffer outright.
808///
809/// The crate's own match finders keep theirs in the compression context's
810/// single allocation at the capacity the frame was laid out for; one moves
811/// into an allocation of its own if a frame brings more than that (a size
812/// hint that undercounted). Only the first `len` bytes are ever read; the rest
813/// of the room is left unwritten. Its bytes survive every layout: `bind`
814/// carries them into the new room wherever it lands, since a matcher keeps its
815/// dictionary at the head of the history from one frame to the next.
816///
817/// # Examples
818/// ```
819/// use structured_zstd::encoding::HistoryBuf;
820///
821/// let mut history = HistoryBuf::new();
822/// history.extend_from_slice(b"older block, newer block");
823/// history.drain_front(b"older block, ".len());
824/// assert_eq!(&history[..], b"newer block");
825/// ```
826///
827/// [`Matcher::fill_in_place`]: crate::encoding::Matcher::fill_in_place
828/// [`Matcher`]: crate::encoding::Matcher
829pub struct HistoryBuf {
830    ptr: NonNull<u8>,
831    capacity: usize,
832    len: usize,
833    /// Backs the room when the buffer owns it: its length stays `0` and its
834    /// capacity is the room, which this type manages.
835    own: Vec<u8>,
836}
837
838// SAFETY: the buffer is the sole handle to its room, whether carved from the
839// context's workspace or owned.
840unsafe impl Send for HistoryBuf {}
841// SAFETY: `&HistoryBuf` only reads.
842unsafe impl Sync for HistoryBuf {}
843
844impl HistoryBuf {
845    /// An empty buffer with no room.
846    pub const fn new() -> Self {
847        Self {
848            ptr: NonNull::dangling(),
849            capacity: 0,
850            len: 0,
851            own: Vec::new(),
852        }
853    }
854
855    /// Workspace bytes [`Self::bind`] carves for a frame that needs `capacity`
856    /// bytes: never less than the buffer already holds.
857    pub(crate) fn workspace_bytes(&self, capacity: usize) -> usize {
858        region_bytes::<u8>(capacity.max(self.len))
859    }
860
861    /// Lays the room out in `workspace` after the tables, at `capacity` bytes
862    /// or the length already held if that is more, carrying the bytes over.
863    ///
864    /// Binds before the tables of the same layout, so nothing has written the
865    /// workspace since the previous layout: the old room is intact whether it
866    /// is where it was, elsewhere in the same allocation, in the allocation the
867    /// workspace retired, or owned.
868    pub(crate) fn bind(&mut self, workspace: &mut Workspace, capacity: usize) {
869        let capacity = capacity.max(self.len);
870        let room = workspace.history_room(capacity);
871        if self.ptr != room.ptr {
872            // SAFETY: the old room is live and holds `len` written bytes (see
873            // above), and the new one has room for them; `copy` allows the two
874            // to overlap within one allocation.
875            unsafe { core::ptr::copy(self.ptr.as_ptr(), room.ptr.as_ptr(), self.len) };
876        }
877        self.ptr = room.ptr;
878        self.capacity = capacity;
879        self.own = Vec::new();
880        workspace.release_retired();
881    }
882
883    /// Moves the bytes into a room of their own, for a matcher leaving the
884    /// context whose workspace holds them.
885    pub(crate) fn leave_workspace(&mut self) {
886        if self.own.capacity() == 0 && self.capacity != 0 {
887            self.move_room(self.len);
888        }
889    }
890
891    /// Bytes held.
892    pub fn len(&self) -> usize {
893        self.len
894    }
895
896    /// Whether no bytes are held.
897    pub fn is_empty(&self) -> bool {
898        self.len == 0
899    }
900
901    /// Bytes the buffer holds room for without moving.
902    pub fn capacity(&self) -> usize {
903        self.capacity
904    }
905
906    /// Heap bytes the buffer owns; room in a context's workspace is counted
907    /// with the workspace.
908    pub(crate) fn owned_bytes(&self) -> usize {
909        self.own.capacity()
910    }
911
912    /// Drops every byte, keeping the room.
913    pub fn clear(&mut self) {
914        self.len = 0;
915    }
916
917    /// Keeps the first `len` bytes; a no-op when fewer are held.
918    pub fn truncate(&mut self, len: usize) {
919        self.len = self.len.min(len);
920    }
921
922    /// Makes room for `additional` more bytes, at least doubling the room when
923    /// it has to move, as a `Vec` does.
924    ///
925    /// # Panics
926    ///
927    /// Panics when the length would overflow `usize`.
928    pub fn reserve(&mut self, additional: usize) {
929        let needed = self
930            .len
931            .checked_add(additional)
932            .expect("history length overflows usize");
933        if needed > self.capacity {
934            // A room too large to double is outgrown by exactly what is needed.
935            let doubled = self.capacity.checked_mul(2).unwrap_or(needed);
936            self.move_room(needed.max(doubled));
937        }
938    }
939
940    /// Makes room for exactly `additional` more bytes when it has to move.
941    pub(crate) fn reserve_exact(&mut self, additional: usize) {
942        let needed = self
943            .len
944            .checked_add(additional)
945            .expect("history length overflows usize");
946        if needed > self.capacity {
947            self.move_room(needed);
948        }
949    }
950
951    /// Appends `bytes`.
952    pub fn extend_from_slice(&mut self, bytes: &[u8]) {
953        self.reserve(bytes.len());
954        // SAFETY: `reserve` left room for `bytes.len()` more bytes past `len`,
955        // and `bytes` is borrowed from elsewhere, never from this room.
956        unsafe {
957            core::ptr::copy_nonoverlapping(
958                bytes.as_ptr(),
959                self.ptr.as_ptr().add(self.len),
960                bytes.len(),
961            );
962        }
963        self.len += bytes.len();
964    }
965
966    pub(crate) fn push(&mut self, byte: u8) {
967        self.reserve(1);
968        // SAFETY: `reserve` left room for one more byte past `len`.
969        unsafe { self.ptr.as_ptr().add(self.len).write(byte) };
970        self.len += 1;
971    }
972
973    /// Sets the length to `len`, writing `value` into any bytes it adds.
974    pub(crate) fn resize(&mut self, len: usize, value: u8) {
975        if len > self.len {
976            self.reserve(len - self.len);
977            // SAFETY: `reserve` left room up to `len`.
978            unsafe {
979                core::ptr::write_bytes(self.ptr.as_ptr().add(self.len), value, len - self.len);
980            }
981        }
982        self.len = len;
983    }
984
985    /// Drops the first `count` bytes, moving the rest to the front.
986    ///
987    /// # Panics
988    ///
989    /// Panics when `count` exceeds the length.
990    pub fn drain_front(&mut self, count: usize) {
991        assert!(count <= self.len, "draining past the history's end");
992        // SAFETY: both ranges lie within the written `len` bytes; `copy`
993        // handles the overlap.
994        unsafe {
995            core::ptr::copy(
996                self.ptr.as_ptr().add(count),
997                self.ptr.as_ptr(),
998                self.len - count,
999            );
1000        }
1001        self.len -= count;
1002    }
1003
1004    /// Moves the written bytes into an owned room of `capacity` bytes.
1005    fn move_room(&mut self, capacity: usize) {
1006        let mut own: Vec<u8> = Vec::with_capacity(capacity);
1007        // SAFETY: the new room has space for `len` bytes, and the old room
1008        // holds them; the two allocations are distinct.
1009        unsafe { core::ptr::copy_nonoverlapping(self.ptr.as_ptr(), own.as_mut_ptr(), self.len) };
1010        // SAFETY: a `Vec`'s buffer pointer is never null.
1011        self.ptr = unsafe { NonNull::new_unchecked(own.as_mut_ptr()) };
1012        self.capacity = own.capacity();
1013        self.own = own;
1014    }
1015}
1016
1017impl Default for HistoryBuf {
1018    fn default() -> Self {
1019        Self::new()
1020    }
1021}
1022
1023impl Clone for HistoryBuf {
1024    /// A copy owning its bytes, whatever the source's storage.
1025    fn clone(&self) -> Self {
1026        let mut copy = Self::new();
1027        copy.extend_from_slice(self);
1028        copy
1029    }
1030
1031    /// Copies the bytes into the room this buffer already has when they fit,
1032    /// so restoring a snapshot keeps the history where it was laid out.
1033    fn clone_from(&mut self, source: &Self) {
1034        self.clear();
1035        self.extend_from_slice(source);
1036    }
1037}
1038
1039/// A buffer owning `bytes`, for tests that set a history up by hand.
1040#[cfg(test)]
1041impl From<Vec<u8>> for HistoryBuf {
1042    fn from(mut bytes: Vec<u8>) -> Self {
1043        let len = bytes.len();
1044        // The owned room keeps length 0 (see the field); the bytes stay
1045        // written, and this buffer's `len` claims them.
1046        // SAFETY: shrinking a `Vec`'s length to 0 needs no drop for `u8`.
1047        unsafe { bytes.set_len(0) };
1048        Self {
1049            // SAFETY: a `Vec`'s buffer pointer is never null.
1050            ptr: unsafe { NonNull::new_unchecked(bytes.as_mut_ptr()) },
1051            capacity: bytes.capacity(),
1052            len,
1053            own: bytes,
1054        }
1055    }
1056}
1057
1058impl core::ops::Deref for HistoryBuf {
1059    type Target = [u8];
1060
1061    fn deref(&self) -> &[u8] {
1062        // SAFETY: the first `len` bytes were written by this buffer, and its
1063        // room is live.
1064        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
1065    }
1066}
1067
1068impl core::ops::DerefMut for HistoryBuf {
1069    fn deref_mut(&mut self) -> &mut [u8] {
1070        // SAFETY: as `deref`, with `&mut self` making the access exclusive.
1071        unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
1072    }
1073}
1074
1075impl core::fmt::Debug for HistoryBuf {
1076    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1077        f.debug_struct("HistoryBuf")
1078            .field("len", &self.len)
1079            .field("capacity", &self.capacity)
1080            .finish()
1081    }
1082}
1083
1084/// What a match finder settles about its history before the next frame is laid
1085/// out: the floor past every position the previous frame indexed, which is
1086/// read off the history's length, and the resident dictionary prefix the reset
1087/// may keep. Taking both first lets the history drop everything else before
1088/// the layout sizes its room and carries its bytes into it.
1089#[derive(Clone, Copy, Debug)]
1090pub(crate) struct RetiredHistory {
1091    pub(crate) next_floor: usize,
1092    pub(crate) kept: Option<usize>,
1093}
1094
1095/// A byte buffer a block is read into: the match finder's history, or the
1096/// output of an uncompressed frame, which carries its blocks raw.
1097pub(crate) trait IngestBuffer: core::ops::DerefMut<Target = [u8]> {
1098    fn extend_from_slice(&mut self, bytes: &[u8]);
1099    fn resize(&mut self, len: usize, value: u8);
1100    fn truncate(&mut self, len: usize);
1101    fn push(&mut self, byte: u8);
1102}
1103
1104impl IngestBuffer for Vec<u8> {
1105    fn extend_from_slice(&mut self, bytes: &[u8]) {
1106        Vec::extend_from_slice(self, bytes);
1107    }
1108
1109    fn resize(&mut self, len: usize, value: u8) {
1110        Vec::resize(self, len, value);
1111    }
1112
1113    fn truncate(&mut self, len: usize) {
1114        Vec::truncate(self, len);
1115    }
1116
1117    fn push(&mut self, byte: u8) {
1118        Vec::push(self, byte);
1119    }
1120}
1121
1122impl IngestBuffer for HistoryBuf {
1123    fn extend_from_slice(&mut self, bytes: &[u8]) {
1124        HistoryBuf::extend_from_slice(self, bytes);
1125    }
1126
1127    fn resize(&mut self, len: usize, value: u8) {
1128        HistoryBuf::resize(self, len, value);
1129    }
1130
1131    fn truncate(&mut self, len: usize) {
1132        HistoryBuf::truncate(self, len);
1133    }
1134
1135    fn push(&mut self, byte: u8) {
1136        HistoryBuf::push(self, byte);
1137    }
1138}
1139
1140/// A buffer in a workspace with a fixed capacity and a length, used like a
1141/// `Vec` that never reallocates.
1142pub(crate) struct RegionVec<T> {
1143    region: Region<T>,
1144    len: usize,
1145}
1146
1147impl<T: Copy> RegionVec<T> {
1148    /// A buffer with no room, the state of a holder before its first bind.
1149    pub(crate) const fn empty() -> Self {
1150        Self {
1151            region: Region::empty(),
1152            len: 0,
1153        }
1154    }
1155
1156    pub(crate) fn capacity(&self) -> usize {
1157        self.region.len
1158    }
1159
1160    pub(crate) fn clear(&mut self) {
1161        self.len = 0;
1162    }
1163
1164    /// # Panics
1165    ///
1166    /// Panics when the buffer is full: its capacity was sized from the most
1167    /// the frame can put in it, so a full buffer is a sizing bug.
1168    #[inline]
1169    pub(crate) fn push(&mut self, value: T) {
1170        if self.len == self.region.len {
1171            capacity_exhausted();
1172        }
1173        // SAFETY: `len < capacity`, so the slot is inside the region.
1174        unsafe { self.region.as_mut_ptr().add(self.len).write(value) };
1175        self.len += 1;
1176    }
1177
1178    /// # Panics
1179    ///
1180    /// Panics when `values` does not fit, as [`Self::push`].
1181    pub(crate) fn extend_from_slice(&mut self, values: &[T]) {
1182        if values.len() > self.region.len - self.len {
1183            capacity_exhausted();
1184        }
1185        // SAFETY: the destination range `len..len + values.len()` is inside the
1186        // region by the check above, and a workspace region never overlaps a
1187        // slice borrowed from elsewhere.
1188        unsafe {
1189            core::ptr::copy_nonoverlapping(
1190                values.as_ptr(),
1191                self.region.as_mut_ptr().add(self.len),
1192                values.len(),
1193            );
1194        }
1195        self.len += values.len();
1196    }
1197
1198    /// Pointer to the first value, for writes past the length that
1199    /// [`Self::set_len`] then claims.
1200    pub(crate) fn as_mut_ptr(&mut self) -> *mut T {
1201        self.region.as_mut_ptr()
1202    }
1203
1204    /// # Safety
1205    ///
1206    /// `len` must not exceed the capacity, and every value below it must have
1207    /// been written.
1208    pub(crate) unsafe fn set_len(&mut self, len: usize) {
1209        debug_assert!(len <= self.region.len);
1210        self.len = len;
1211    }
1212}
1213
1214impl<T: Copy> core::ops::Deref for RegionVec<T> {
1215    type Target = [T];
1216
1217    fn deref(&self) -> &[T] {
1218        // SAFETY: the first `len` values were written by `push`,
1219        // `extend_from_slice` or a caller of `set_len`, and the region is live.
1220        unsafe { core::slice::from_raw_parts(self.region.as_ptr(), self.len) }
1221    }
1222}
1223
1224impl<T: Copy> core::ops::DerefMut for RegionVec<T> {
1225    fn deref_mut(&mut self) -> &mut [T] {
1226        // SAFETY: as `deref`, with `&mut self` making the access exclusive.
1227        unsafe { core::slice::from_raw_parts_mut(self.region.as_mut_ptr(), self.len) }
1228    }
1229}
1230
1231impl<T: Copy> Default for RegionVec<T> {
1232    fn default() -> Self {
1233        Self::empty()
1234    }
1235}
1236
1237#[cold]
1238#[inline(never)]
1239fn capacity_exhausted() -> ! {
1240    panic!("workspace buffer sized below what the frame put in it")
1241}
1242
1243#[cfg(test)]
1244mod tests;