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