xref: /freebsd/sys/contrib/openzfs/module/zstd/lib/compress/zstd_cwksp.h (revision 924226fba12cc9a228c73b956e1b7fa24c60b055)
1 /*
2  * Copyright (c) 2016-2020, Yann Collet, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under both the BSD-style license (found in the
6  * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7  * in the COPYING file in the root directory of this source tree).
8  * You may select, at your option, one of the above-listed licenses.
9  */
10 
11 #ifndef ZSTD_CWKSP_H
12 #define ZSTD_CWKSP_H
13 
14 /*-*************************************
15 *  Dependencies
16 ***************************************/
17 #include "../common/zstd_internal.h"
18 
19 #if defined (__cplusplus)
20 extern "C" {
21 #endif
22 
23 /*-*************************************
24 *  Constants
25 ***************************************/
26 
27 /* Since the workspace is effectively its own little malloc implementation /
28  * arena, when we run under ASAN, we should similarly insert redzones between
29  * each internal element of the workspace, so ASAN will catch overruns that
30  * reach outside an object but that stay inside the workspace.
31  *
32  * This defines the size of that redzone.
33  */
34 #ifndef ZSTD_CWKSP_ASAN_REDZONE_SIZE
35 #define ZSTD_CWKSP_ASAN_REDZONE_SIZE 128
36 #endif
37 
38 /*-*************************************
39 *  Structures
40 ***************************************/
41 typedef enum {
42     ZSTD_cwksp_alloc_objects,
43     ZSTD_cwksp_alloc_buffers,
44     ZSTD_cwksp_alloc_aligned
45 } ZSTD_cwksp_alloc_phase_e;
46 
47 /**
48  * Zstd fits all its internal datastructures into a single continuous buffer,
49  * so that it only needs to perform a single OS allocation (or so that a buffer
50  * can be provided to it and it can perform no allocations at all). This buffer
51  * is called the workspace.
52  *
53  * Several optimizations complicate that process of allocating memory ranges
54  * from this workspace for each internal datastructure:
55  *
56  * - These different internal datastructures have different setup requirements:
57  *
58  *   - The static objects need to be cleared once and can then be trivially
59  *     reused for each compression.
60  *
61  *   - Various buffers don't need to be initialized at all--they are always
62  *     written into before they're read.
63  *
64  *   - The matchstate tables have a unique requirement that they don't need
65  *     their memory to be totally cleared, but they do need the memory to have
66  *     some bound, i.e., a guarantee that all values in the memory they've been
67  *     allocated is less than some maximum value (which is the starting value
68  *     for the indices that they will then use for compression). When this
69  *     guarantee is provided to them, they can use the memory without any setup
70  *     work. When it can't, they have to clear the area.
71  *
72  * - These buffers also have different alignment requirements.
73  *
74  * - We would like to reuse the objects in the workspace for multiple
75  *   compressions without having to perform any expensive reallocation or
76  *   reinitialization work.
77  *
78  * - We would like to be able to efficiently reuse the workspace across
79  *   multiple compressions **even when the compression parameters change** and
80  *   we need to resize some of the objects (where possible).
81  *
82  * To attempt to manage this buffer, given these constraints, the ZSTD_cwksp
83  * abstraction was created. It works as follows:
84  *
85  * Workspace Layout:
86  *
87  * [                        ... workspace ...                         ]
88  * [objects][tables ... ->] free space [<- ... aligned][<- ... buffers]
89  *
90  * The various objects that live in the workspace are divided into the
91  * following categories, and are allocated separately:
92  *
93  * - Static objects: this is optionally the enclosing ZSTD_CCtx or ZSTD_CDict,
94  *   so that literally everything fits in a single buffer. Note: if present,
95  *   this must be the first object in the workspace, since ZSTD_free{CCtx,
96  *   CDict}() rely on a pointer comparison to see whether one or two frees are
97  *   required.
98  *
99  * - Fixed size objects: these are fixed-size, fixed-count objects that are
100  *   nonetheless "dynamically" allocated in the workspace so that we can
101  *   control how they're initialized separately from the broader ZSTD_CCtx.
102  *   Examples:
103  *   - Entropy Workspace
104  *   - 2 x ZSTD_compressedBlockState_t
105  *   - CDict dictionary contents
106  *
107  * - Tables: these are any of several different datastructures (hash tables,
108  *   chain tables, binary trees) that all respect a common format: they are
109  *   uint32_t arrays, all of whose values are between 0 and (nextSrc - base).
110  *   Their sizes depend on the cparams.
111  *
112  * - Aligned: these buffers are used for various purposes that require 4 byte
113  *   alignment, but don't require any initialization before they're used.
114  *
115  * - Buffers: these buffers are used for various purposes that don't require
116  *   any alignment or initialization before they're used. This means they can
117  *   be moved around at no cost for a new compression.
118  *
119  * Allocating Memory:
120  *
121  * The various types of objects must be allocated in order, so they can be
122  * correctly packed into the workspace buffer. That order is:
123  *
124  * 1. Objects
125  * 2. Buffers
126  * 3. Aligned
127  * 4. Tables
128  *
129  * Attempts to reserve objects of different types out of order will fail.
130  */
131 typedef struct {
132     void* workspace;
133     void* workspaceEnd;
134 
135     void* objectEnd;
136     void* tableEnd;
137     void* tableValidEnd;
138     void* allocStart;
139 
140     int allocFailed;
141     int workspaceOversizedDuration;
142     ZSTD_cwksp_alloc_phase_e phase;
143 } ZSTD_cwksp;
144 
145 /*-*************************************
146 *  Functions
147 ***************************************/
148 
149 MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws);
150 
151 MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) {
152     (void)ws;
153     assert(ws->workspace <= ws->objectEnd);
154     assert(ws->objectEnd <= ws->tableEnd);
155     assert(ws->objectEnd <= ws->tableValidEnd);
156     assert(ws->tableEnd <= ws->allocStart);
157     assert(ws->tableValidEnd <= ws->allocStart);
158     assert(ws->allocStart <= ws->workspaceEnd);
159 }
160 
161 /**
162  * Align must be a power of 2.
163  */
164 MEM_STATIC size_t ZSTD_cwksp_align(size_t size, size_t const align) {
165     size_t const mask = align - 1;
166     assert((align & mask) == 0);
167     return (size + mask) & ~mask;
168 }
169 
170 /**
171  * Use this to determine how much space in the workspace we will consume to
172  * allocate this object. (Normally it should be exactly the size of the object,
173  * but under special conditions, like ASAN, where we pad each object, it might
174  * be larger.)
175  *
176  * Since tables aren't currently redzoned, you don't need to call through this
177  * to figure out how much space you need for the matchState tables. Everything
178  * else is though.
179  */
180 MEM_STATIC size_t ZSTD_cwksp_alloc_size(size_t size) {
181 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
182     return size + 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;
183 #else
184     return size;
185 #endif
186 }
187 
188 MEM_STATIC void ZSTD_cwksp_internal_advance_phase(
189         ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) {
190     assert(phase >= ws->phase);
191     if (phase > ws->phase) {
192         if (ws->phase < ZSTD_cwksp_alloc_buffers &&
193                 phase >= ZSTD_cwksp_alloc_buffers) {
194             ws->tableValidEnd = ws->objectEnd;
195         }
196         if (ws->phase < ZSTD_cwksp_alloc_aligned &&
197                 phase >= ZSTD_cwksp_alloc_aligned) {
198             /* If unaligned allocations down from a too-large top have left us
199              * unaligned, we need to realign our alloc ptr. Technically, this
200              * can consume space that is unaccounted for in the neededSpace
201              * calculation. However, I believe this can only happen when the
202              * workspace is too large, and specifically when it is too large
203              * by a larger margin than the space that will be consumed. */
204             /* TODO: cleaner, compiler warning friendly way to do this??? */
205             ws->allocStart = (BYTE*)ws->allocStart - ((size_t)ws->allocStart & (sizeof(U32)-1));
206             if (ws->allocStart < ws->tableValidEnd) {
207                 ws->tableValidEnd = ws->allocStart;
208             }
209         }
210         ws->phase = phase;
211     }
212 }
213 
214 /**
215  * Returns whether this object/buffer/etc was allocated in this workspace.
216  */
217 MEM_STATIC int ZSTD_cwksp_owns_buffer(const ZSTD_cwksp* ws, const void* ptr) {
218     return (ptr != NULL) && (ws->workspace <= ptr) && (ptr <= ws->workspaceEnd);
219 }
220 
221 /**
222  * Internal function. Do not use directly.
223  */
224 MEM_STATIC void* ZSTD_cwksp_reserve_internal(
225         ZSTD_cwksp* ws, size_t bytes, ZSTD_cwksp_alloc_phase_e phase) {
226     void* alloc;
227     void* bottom = ws->tableEnd;
228     ZSTD_cwksp_internal_advance_phase(ws, phase);
229     alloc = (BYTE *)ws->allocStart - bytes;
230 
231 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
232     /* over-reserve space */
233     alloc = (BYTE *)alloc - 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;
234 #endif
235 
236     DEBUGLOG(5, "cwksp: reserving %p %zd bytes, %zd bytes remaining",
237         alloc, bytes, ZSTD_cwksp_available_space(ws) - bytes);
238     ZSTD_cwksp_assert_internal_consistency(ws);
239     assert(alloc >= bottom);
240     if (alloc < bottom) {
241         DEBUGLOG(4, "cwksp: alloc failed!");
242         ws->allocFailed = 1;
243         return NULL;
244     }
245     if (alloc < ws->tableValidEnd) {
246         ws->tableValidEnd = alloc;
247     }
248     ws->allocStart = alloc;
249 
250 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
251     /* Move alloc so there's ZSTD_CWKSP_ASAN_REDZONE_SIZE unused space on
252      * either size. */
253     alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE;
254     __asan_unpoison_memory_region(alloc, bytes);
255 #endif
256 
257     return alloc;
258 }
259 
260 /**
261  * Reserves and returns unaligned memory.
262  */
263 MEM_STATIC BYTE* ZSTD_cwksp_reserve_buffer(ZSTD_cwksp* ws, size_t bytes) {
264     return (BYTE*)ZSTD_cwksp_reserve_internal(ws, bytes, ZSTD_cwksp_alloc_buffers);
265 }
266 
267 /**
268  * Reserves and returns memory sized on and aligned on sizeof(unsigned).
269  */
270 MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) {
271     assert((bytes & (sizeof(U32)-1)) == 0);
272     return ZSTD_cwksp_reserve_internal(ws, ZSTD_cwksp_align(bytes, sizeof(U32)), ZSTD_cwksp_alloc_aligned);
273 }
274 
275 /**
276  * Aligned on sizeof(unsigned). These buffers have the special property that
277  * their values remain constrained, allowing us to re-use them without
278  * memset()-ing them.
279  */
280 MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) {
281     const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned;
282     void* alloc = ws->tableEnd;
283     void* end = (BYTE *)alloc + bytes;
284     void* top = ws->allocStart;
285 
286     DEBUGLOG(5, "cwksp: reserving %p table %zd bytes, %zd bytes remaining",
287         alloc, bytes, ZSTD_cwksp_available_space(ws) - bytes);
288     assert((bytes & (sizeof(U32)-1)) == 0);
289     ZSTD_cwksp_internal_advance_phase(ws, phase);
290     ZSTD_cwksp_assert_internal_consistency(ws);
291     assert(end <= top);
292     if (end > top) {
293         DEBUGLOG(4, "cwksp: table alloc failed!");
294         ws->allocFailed = 1;
295         return NULL;
296     }
297     ws->tableEnd = end;
298 
299 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
300     __asan_unpoison_memory_region(alloc, bytes);
301 #endif
302 
303     return alloc;
304 }
305 
306 /**
307  * Aligned on sizeof(void*).
308  */
309 MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) {
310     size_t roundedBytes = ZSTD_cwksp_align(bytes, sizeof(void*));
311     void* alloc = ws->objectEnd;
312     void* end = (BYTE*)alloc + roundedBytes;
313 
314 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
315     /* over-reserve space */
316     end = (BYTE *)end + 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;
317 #endif
318 
319     DEBUGLOG(5,
320         "cwksp: reserving %p object %zd bytes (rounded to %zd), %zd bytes remaining",
321         alloc, bytes, roundedBytes, ZSTD_cwksp_available_space(ws) - roundedBytes);
322     assert(((size_t)alloc & (sizeof(void*)-1)) == 0);
323     assert((bytes & (sizeof(void*)-1)) == 0);
324     ZSTD_cwksp_assert_internal_consistency(ws);
325     /* we must be in the first phase, no advance is possible */
326     if (ws->phase != ZSTD_cwksp_alloc_objects || end > ws->workspaceEnd) {
327         DEBUGLOG(4, "cwksp: object alloc failed!");
328         ws->allocFailed = 1;
329         return NULL;
330     }
331     ws->objectEnd = end;
332     ws->tableEnd = end;
333     ws->tableValidEnd = end;
334 
335 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
336     /* Move alloc so there's ZSTD_CWKSP_ASAN_REDZONE_SIZE unused space on
337      * either size. */
338     alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE;
339     __asan_unpoison_memory_region(alloc, bytes);
340 #endif
341 
342     return alloc;
343 }
344 
345 MEM_STATIC void ZSTD_cwksp_mark_tables_dirty(ZSTD_cwksp* ws) {
346     DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_dirty");
347 
348 #if defined (MEMORY_SANITIZER) && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE)
349     /* To validate that the table re-use logic is sound, and that we don't
350      * access table space that we haven't cleaned, we re-"poison" the table
351      * space every time we mark it dirty. */
352     {
353         size_t size = (BYTE*)ws->tableValidEnd - (BYTE*)ws->objectEnd;
354         assert(__msan_test_shadow(ws->objectEnd, size) == -1);
355         __msan_poison(ws->objectEnd, size);
356     }
357 #endif
358 
359     assert(ws->tableValidEnd >= ws->objectEnd);
360     assert(ws->tableValidEnd <= ws->allocStart);
361     ws->tableValidEnd = ws->objectEnd;
362     ZSTD_cwksp_assert_internal_consistency(ws);
363 }
364 
365 MEM_STATIC void ZSTD_cwksp_mark_tables_clean(ZSTD_cwksp* ws) {
366     DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_clean");
367     assert(ws->tableValidEnd >= ws->objectEnd);
368     assert(ws->tableValidEnd <= ws->allocStart);
369     if (ws->tableValidEnd < ws->tableEnd) {
370         ws->tableValidEnd = ws->tableEnd;
371     }
372     ZSTD_cwksp_assert_internal_consistency(ws);
373 }
374 
375 /**
376  * Zero the part of the allocated tables not already marked clean.
377  */
378 MEM_STATIC void ZSTD_cwksp_clean_tables(ZSTD_cwksp* ws) {
379     DEBUGLOG(4, "cwksp: ZSTD_cwksp_clean_tables");
380     assert(ws->tableValidEnd >= ws->objectEnd);
381     assert(ws->tableValidEnd <= ws->allocStart);
382     if (ws->tableValidEnd < ws->tableEnd) {
383         memset(ws->tableValidEnd, 0, (BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd);
384     }
385     ZSTD_cwksp_mark_tables_clean(ws);
386 }
387 
388 /**
389  * Invalidates table allocations.
390  * All other allocations remain valid.
391  */
392 MEM_STATIC void ZSTD_cwksp_clear_tables(ZSTD_cwksp* ws) {
393     DEBUGLOG(4, "cwksp: clearing tables!");
394 
395 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
396     {
397         size_t size = (BYTE*)ws->tableValidEnd - (BYTE*)ws->objectEnd;
398         __asan_poison_memory_region(ws->objectEnd, size);
399     }
400 #endif
401 
402     ws->tableEnd = ws->objectEnd;
403     ZSTD_cwksp_assert_internal_consistency(ws);
404 }
405 
406 /**
407  * Invalidates all buffer, aligned, and table allocations.
408  * Object allocations remain valid.
409  */
410 MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) {
411     DEBUGLOG(4, "cwksp: clearing!");
412 
413 #if defined (MEMORY_SANITIZER) && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE)
414     /* To validate that the context re-use logic is sound, and that we don't
415      * access stuff that this compression hasn't initialized, we re-"poison"
416      * the workspace (or at least the non-static, non-table parts of it)
417      * every time we start a new compression. */
418     {
419         size_t size = (BYTE*)ws->workspaceEnd - (BYTE*)ws->tableValidEnd;
420         __msan_poison(ws->tableValidEnd, size);
421     }
422 #endif
423 
424 #if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
425     {
426         size_t size = (BYTE*)ws->workspaceEnd - (BYTE*)ws->objectEnd;
427         __asan_poison_memory_region(ws->objectEnd, size);
428     }
429 #endif
430 
431     ws->tableEnd = ws->objectEnd;
432     ws->allocStart = ws->workspaceEnd;
433     ws->allocFailed = 0;
434     if (ws->phase > ZSTD_cwksp_alloc_buffers) {
435         ws->phase = ZSTD_cwksp_alloc_buffers;
436     }
437     ZSTD_cwksp_assert_internal_consistency(ws);
438 }
439 
440 /**
441  * The provided workspace takes ownership of the buffer [start, start+size).
442  * Any existing values in the workspace are ignored (the previously managed
443  * buffer, if present, must be separately freed).
444  */
445 MEM_STATIC void ZSTD_cwksp_init(ZSTD_cwksp* ws, void* start, size_t size) {
446     DEBUGLOG(4, "cwksp: init'ing workspace with %zd bytes", size);
447     assert(((size_t)start & (sizeof(void*)-1)) == 0); /* ensure correct alignment */
448     ws->workspace = start;
449     ws->workspaceEnd = (BYTE*)start + size;
450     ws->objectEnd = ws->workspace;
451     ws->tableValidEnd = ws->objectEnd;
452     ws->phase = ZSTD_cwksp_alloc_objects;
453     ZSTD_cwksp_clear(ws);
454     ws->workspaceOversizedDuration = 0;
455     ZSTD_cwksp_assert_internal_consistency(ws);
456 }
457 
458 MEM_STATIC size_t ZSTD_cwksp_create(ZSTD_cwksp* ws, size_t size, ZSTD_customMem customMem) {
459     void* workspace = ZSTD_malloc(size, customMem);
460     DEBUGLOG(4, "cwksp: creating new workspace with %zd bytes", size);
461     RETURN_ERROR_IF(workspace == NULL, memory_allocation, "NULL pointer!");
462     ZSTD_cwksp_init(ws, workspace, size);
463     return 0;
464 }
465 
466 MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) {
467     void *ptr = ws->workspace;
468     DEBUGLOG(4, "cwksp: freeing workspace");
469     memset(ws, 0, sizeof(ZSTD_cwksp));
470     ZSTD_free(ptr, customMem);
471 }
472 
473 /**
474  * Moves the management of a workspace from one cwksp to another. The src cwksp
475  * is left in an invalid state (src must be re-init()'ed before its used again).
476  */
477 MEM_STATIC void ZSTD_cwksp_move(ZSTD_cwksp* dst, ZSTD_cwksp* src) {
478     *dst = *src;
479     memset(src, 0, sizeof(ZSTD_cwksp));
480 }
481 
482 MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) {
483     return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace);
484 }
485 
486 MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) {
487     return ws->allocFailed;
488 }
489 
490 /*-*************************************
491 *  Functions Checking Free Space
492 ***************************************/
493 
494 MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws) {
495     return (size_t)((BYTE*)ws->allocStart - (BYTE*)ws->tableEnd);
496 }
497 
498 MEM_STATIC int ZSTD_cwksp_check_available(ZSTD_cwksp* ws, size_t additionalNeededSpace) {
499     return ZSTD_cwksp_available_space(ws) >= additionalNeededSpace;
500 }
501 
502 MEM_STATIC int ZSTD_cwksp_check_too_large(ZSTD_cwksp* ws, size_t additionalNeededSpace) {
503     return ZSTD_cwksp_check_available(
504         ws, additionalNeededSpace * ZSTD_WORKSPACETOOLARGE_FACTOR);
505 }
506 
507 MEM_STATIC int ZSTD_cwksp_check_wasteful(ZSTD_cwksp* ws, size_t additionalNeededSpace) {
508     return ZSTD_cwksp_check_too_large(ws, additionalNeededSpace)
509         && ws->workspaceOversizedDuration > ZSTD_WORKSPACETOOLARGE_MAXDURATION;
510 }
511 
512 MEM_STATIC void ZSTD_cwksp_bump_oversized_duration(
513         ZSTD_cwksp* ws, size_t additionalNeededSpace) {
514     if (ZSTD_cwksp_check_too_large(ws, additionalNeededSpace)) {
515         ws->workspaceOversizedDuration++;
516     } else {
517         ws->workspaceOversizedDuration = 0;
518     }
519 }
520 
521 #if defined (__cplusplus)
522 }
523 #endif
524 
525 #endif /* ZSTD_CWKSP_H */
526