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    /// The first value's address, taken from the table's own pointer rather
752    /// than through a slice: it stays usable while slices of the table are
753    /// made and dropped around it, until the table is laid out again. The
754    /// slice method of the same name, reached through `Deref`, would derive it
755    /// from a temporary slice that the next one invalidates.
756    pub(crate) fn as_ptr(&self) -> *const T {
757        self.view.as_ptr()
758    }
759
760    /// Writable form of [`Self::as_ptr`], with the same lifetime.
761    pub(crate) fn as_mut_ptr(&mut self) -> *mut T {
762        self.view.as_mut_ptr()
763    }
764
765    /// Heap bytes the table owns; a workspace table owns none.
766    pub(crate) fn owned_bytes(&self) -> usize {
767        self.own.capacity() * size_of::<T>()
768    }
769
770    /// Moves the values into an allocation of their own, for a matcher leaving
771    /// the context whose workspace holds them.
772    pub(crate) fn leave_workspace(&mut self) {
773        if self.own.is_empty() && !self.is_empty() {
774            *self = self.clone();
775        }
776    }
777}
778
779impl<T: Copy> Clone for Table<T> {
780    /// A copy owning its values, whatever the source's storage.
781    fn clone(&self) -> Self {
782        Self::owned(self.as_slice().to_vec())
783    }
784
785    /// Copies the values in place when the lengths match, so restoring a
786    /// snapshot into a workspace table keeps it in the workspace.
787    fn clone_from(&mut self, source: &Self) {
788        if self.len() == source.len() {
789            self.as_mut_slice().copy_from_slice(source.as_slice());
790        } else {
791            *self = source.clone();
792        }
793    }
794}
795
796impl<T: Copy> Default for Table<T> {
797    fn default() -> Self {
798        Self::empty()
799    }
800}
801
802impl<T: Copy> core::ops::Deref for Table<T> {
803    type Target = [T];
804
805    fn deref(&self) -> &[T] {
806        self.as_slice()
807    }
808}
809
810impl<T: Copy> core::ops::DerefMut for Table<T> {
811    fn deref_mut(&mut self) -> &mut [T] {
812        self.as_mut_slice()
813    }
814}
815
816/// A match finder's input history: bytes appended block by block, trimmed at
817/// the front as the window slides, used like a `Vec<u8>`.
818///
819/// The encoder reads input straight into it through
820/// [`Matcher::fill_in_place`], so a [`Matcher`] keeps its window in one. A
821/// matcher defined outside this crate owns its buffer outright.
822///
823/// The crate's own match finders keep theirs in the compression context's
824/// single allocation at the capacity the frame was laid out for; one moves
825/// into an allocation of its own if a frame brings more than that (a size
826/// hint that undercounted). Only the first `len` bytes are ever read; the rest
827/// of the room is left unwritten. Its bytes survive every layout: `bind`
828/// carries them into the new room wherever it lands, since a matcher keeps its
829/// dictionary at the head of the history from one frame to the next.
830///
831/// # Examples
832/// ```
833/// use structured_zstd::encoding::HistoryBuf;
834///
835/// let mut history = HistoryBuf::new();
836/// history.extend_from_slice(b"older block, newer block");
837/// history.drain_front(b"older block, ".len());
838/// assert_eq!(&history[..], b"newer block");
839/// ```
840///
841/// [`Matcher::fill_in_place`]: crate::encoding::Matcher::fill_in_place
842/// [`Matcher`]: crate::encoding::Matcher
843pub struct HistoryBuf {
844    ptr: NonNull<u8>,
845    capacity: usize,
846    len: usize,
847    /// Backs the room when the buffer owns it: its length stays `0` and its
848    /// capacity is the room, which this type manages.
849    own: Vec<u8>,
850}
851
852// SAFETY: the buffer is the sole handle to its room, whether carved from the
853// context's workspace or owned.
854unsafe impl Send for HistoryBuf {}
855// SAFETY: `&HistoryBuf` only reads.
856unsafe impl Sync for HistoryBuf {}
857
858impl HistoryBuf {
859    /// An empty buffer with no room.
860    pub const fn new() -> Self {
861        Self {
862            ptr: NonNull::dangling(),
863            capacity: 0,
864            len: 0,
865            own: Vec::new(),
866        }
867    }
868
869    /// Workspace bytes [`Self::bind`] carves for a frame that needs `capacity`
870    /// bytes: never less than the buffer already holds.
871    pub(crate) fn workspace_bytes(&self, capacity: usize) -> usize {
872        region_bytes::<u8>(capacity.max(self.len))
873    }
874
875    /// Lays the room out in `workspace` after the tables, at `capacity` bytes
876    /// or the length already held if that is more, carrying the bytes over.
877    ///
878    /// Binds before the tables of the same layout, so nothing has written the
879    /// workspace since the previous layout: the old room is intact whether it
880    /// is where it was, elsewhere in the same allocation, in the allocation the
881    /// workspace retired, or owned.
882    pub(crate) fn bind(&mut self, workspace: &mut Workspace, capacity: usize) {
883        let capacity = capacity.max(self.len);
884        let room = workspace.history_room(capacity);
885        if self.ptr != room.ptr {
886            // SAFETY: the old room is live and holds `len` written bytes (see
887            // above), and the new one has room for them; `copy` allows the two
888            // to overlap within one allocation.
889            unsafe { core::ptr::copy(self.ptr.as_ptr(), room.ptr.as_ptr(), self.len) };
890        }
891        self.ptr = room.ptr;
892        self.capacity = capacity;
893        self.own = Vec::new();
894        workspace.release_retired();
895    }
896
897    /// Moves the bytes into a room of their own, for a matcher leaving the
898    /// context whose workspace holds them.
899    pub(crate) fn leave_workspace(&mut self) {
900        if self.own.capacity() == 0 && self.capacity != 0 {
901            self.move_room(self.len);
902        }
903    }
904
905    /// Bytes held.
906    pub fn len(&self) -> usize {
907        self.len
908    }
909
910    /// Whether no bytes are held.
911    pub fn is_empty(&self) -> bool {
912        self.len == 0
913    }
914
915    /// Bytes the buffer holds room for without moving.
916    pub fn capacity(&self) -> usize {
917        self.capacity
918    }
919
920    /// Heap bytes the buffer owns; room in a context's workspace is counted
921    /// with the workspace.
922    pub(crate) fn owned_bytes(&self) -> usize {
923        self.own.capacity()
924    }
925
926    /// Drops every byte, keeping the room.
927    pub fn clear(&mut self) {
928        self.len = 0;
929    }
930
931    /// Keeps the first `len` bytes; a no-op when fewer are held.
932    pub fn truncate(&mut self, len: usize) {
933        self.len = self.len.min(len);
934    }
935
936    /// Makes room for `additional` more bytes, at least doubling the room when
937    /// it has to move, as a `Vec` does.
938    ///
939    /// # Panics
940    ///
941    /// Panics when the length would overflow `usize`.
942    pub fn reserve(&mut self, additional: usize) {
943        let needed = self
944            .len
945            .checked_add(additional)
946            .expect("history length overflows usize");
947        if needed > self.capacity {
948            // A room too large to double is outgrown by exactly what is needed.
949            let doubled = self.capacity.checked_mul(2).unwrap_or(needed);
950            self.move_room(needed.max(doubled));
951        }
952    }
953
954    /// Makes room for exactly `additional` more bytes when it has to move.
955    pub(crate) fn reserve_exact(&mut self, additional: usize) {
956        let needed = self
957            .len
958            .checked_add(additional)
959            .expect("history length overflows usize");
960        if needed > self.capacity {
961            self.move_room(needed);
962        }
963    }
964
965    /// Appends `bytes`.
966    pub fn extend_from_slice(&mut self, bytes: &[u8]) {
967        self.reserve(bytes.len());
968        // SAFETY: `reserve` left room for `bytes.len()` more bytes past `len`,
969        // and `bytes` is borrowed from elsewhere, never from this room.
970        unsafe {
971            core::ptr::copy_nonoverlapping(
972                bytes.as_ptr(),
973                self.ptr.as_ptr().add(self.len),
974                bytes.len(),
975            );
976        }
977        self.len += bytes.len();
978    }
979
980    pub(crate) fn push(&mut self, byte: u8) {
981        self.reserve(1);
982        // SAFETY: `reserve` left room for one more byte past `len`.
983        unsafe { self.ptr.as_ptr().add(self.len).write(byte) };
984        self.len += 1;
985    }
986
987    /// Sets the length to `len`, writing `value` into any bytes it adds.
988    pub(crate) fn resize(&mut self, len: usize, value: u8) {
989        if len > self.len {
990            self.reserve(len - self.len);
991            // SAFETY: `reserve` left room up to `len`.
992            unsafe {
993                core::ptr::write_bytes(self.ptr.as_ptr().add(self.len), value, len - self.len);
994            }
995        }
996        self.len = len;
997    }
998
999    /// Drops the first `count` bytes, moving the rest to the front.
1000    ///
1001    /// # Panics
1002    ///
1003    /// Panics when `count` exceeds the length.
1004    pub fn drain_front(&mut self, count: usize) {
1005        assert!(count <= self.len, "draining past the history's end");
1006        // SAFETY: both ranges lie within the written `len` bytes; `copy`
1007        // handles the overlap.
1008        unsafe {
1009            core::ptr::copy(
1010                self.ptr.as_ptr().add(count),
1011                self.ptr.as_ptr(),
1012                self.len - count,
1013            );
1014        }
1015        self.len -= count;
1016    }
1017
1018    /// Moves the written bytes into an owned room of `capacity` bytes.
1019    fn move_room(&mut self, capacity: usize) {
1020        let mut own: Vec<u8> = Vec::with_capacity(capacity);
1021        // SAFETY: the new room has space for `len` bytes, and the old room
1022        // holds them; the two allocations are distinct.
1023        unsafe { core::ptr::copy_nonoverlapping(self.ptr.as_ptr(), own.as_mut_ptr(), self.len) };
1024        // SAFETY: a `Vec`'s buffer pointer is never null.
1025        self.ptr = unsafe { NonNull::new_unchecked(own.as_mut_ptr()) };
1026        self.capacity = own.capacity();
1027        self.own = own;
1028    }
1029}
1030
1031impl Default for HistoryBuf {
1032    fn default() -> Self {
1033        Self::new()
1034    }
1035}
1036
1037impl Clone for HistoryBuf {
1038    /// A copy owning its bytes, whatever the source's storage.
1039    fn clone(&self) -> Self {
1040        let mut copy = Self::new();
1041        copy.extend_from_slice(self);
1042        copy
1043    }
1044
1045    /// Copies the bytes into the room this buffer already has when they fit,
1046    /// so restoring a snapshot keeps the history where it was laid out.
1047    fn clone_from(&mut self, source: &Self) {
1048        self.clear();
1049        self.extend_from_slice(source);
1050    }
1051}
1052
1053/// A buffer owning `bytes`, for tests that set a history up by hand.
1054#[cfg(test)]
1055impl From<Vec<u8>> for HistoryBuf {
1056    fn from(mut bytes: Vec<u8>) -> Self {
1057        let len = bytes.len();
1058        // The owned room keeps length 0 (see the field); the bytes stay
1059        // written, and this buffer's `len` claims them.
1060        // SAFETY: shrinking a `Vec`'s length to 0 needs no drop for `u8`.
1061        unsafe { bytes.set_len(0) };
1062        Self {
1063            // SAFETY: a `Vec`'s buffer pointer is never null.
1064            ptr: unsafe { NonNull::new_unchecked(bytes.as_mut_ptr()) },
1065            capacity: bytes.capacity(),
1066            len,
1067            own: bytes,
1068        }
1069    }
1070}
1071
1072impl core::ops::Deref for HistoryBuf {
1073    type Target = [u8];
1074
1075    fn deref(&self) -> &[u8] {
1076        // SAFETY: the first `len` bytes were written by this buffer, and its
1077        // room is live.
1078        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
1079    }
1080}
1081
1082impl core::ops::DerefMut for HistoryBuf {
1083    fn deref_mut(&mut self) -> &mut [u8] {
1084        // SAFETY: as `deref`, with `&mut self` making the access exclusive.
1085        unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
1086    }
1087}
1088
1089impl core::fmt::Debug for HistoryBuf {
1090    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1091        f.debug_struct("HistoryBuf")
1092            .field("len", &self.len)
1093            .field("capacity", &self.capacity)
1094            .finish()
1095    }
1096}
1097
1098/// What a match finder settles about its history before the next frame is laid
1099/// out: the floor past every position the previous frame indexed, which is
1100/// read off the history's length, and the resident dictionary prefix the reset
1101/// may keep. Taking both first lets the history drop everything else before
1102/// the layout sizes its room and carries its bytes into it.
1103#[derive(Clone, Copy, Debug)]
1104pub(crate) struct RetiredHistory {
1105    pub(crate) next_floor: usize,
1106    pub(crate) kept: Option<usize>,
1107}
1108
1109/// A byte buffer a block is read into: the match finder's history, or the
1110/// output of an uncompressed frame, which carries its blocks raw.
1111pub(crate) trait IngestBuffer: core::ops::DerefMut<Target = [u8]> {
1112    fn extend_from_slice(&mut self, bytes: &[u8]);
1113    fn resize(&mut self, len: usize, value: u8);
1114    fn truncate(&mut self, len: usize);
1115    fn push(&mut self, byte: u8);
1116}
1117
1118impl IngestBuffer for Vec<u8> {
1119    fn extend_from_slice(&mut self, bytes: &[u8]) {
1120        Vec::extend_from_slice(self, bytes);
1121    }
1122
1123    fn resize(&mut self, len: usize, value: u8) {
1124        Vec::resize(self, len, value);
1125    }
1126
1127    fn truncate(&mut self, len: usize) {
1128        Vec::truncate(self, len);
1129    }
1130
1131    fn push(&mut self, byte: u8) {
1132        Vec::push(self, byte);
1133    }
1134}
1135
1136impl IngestBuffer for HistoryBuf {
1137    fn extend_from_slice(&mut self, bytes: &[u8]) {
1138        HistoryBuf::extend_from_slice(self, bytes);
1139    }
1140
1141    fn resize(&mut self, len: usize, value: u8) {
1142        HistoryBuf::resize(self, len, value);
1143    }
1144
1145    fn truncate(&mut self, len: usize) {
1146        HistoryBuf::truncate(self, len);
1147    }
1148
1149    fn push(&mut self, byte: u8) {
1150        HistoryBuf::push(self, byte);
1151    }
1152}
1153
1154/// A buffer in a workspace with a fixed capacity and a length, used like a
1155/// `Vec` that never reallocates.
1156pub(crate) struct RegionVec<T> {
1157    region: Region<T>,
1158    len: usize,
1159}
1160
1161impl<T: Copy> RegionVec<T> {
1162    /// A buffer with no room, the state of a holder before its first bind.
1163    pub(crate) const fn empty() -> Self {
1164        Self {
1165            region: Region::empty(),
1166            len: 0,
1167        }
1168    }
1169
1170    pub(crate) fn capacity(&self) -> usize {
1171        self.region.len
1172    }
1173
1174    pub(crate) fn clear(&mut self) {
1175        self.len = 0;
1176    }
1177
1178    /// # Panics
1179    ///
1180    /// Panics when the buffer is full: its capacity was sized from the most
1181    /// the frame can put in it, so a full buffer is a sizing bug.
1182    #[inline]
1183    pub(crate) fn push(&mut self, value: T) {
1184        if self.len == self.region.len {
1185            capacity_exhausted();
1186        }
1187        // SAFETY: `len < capacity`, so the slot is inside the region.
1188        unsafe { self.region.as_mut_ptr().add(self.len).write(value) };
1189        self.len += 1;
1190    }
1191
1192    /// Pointer to the first value, for writes past the length that
1193    /// [`Self::set_len`] then claims.
1194    pub(crate) fn as_mut_ptr(&mut self) -> *mut T {
1195        self.region.as_mut_ptr()
1196    }
1197
1198    /// # Safety
1199    ///
1200    /// `len` must not exceed the capacity, and every value below it must have
1201    /// been written.
1202    pub(crate) unsafe fn set_len(&mut self, len: usize) {
1203        debug_assert!(len <= self.region.len);
1204        self.len = len;
1205    }
1206}
1207
1208impl<T: Copy> core::ops::Deref for RegionVec<T> {
1209    type Target = [T];
1210
1211    fn deref(&self) -> &[T] {
1212        // SAFETY: the first `len` values were written by `push`,
1213        // `extend_from_slice` or a caller of `set_len`, and the region is live.
1214        unsafe { core::slice::from_raw_parts(self.region.as_ptr(), self.len) }
1215    }
1216}
1217
1218impl<T: Copy> core::ops::DerefMut for RegionVec<T> {
1219    fn deref_mut(&mut self) -> &mut [T] {
1220        // SAFETY: as `deref`, with `&mut self` making the access exclusive.
1221        unsafe { core::slice::from_raw_parts_mut(self.region.as_mut_ptr(), self.len) }
1222    }
1223}
1224
1225impl<T: Copy> Default for RegionVec<T> {
1226    fn default() -> Self {
1227        Self::empty()
1228    }
1229}
1230
1231#[cold]
1232#[inline(never)]
1233pub(crate) fn capacity_exhausted() -> ! {
1234    panic!("workspace buffer sized below what the frame put in it")
1235}
1236
1237#[cfg(test)]
1238mod tests;