Path: blob/main/sys/contrib/openzfs/module/zstd/lib/compress/zstd_cwksp.h
48774 views
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only1/*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 the6* LICENSE file in the root directory of this source tree) and the GPLv2 (found7* 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*/1011#ifndef ZSTD_CWKSP_H12#define ZSTD_CWKSP_H1314/*-*************************************15* Dependencies16***************************************/17#include "../common/zstd_internal.h"1819#if defined (__cplusplus)20extern "C" {21#endif2223/*-*************************************24* Constants25***************************************/2627/* Since the workspace is effectively its own little malloc implementation /28* arena, when we run under ASAN, we should similarly insert redzones between29* each internal element of the workspace, so ASAN will catch overruns that30* 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_SIZE35#define ZSTD_CWKSP_ASAN_REDZONE_SIZE 12836#endif3738/*-*************************************39* Structures40***************************************/41typedef enum {42ZSTD_cwksp_alloc_objects,43ZSTD_cwksp_alloc_buffers,44ZSTD_cwksp_alloc_aligned45} ZSTD_cwksp_alloc_phase_e;4647/**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 buffer50* can be provided to it and it can perform no allocations at all). This buffer51* is called the workspace.52*53* Several optimizations complicate that process of allocating memory ranges54* 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 trivially59* reused for each compression.60*61* - Various buffers don't need to be initialized at all--they are always62* written into before they're read.63*64* - The matchstate tables have a unique requirement that they don't need65* their memory to be totally cleared, but they do need the memory to have66* some bound, i.e., a guarantee that all values in the memory they've been67* allocated is less than some maximum value (which is the starting value68* for the indices that they will then use for compression). When this69* guarantee is provided to them, they can use the memory without any setup70* 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 multiple75* compressions without having to perform any expensive reallocation or76* reinitialization work.77*78* - We would like to be able to efficiently reuse the workspace across79* multiple compressions **even when the compression parameters change** and80* we need to resize some of the objects (where possible).81*82* To attempt to manage this buffer, given these constraints, the ZSTD_cwksp83* 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 the91* 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 are97* required.98*99* - Fixed size objects: these are fixed-size, fixed-count objects that are100* nonetheless "dynamically" allocated in the workspace so that we can101* control how they're initialized separately from the broader ZSTD_CCtx.102* Examples:103* - Entropy Workspace104* - 2 x ZSTD_compressedBlockState_t105* - CDict dictionary contents106*107* - Tables: these are any of several different datastructures (hash tables,108* chain tables, binary trees) that all respect a common format: they are109* 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 byte113* alignment, but don't require any initialization before they're used.114*115* - Buffers: these buffers are used for various purposes that don't require116* any alignment or initialization before they're used. This means they can117* 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 be122* correctly packed into the workspace buffer. That order is:123*124* 1. Objects125* 2. Buffers126* 3. Aligned127* 4. Tables128*129* Attempts to reserve objects of different types out of order will fail.130*/131typedef struct {132void* workspace;133void* workspaceEnd;134135void* objectEnd;136void* tableEnd;137void* tableValidEnd;138void* allocStart;139140int allocFailed;141int workspaceOversizedDuration;142ZSTD_cwksp_alloc_phase_e phase;143} ZSTD_cwksp;144145/*-*************************************146* Functions147***************************************/148149MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws);150151MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) {152(void)ws;153assert(ws->workspace <= ws->objectEnd);154assert(ws->objectEnd <= ws->tableEnd);155assert(ws->objectEnd <= ws->tableValidEnd);156assert(ws->tableEnd <= ws->allocStart);157assert(ws->tableValidEnd <= ws->allocStart);158assert(ws->allocStart <= ws->workspaceEnd);159}160161/**162* Align must be a power of 2.163*/164MEM_STATIC size_t ZSTD_cwksp_align(size_t size, size_t const align) {165size_t const mask = align - 1;166assert((align & mask) == 0);167return (size + mask) & ~mask;168}169170/**171* Use this to determine how much space in the workspace we will consume to172* 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 might174* be larger.)175*176* Since tables aren't currently redzoned, you don't need to call through this177* to figure out how much space you need for the matchState tables. Everything178* else is though.179*/180MEM_STATIC size_t ZSTD_cwksp_alloc_size(size_t size) {181#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)182return size + 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;183#else184return size;185#endif186}187188MEM_STATIC void ZSTD_cwksp_internal_advance_phase(189ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) {190assert(phase >= ws->phase);191if (phase > ws->phase) {192if (ws->phase < ZSTD_cwksp_alloc_buffers &&193phase >= ZSTD_cwksp_alloc_buffers) {194ws->tableValidEnd = ws->objectEnd;195}196if (ws->phase < ZSTD_cwksp_alloc_aligned &&197phase >= ZSTD_cwksp_alloc_aligned) {198/* If unaligned allocations down from a too-large top have left us199* unaligned, we need to realign our alloc ptr. Technically, this200* can consume space that is unaccounted for in the neededSpace201* calculation. However, I believe this can only happen when the202* workspace is too large, and specifically when it is too large203* by a larger margin than the space that will be consumed. */204/* TODO: cleaner, compiler warning friendly way to do this??? */205ws->allocStart = (BYTE*)ws->allocStart - ((size_t)ws->allocStart & (sizeof(U32)-1));206if (ws->allocStart < ws->tableValidEnd) {207ws->tableValidEnd = ws->allocStart;208}209}210ws->phase = phase;211}212}213214/**215* Returns whether this object/buffer/etc was allocated in this workspace.216*/217MEM_STATIC int ZSTD_cwksp_owns_buffer(const ZSTD_cwksp* ws, const void* ptr) {218return (ptr != NULL) && (ws->workspace <= ptr) && (ptr <= ws->workspaceEnd);219}220221/**222* Internal function. Do not use directly.223*/224MEM_STATIC void* ZSTD_cwksp_reserve_internal(225ZSTD_cwksp* ws, size_t bytes, ZSTD_cwksp_alloc_phase_e phase) {226void* alloc;227void* bottom = ws->tableEnd;228ZSTD_cwksp_internal_advance_phase(ws, phase);229alloc = (BYTE *)ws->allocStart - bytes;230231#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)232/* over-reserve space */233alloc = (BYTE *)alloc - 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;234#endif235236DEBUGLOG(5, "cwksp: reserving %p %zd bytes, %zd bytes remaining",237alloc, bytes, ZSTD_cwksp_available_space(ws) - bytes);238ZSTD_cwksp_assert_internal_consistency(ws);239assert(alloc >= bottom);240if (alloc < bottom) {241DEBUGLOG(4, "cwksp: alloc failed!");242ws->allocFailed = 1;243return NULL;244}245if (alloc < ws->tableValidEnd) {246ws->tableValidEnd = alloc;247}248ws->allocStart = alloc;249250#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)251/* Move alloc so there's ZSTD_CWKSP_ASAN_REDZONE_SIZE unused space on252* either size. */253alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE;254__asan_unpoison_memory_region(alloc, bytes);255#endif256257return alloc;258}259260/**261* Reserves and returns unaligned memory.262*/263MEM_STATIC BYTE* ZSTD_cwksp_reserve_buffer(ZSTD_cwksp* ws, size_t bytes) {264return (BYTE*)ZSTD_cwksp_reserve_internal(ws, bytes, ZSTD_cwksp_alloc_buffers);265}266267/**268* Reserves and returns memory sized on and aligned on sizeof(unsigned).269*/270MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) {271assert((bytes & (sizeof(U32)-1)) == 0);272return ZSTD_cwksp_reserve_internal(ws, ZSTD_cwksp_align(bytes, sizeof(U32)), ZSTD_cwksp_alloc_aligned);273}274275/**276* Aligned on sizeof(unsigned). These buffers have the special property that277* their values remain constrained, allowing us to re-use them without278* memset()-ing them.279*/280MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) {281const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned;282void* alloc = ws->tableEnd;283void* end = (BYTE *)alloc + bytes;284void* top = ws->allocStart;285286DEBUGLOG(5, "cwksp: reserving %p table %zd bytes, %zd bytes remaining",287alloc, bytes, ZSTD_cwksp_available_space(ws) - bytes);288assert((bytes & (sizeof(U32)-1)) == 0);289ZSTD_cwksp_internal_advance_phase(ws, phase);290ZSTD_cwksp_assert_internal_consistency(ws);291assert(end <= top);292if (end > top) {293DEBUGLOG(4, "cwksp: table alloc failed!");294ws->allocFailed = 1;295return NULL;296}297ws->tableEnd = end;298299#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)300__asan_unpoison_memory_region(alloc, bytes);301#endif302303return alloc;304}305306/**307* Aligned on sizeof(void*).308*/309MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) {310size_t roundedBytes = ZSTD_cwksp_align(bytes, sizeof(void*));311void* alloc = ws->objectEnd;312void* end = (BYTE*)alloc + roundedBytes;313314#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)315/* over-reserve space */316end = (BYTE *)end + 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE;317#endif318319DEBUGLOG(5,320"cwksp: reserving %p object %zd bytes (rounded to %zd), %zd bytes remaining",321alloc, bytes, roundedBytes, ZSTD_cwksp_available_space(ws) - roundedBytes);322assert(((size_t)alloc & (sizeof(void*)-1)) == 0);323assert((bytes & (sizeof(void*)-1)) == 0);324ZSTD_cwksp_assert_internal_consistency(ws);325/* we must be in the first phase, no advance is possible */326if (ws->phase != ZSTD_cwksp_alloc_objects || end > ws->workspaceEnd) {327DEBUGLOG(4, "cwksp: object alloc failed!");328ws->allocFailed = 1;329return NULL;330}331ws->objectEnd = end;332ws->tableEnd = end;333ws->tableValidEnd = end;334335#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)336/* Move alloc so there's ZSTD_CWKSP_ASAN_REDZONE_SIZE unused space on337* either size. */338alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE;339__asan_unpoison_memory_region(alloc, bytes);340#endif341342return alloc;343}344345MEM_STATIC void ZSTD_cwksp_mark_tables_dirty(ZSTD_cwksp* ws) {346DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_dirty");347348#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't350* access table space that we haven't cleaned, we re-"poison" the table351* space every time we mark it dirty. */352{353size_t size = (BYTE*)ws->tableValidEnd - (BYTE*)ws->objectEnd;354assert(__msan_test_shadow(ws->objectEnd, size) == -1);355__msan_poison(ws->objectEnd, size);356}357#endif358359assert(ws->tableValidEnd >= ws->objectEnd);360assert(ws->tableValidEnd <= ws->allocStart);361ws->tableValidEnd = ws->objectEnd;362ZSTD_cwksp_assert_internal_consistency(ws);363}364365MEM_STATIC void ZSTD_cwksp_mark_tables_clean(ZSTD_cwksp* ws) {366DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_clean");367assert(ws->tableValidEnd >= ws->objectEnd);368assert(ws->tableValidEnd <= ws->allocStart);369if (ws->tableValidEnd < ws->tableEnd) {370ws->tableValidEnd = ws->tableEnd;371}372ZSTD_cwksp_assert_internal_consistency(ws);373}374375/**376* Zero the part of the allocated tables not already marked clean.377*/378MEM_STATIC void ZSTD_cwksp_clean_tables(ZSTD_cwksp* ws) {379DEBUGLOG(4, "cwksp: ZSTD_cwksp_clean_tables");380assert(ws->tableValidEnd >= ws->objectEnd);381assert(ws->tableValidEnd <= ws->allocStart);382if (ws->tableValidEnd < ws->tableEnd) {383memset(ws->tableValidEnd, 0, (BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd);384}385ZSTD_cwksp_mark_tables_clean(ws);386}387388/**389* Invalidates table allocations.390* All other allocations remain valid.391*/392MEM_STATIC void ZSTD_cwksp_clear_tables(ZSTD_cwksp* ws) {393DEBUGLOG(4, "cwksp: clearing tables!");394395#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)396{397size_t size = (BYTE*)ws->tableValidEnd - (BYTE*)ws->objectEnd;398__asan_poison_memory_region(ws->objectEnd, size);399}400#endif401402ws->tableEnd = ws->objectEnd;403ZSTD_cwksp_assert_internal_consistency(ws);404}405406/**407* Invalidates all buffer, aligned, and table allocations.408* Object allocations remain valid.409*/410MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) {411DEBUGLOG(4, "cwksp: clearing!");412413#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't415* 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{419size_t size = (BYTE*)ws->workspaceEnd - (BYTE*)ws->tableValidEnd;420__msan_poison(ws->tableValidEnd, size);421}422#endif423424#if defined (ADDRESS_SANITIZER) && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)425{426size_t size = (BYTE*)ws->workspaceEnd - (BYTE*)ws->objectEnd;427__asan_poison_memory_region(ws->objectEnd, size);428}429#endif430431ws->tableEnd = ws->objectEnd;432ws->allocStart = ws->workspaceEnd;433ws->allocFailed = 0;434if (ws->phase > ZSTD_cwksp_alloc_buffers) {435ws->phase = ZSTD_cwksp_alloc_buffers;436}437ZSTD_cwksp_assert_internal_consistency(ws);438}439440/**441* The provided workspace takes ownership of the buffer [start, start+size).442* Any existing values in the workspace are ignored (the previously managed443* buffer, if present, must be separately freed).444*/445MEM_STATIC void ZSTD_cwksp_init(ZSTD_cwksp* ws, void* start, size_t size) {446DEBUGLOG(4, "cwksp: init'ing workspace with %zd bytes", size);447assert(((size_t)start & (sizeof(void*)-1)) == 0); /* ensure correct alignment */448ws->workspace = start;449ws->workspaceEnd = (BYTE*)start + size;450ws->objectEnd = ws->workspace;451ws->tableValidEnd = ws->objectEnd;452ws->phase = ZSTD_cwksp_alloc_objects;453ZSTD_cwksp_clear(ws);454ws->workspaceOversizedDuration = 0;455ZSTD_cwksp_assert_internal_consistency(ws);456}457458MEM_STATIC size_t ZSTD_cwksp_create(ZSTD_cwksp* ws, size_t size, ZSTD_customMem customMem) {459void* workspace = ZSTD_malloc(size, customMem);460DEBUGLOG(4, "cwksp: creating new workspace with %zd bytes", size);461RETURN_ERROR_IF(workspace == NULL, memory_allocation, "NULL pointer!");462ZSTD_cwksp_init(ws, workspace, size);463return 0;464}465466MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) {467void *ptr = ws->workspace;468DEBUGLOG(4, "cwksp: freeing workspace");469memset(ws, 0, sizeof(ZSTD_cwksp));470ZSTD_free(ptr, customMem);471}472473/**474* Moves the management of a workspace from one cwksp to another. The src cwksp475* is left in an invalid state (src must be re-init()'ed before its used again).476*/477MEM_STATIC void ZSTD_cwksp_move(ZSTD_cwksp* dst, ZSTD_cwksp* src) {478*dst = *src;479memset(src, 0, sizeof(ZSTD_cwksp));480}481482MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) {483return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace);484}485486MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) {487return ws->allocFailed;488}489490/*-*************************************491* Functions Checking Free Space492***************************************/493494MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws) {495return (size_t)((BYTE*)ws->allocStart - (BYTE*)ws->tableEnd);496}497498MEM_STATIC int ZSTD_cwksp_check_available(ZSTD_cwksp* ws, size_t additionalNeededSpace) {499return ZSTD_cwksp_available_space(ws) >= additionalNeededSpace;500}501502MEM_STATIC int ZSTD_cwksp_check_too_large(ZSTD_cwksp* ws, size_t additionalNeededSpace) {503return ZSTD_cwksp_check_available(504ws, additionalNeededSpace * ZSTD_WORKSPACETOOLARGE_FACTOR);505}506507MEM_STATIC int ZSTD_cwksp_check_wasteful(ZSTD_cwksp* ws, size_t additionalNeededSpace) {508return ZSTD_cwksp_check_too_large(ws, additionalNeededSpace)509&& ws->workspaceOversizedDuration > ZSTD_WORKSPACETOOLARGE_MAXDURATION;510}511512MEM_STATIC void ZSTD_cwksp_bump_oversized_duration(513ZSTD_cwksp* ws, size_t additionalNeededSpace) {514if (ZSTD_cwksp_check_too_large(ws, additionalNeededSpace)) {515ws->workspaceOversizedDuration++;516} else {517ws->workspaceOversizedDuration = 0;518}519}520521#if defined (__cplusplus)522}523#endif524525#endif /* ZSTD_CWKSP_H */526527528