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;