feat(compress): move workspace lifecycle orchestration to Rust
Route private workspace init, dynamic allocation, release, and move ordering through a Rust lifecycle bridge. C continues to own the opaque workspace layout, sanitizer-sensitive byte operations, custom allocator callbacks, and consistency assertions; Rust owns publication order, allocation-before-initialization, metadata-zero-before-release, and source invalidation. The checked projection keeps the field-slot and callback ABI explicit and adds focused lifecycle tests for successful and failed allocation, free ordering, and move semantics. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --tests - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1 - ulimit -v 41943040; make -j1 -C tests test
This commit is contained in:
+155
-22
@@ -168,6 +168,75 @@ typedef struct {
|
|||||||
ZSTD_cwksp_static_alloc_e isStatic;
|
ZSTD_cwksp_static_alloc_e isStatic;
|
||||||
} ZSTD_cwksp;
|
} ZSTD_cwksp;
|
||||||
|
|
||||||
|
/* The workspace layout remains private to C. Rust receives field slots for
|
||||||
|
* initialization and opaque C callbacks for byte-level operations, allocator
|
||||||
|
* ownership, and sanitizer bookkeeping. */
|
||||||
|
typedef void* (*ZSTD_rust_cwksp_allocate_f)(void* context, size_t size);
|
||||||
|
typedef void (*ZSTD_rust_cwksp_release_f)(
|
||||||
|
void* context, void* workspace, size_t workspaceSize);
|
||||||
|
typedef void (*ZSTD_rust_cwksp_callback_f)(void* context);
|
||||||
|
typedef void (*ZSTD_rust_cwksp_copy_f)(void* destination, const void* source);
|
||||||
|
typedef struct {
|
||||||
|
void* callbackContext;
|
||||||
|
void** workspace;
|
||||||
|
void** workspaceEnd;
|
||||||
|
void** objectEnd;
|
||||||
|
void** tableEnd;
|
||||||
|
void** tableValidEnd;
|
||||||
|
void** allocStart;
|
||||||
|
void** initOnceStart;
|
||||||
|
BYTE* allocFailed;
|
||||||
|
int* workspaceOversizedDuration;
|
||||||
|
int* phase;
|
||||||
|
int* isStatic;
|
||||||
|
void* allocatorContext;
|
||||||
|
ZSTD_rust_cwksp_allocate_f allocate;
|
||||||
|
void* releaseContext;
|
||||||
|
ZSTD_rust_cwksp_release_f release;
|
||||||
|
ZSTD_rust_cwksp_callback_f clear;
|
||||||
|
ZSTD_rust_cwksp_callback_f zero;
|
||||||
|
} ZSTD_rust_cwkspState;
|
||||||
|
typedef char ZSTD_rust_cwksp_enum_layout[
|
||||||
|
(sizeof(ZSTD_cwksp_alloc_phase_e) == sizeof(int)
|
||||||
|
&& sizeof(ZSTD_cwksp_static_alloc_e) == sizeof(int))
|
||||||
|
? 1 : -1];
|
||||||
|
typedef char ZSTD_rust_cwksp_constants_layout[
|
||||||
|
(ZSTD_CWKSP_ALIGNMENT_BYTES == 64) ? 1 : -1];
|
||||||
|
typedef char ZSTD_rust_cwksp_state_layout[
|
||||||
|
(offsetof(ZSTD_rust_cwkspState, callbackContext) == 0
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, workspace) == sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, workspaceEnd) == 2 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, objectEnd) == 3 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, tableEnd) == 4 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, tableValidEnd) == 5 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, allocStart) == 6 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, initOnceStart) == 7 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, allocFailed) == 8 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, workspaceOversizedDuration)
|
||||||
|
== 9 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, phase) == 10 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, isStatic) == 11 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, allocatorContext)
|
||||||
|
== 12 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, allocate) == 13 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, releaseContext)
|
||||||
|
== 14 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, release) == 15 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, clear) == 16 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTD_rust_cwkspState, zero) == 17 * sizeof(void*)
|
||||||
|
&& sizeof(ZSTD_rust_cwkspState) == 18 * sizeof(void*))
|
||||||
|
? 1 : -1];
|
||||||
|
size_t ZSTD_rust_cwkspInit(
|
||||||
|
const ZSTD_rust_cwkspState* state,
|
||||||
|
void* start, size_t size, int isStatic);
|
||||||
|
size_t ZSTD_rust_cwkspCreate(
|
||||||
|
const ZSTD_rust_cwkspState* state, size_t size);
|
||||||
|
void ZSTD_rust_cwkspFree(const ZSTD_rust_cwkspState* state);
|
||||||
|
void ZSTD_rust_cwkspMove(
|
||||||
|
void* destination, void* source,
|
||||||
|
ZSTD_rust_cwksp_copy_f copy,
|
||||||
|
ZSTD_rust_cwksp_callback_f zero);
|
||||||
|
|
||||||
/*-*************************************
|
/*-*************************************
|
||||||
* Functions
|
* Functions
|
||||||
***************************************/
|
***************************************/
|
||||||
@@ -657,6 +726,68 @@ MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) {
|
|||||||
ZSTD_cwksp_assert_internal_consistency(ws);
|
ZSTD_cwksp_assert_internal_consistency(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MEM_STATIC void ZSTD_cwksp_rust_clear(void* context)
|
||||||
|
{
|
||||||
|
ZSTD_cwksp_clear((ZSTD_cwksp*)context);
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_STATIC void ZSTD_cwksp_rust_zero(void* context)
|
||||||
|
{
|
||||||
|
ZSTD_memset(context, 0, sizeof(ZSTD_cwksp));
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_STATIC void ZSTD_cwksp_rust_copy(void* destination, const void* source)
|
||||||
|
{
|
||||||
|
ZSTD_memcpy(destination, source, sizeof(ZSTD_cwksp));
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_STATIC void* ZSTD_cwksp_rust_allocate(void* context, size_t size)
|
||||||
|
{
|
||||||
|
void* const workspace = ZSTD_customMalloc(
|
||||||
|
size, *(const ZSTD_customMem*)context);
|
||||||
|
if (workspace != NULL) {
|
||||||
|
assert(((size_t)workspace & (sizeof(void*) - 1)) == 0);
|
||||||
|
}
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_STATIC void ZSTD_cwksp_rust_release(
|
||||||
|
void* context, void* workspace, size_t workspaceSize)
|
||||||
|
{
|
||||||
|
ZSTD_customMem const customMem = *(const ZSTD_customMem*)context;
|
||||||
|
(void)workspaceSize;
|
||||||
|
#if ZSTD_MEMORY_SANITIZER && !defined(ZSTD_MSAN_DONT_POISON_WORKSPACE)
|
||||||
|
if (workspace != NULL && customMem.customFree != NULL) {
|
||||||
|
__msan_unpoison(workspace, workspaceSize);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ZSTD_customFree(workspace, customMem);
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_STATIC ZSTD_rust_cwkspState ZSTD_cwksp_rust_state(ZSTD_cwksp* ws)
|
||||||
|
{
|
||||||
|
ZSTD_rust_cwkspState state;
|
||||||
|
state.callbackContext = ws;
|
||||||
|
state.workspace = &ws->workspace;
|
||||||
|
state.workspaceEnd = &ws->workspaceEnd;
|
||||||
|
state.objectEnd = &ws->objectEnd;
|
||||||
|
state.tableEnd = &ws->tableEnd;
|
||||||
|
state.tableValidEnd = &ws->tableValidEnd;
|
||||||
|
state.allocStart = &ws->allocStart;
|
||||||
|
state.initOnceStart = &ws->initOnceStart;
|
||||||
|
state.allocFailed = &ws->allocFailed;
|
||||||
|
state.workspaceOversizedDuration = &ws->workspaceOversizedDuration;
|
||||||
|
state.phase = (int*)&ws->phase;
|
||||||
|
state.isStatic = (int*)&ws->isStatic;
|
||||||
|
state.allocatorContext = NULL;
|
||||||
|
state.allocate = NULL;
|
||||||
|
state.releaseContext = NULL;
|
||||||
|
state.release = NULL;
|
||||||
|
state.clear = ZSTD_cwksp_rust_clear;
|
||||||
|
state.zero = ZSTD_cwksp_rust_zero;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) {
|
MEM_STATIC size_t ZSTD_cwksp_sizeof(const ZSTD_cwksp* ws) {
|
||||||
return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace);
|
return (size_t)((BYTE*)ws->workspaceEnd - (BYTE*)ws->workspace);
|
||||||
}
|
}
|
||||||
@@ -674,36 +805,38 @@ MEM_STATIC size_t ZSTD_cwksp_used(const ZSTD_cwksp* ws) {
|
|||||||
MEM_STATIC void ZSTD_cwksp_init(ZSTD_cwksp* ws, void* start, size_t size, ZSTD_cwksp_static_alloc_e isStatic) {
|
MEM_STATIC void ZSTD_cwksp_init(ZSTD_cwksp* ws, void* start, size_t size, ZSTD_cwksp_static_alloc_e isStatic) {
|
||||||
DEBUGLOG(4, "cwksp: init'ing workspace with %zd bytes", size);
|
DEBUGLOG(4, "cwksp: init'ing workspace with %zd bytes", size);
|
||||||
assert(((size_t)start & (sizeof(void*)-1)) == 0); /* ensure correct alignment */
|
assert(((size_t)start & (sizeof(void*)-1)) == 0); /* ensure correct alignment */
|
||||||
ws->workspace = start;
|
{ ZSTD_rust_cwkspState const state = ZSTD_cwksp_rust_state(ws);
|
||||||
ws->workspaceEnd = (BYTE*)start + size;
|
size_t const result = ZSTD_rust_cwkspInit(
|
||||||
ws->objectEnd = ws->workspace;
|
&state, start, size, (int)isStatic);
|
||||||
ws->tableValidEnd = ws->objectEnd;
|
assert(!ZSTD_isError(result));
|
||||||
ws->initOnceStart = ZSTD_cwksp_initialAllocStart(ws);
|
(void)result;
|
||||||
ws->phase = ZSTD_cwksp_alloc_objects;
|
}
|
||||||
ws->isStatic = isStatic;
|
|
||||||
ZSTD_cwksp_clear(ws);
|
|
||||||
ws->workspaceOversizedDuration = 0;
|
|
||||||
ZSTD_cwksp_assert_internal_consistency(ws);
|
ZSTD_cwksp_assert_internal_consistency(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
MEM_STATIC size_t ZSTD_cwksp_create(ZSTD_cwksp* ws, size_t size, ZSTD_customMem customMem) {
|
MEM_STATIC size_t ZSTD_cwksp_create(ZSTD_cwksp* ws, size_t size, ZSTD_customMem customMem) {
|
||||||
void* workspace = ZSTD_customMalloc(size, customMem);
|
|
||||||
DEBUGLOG(4, "cwksp: creating new workspace with %zd bytes", size);
|
DEBUGLOG(4, "cwksp: creating new workspace with %zd bytes", size);
|
||||||
RETURN_ERROR_IF(workspace == NULL, memory_allocation, "NULL pointer!");
|
{ ZSTD_rust_cwkspState state = ZSTD_cwksp_rust_state(ws);
|
||||||
ZSTD_cwksp_init(ws, workspace, size, ZSTD_cwksp_dynamic_alloc);
|
state.allocatorContext = &customMem;
|
||||||
return 0;
|
state.allocate = ZSTD_cwksp_rust_allocate;
|
||||||
|
state.releaseContext = &customMem;
|
||||||
|
state.release = ZSTD_cwksp_rust_release;
|
||||||
|
{ size_t const result = ZSTD_rust_cwkspCreate(&state, size);
|
||||||
|
if (!ZSTD_isError(result)) {
|
||||||
|
ZSTD_cwksp_assert_internal_consistency(ws);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) {
|
MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) {
|
||||||
void *ptr = ws->workspace;
|
|
||||||
DEBUGLOG(4, "cwksp: freeing workspace");
|
DEBUGLOG(4, "cwksp: freeing workspace");
|
||||||
#if ZSTD_MEMORY_SANITIZER && !defined(ZSTD_MSAN_DONT_POISON_WORKSPACE)
|
{ ZSTD_rust_cwkspState state = ZSTD_cwksp_rust_state(ws);
|
||||||
if (ptr != NULL && customMem.customFree != NULL) {
|
state.releaseContext = &customMem;
|
||||||
__msan_unpoison(ptr, ZSTD_cwksp_sizeof(ws));
|
state.release = ZSTD_cwksp_rust_release;
|
||||||
|
ZSTD_rust_cwkspFree(&state);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
ZSTD_memset(ws, 0, sizeof(ZSTD_cwksp));
|
|
||||||
ZSTD_customFree(ptr, customMem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -711,8 +844,8 @@ MEM_STATIC void ZSTD_cwksp_free(ZSTD_cwksp* ws, ZSTD_customMem customMem) {
|
|||||||
* is left in an invalid state (src must be re-init()'ed before it's used again).
|
* is left in an invalid state (src must be re-init()'ed before it's used again).
|
||||||
*/
|
*/
|
||||||
MEM_STATIC void ZSTD_cwksp_move(ZSTD_cwksp* dst, ZSTD_cwksp* src) {
|
MEM_STATIC void ZSTD_cwksp_move(ZSTD_cwksp* dst, ZSTD_cwksp* src) {
|
||||||
*dst = *src;
|
ZSTD_rust_cwkspMove(
|
||||||
ZSTD_memset(src, 0, sizeof(ZSTD_cwksp));
|
dst, src, ZSTD_cwksp_rust_copy, ZSTD_cwksp_rust_zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) {
|
MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) {
|
||||||
|
|||||||
@@ -3247,6 +3247,214 @@ pub unsafe extern "C" fn ZSTD_rust_refThreadPool(
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CwkspAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
||||||
|
type CwkspReleaseFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize);
|
||||||
|
type CwkspCallbackFn = unsafe extern "C" fn(*mut c_void);
|
||||||
|
type CwkspCopyFn = unsafe extern "C" fn(*mut c_void, *const c_void);
|
||||||
|
|
||||||
|
const ZSTD_CWKSP_ALLOC_OBJECTS: c_int = 0;
|
||||||
|
const ZSTD_CWKSP_DYNAMIC_ALLOC: c_int = 0;
|
||||||
|
const ZSTD_CWKSP_ALIGNMENT_BYTES: usize = 64;
|
||||||
|
|
||||||
|
/// C-owned field slots and callbacks for the private compression workspace.
|
||||||
|
///
|
||||||
|
/// Rust owns the lifecycle ordering and state publication. C retains the
|
||||||
|
/// `ZSTD_cwksp` layout, sanitizer-sensitive clear/zero/copy operations, and
|
||||||
|
/// custom allocator implementation behind these callbacks. All fields are
|
||||||
|
/// pointer-sized on the C side; nullable function pointers are represented as
|
||||||
|
/// `Option` so reading the C-side NULL slots is well-defined in Rust.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ZSTD_rust_cwkspState {
|
||||||
|
callback_context: *mut c_void,
|
||||||
|
workspace: *mut *mut c_void,
|
||||||
|
workspace_end: *mut *mut c_void,
|
||||||
|
object_end: *mut *mut c_void,
|
||||||
|
table_end: *mut *mut c_void,
|
||||||
|
table_valid_end: *mut *mut c_void,
|
||||||
|
alloc_start: *mut *mut c_void,
|
||||||
|
init_once_start: *mut *mut c_void,
|
||||||
|
alloc_failed: *mut u8,
|
||||||
|
workspace_oversized_duration: *mut c_int,
|
||||||
|
phase: *mut c_int,
|
||||||
|
is_static: *mut c_int,
|
||||||
|
allocator_context: *mut c_void,
|
||||||
|
allocate: Option<CwkspAllocateFn>,
|
||||||
|
release_context: *mut c_void,
|
||||||
|
release: Option<CwkspReleaseFn>,
|
||||||
|
clear: Option<CwkspCallbackFn>,
|
||||||
|
zero: Option<CwkspCallbackFn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
assert!(size_of::<CwkspAllocateFn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<CwkspReleaseFn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<CwkspCallbackFn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<CwkspCopyFn>() == size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, callback_context) == 0);
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, workspace) == size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, workspace_end) == 2 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, object_end) == 3 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, table_end) == 4 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, table_valid_end) == 5 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, alloc_start) == 6 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, init_once_start) == 7 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, alloc_failed) == 8 * size_of::<usize>());
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTD_rust_cwkspState, workspace_oversized_duration)
|
||||||
|
== 9 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, phase) == 10 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, is_static) == 11 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, allocator_context) == 12 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, allocate) == 13 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, release_context) == 14 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, release) == 15 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, clear) == 16 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTD_rust_cwkspState, zero) == 17 * size_of::<usize>());
|
||||||
|
assert!(size_of::<ZSTD_rust_cwkspState>() == 18 * size_of::<usize>());
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cwksp_initial_alloc_start(workspace_end: *mut c_void) -> *mut c_void {
|
||||||
|
((workspace_end as usize) & !(ZSTD_CWKSP_ALIGNMENT_BYTES - 1)) as *mut c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cwksp_state_has_storage(state: &ZSTD_rust_cwkspState) -> bool {
|
||||||
|
!state.callback_context.is_null()
|
||||||
|
&& !state.workspace.is_null()
|
||||||
|
&& !state.workspace_end.is_null()
|
||||||
|
&& !state.object_end.is_null()
|
||||||
|
&& !state.table_end.is_null()
|
||||||
|
&& !state.table_valid_end.is_null()
|
||||||
|
&& !state.alloc_start.is_null()
|
||||||
|
&& !state.init_once_start.is_null()
|
||||||
|
&& !state.alloc_failed.is_null()
|
||||||
|
&& !state.workspace_oversized_duration.is_null()
|
||||||
|
&& !state.phase.is_null()
|
||||||
|
&& !state.is_static.is_null()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn cwksp_init_state(
|
||||||
|
state: &ZSTD_rust_cwkspState,
|
||||||
|
start: *mut c_void,
|
||||||
|
size: usize,
|
||||||
|
is_static: c_int,
|
||||||
|
clear: CwkspCallbackFn,
|
||||||
|
) {
|
||||||
|
let workspace_end = unsafe { start.cast::<u8>().add(size).cast::<c_void>() };
|
||||||
|
unsafe {
|
||||||
|
*state.workspace = start;
|
||||||
|
*state.workspace_end = workspace_end;
|
||||||
|
*state.object_end = start;
|
||||||
|
*state.table_valid_end = start;
|
||||||
|
*state.init_once_start = cwksp_initial_alloc_start(workspace_end);
|
||||||
|
*state.phase = ZSTD_CWKSP_ALLOC_OBJECTS;
|
||||||
|
*state.is_static = is_static;
|
||||||
|
clear(state.callback_context);
|
||||||
|
*state.workspace_oversized_duration = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the private C workspace in the original field-publication order.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTD_rust_cwkspInit(
|
||||||
|
state: *const ZSTD_rust_cwkspState,
|
||||||
|
start: *mut c_void,
|
||||||
|
size: usize,
|
||||||
|
is_static: c_int,
|
||||||
|
) -> usize {
|
||||||
|
let Some(state) = (unsafe { state.as_ref() }) else {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
};
|
||||||
|
let Some(clear) = state.clear else {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
};
|
||||||
|
if !cwksp_state_has_storage(state) {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
}
|
||||||
|
unsafe { cwksp_init_state(state, start, size, is_static, clear) };
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate and initialize a dynamic private C workspace through its custom
|
||||||
|
/// allocator callback. Allocation happens before any workspace field changes.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTD_rust_cwkspCreate(
|
||||||
|
state: *const ZSTD_rust_cwkspState,
|
||||||
|
size: usize,
|
||||||
|
) -> usize {
|
||||||
|
let Some(state) = (unsafe { state.as_ref() }) else {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
};
|
||||||
|
let Some(allocate) = state.allocate else {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
};
|
||||||
|
let Some(clear) = state.clear else {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
};
|
||||||
|
if !cwksp_state_has_storage(state) || state.allocator_context.is_null() {
|
||||||
|
return ERROR(ZstdErrorCode::Generic);
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace = unsafe { allocate(state.allocator_context, size) };
|
||||||
|
if workspace.is_null() {
|
||||||
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
||||||
|
}
|
||||||
|
unsafe { cwksp_init_state(state, workspace, size, ZSTD_CWKSP_DYNAMIC_ALLOC, clear) };
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the C workspace metadata and release its owned allocation in order.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTD_rust_cwkspFree(state: *const ZSTD_rust_cwkspState) {
|
||||||
|
let Some(state) = (unsafe { state.as_ref() }) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(zero) = state.zero else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(release) = state.release else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if state.callback_context.is_null()
|
||||||
|
|| state.workspace.is_null()
|
||||||
|
|| state.workspace_end.is_null()
|
||||||
|
|| state.release_context.is_null()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace = unsafe { *state.workspace };
|
||||||
|
let workspace_size = if workspace.is_null() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let workspace_end = unsafe { *state.workspace_end };
|
||||||
|
(workspace_end as usize).wrapping_sub(workspace as usize)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
zero(state.callback_context);
|
||||||
|
release(state.release_context, workspace, workspace_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move workspace metadata and leave the source in a zeroed invalid state.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTD_rust_cwkspMove(
|
||||||
|
destination: *mut c_void,
|
||||||
|
source: *mut c_void,
|
||||||
|
copy: CwkspCopyFn,
|
||||||
|
zero: CwkspCallbackFn,
|
||||||
|
) {
|
||||||
|
if destination.is_null() || source.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
copy(destination, source);
|
||||||
|
zero(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type InitCCtxCallbackFn = unsafe extern "C" fn(*mut c_void);
|
type InitCCtxCallbackFn = unsafe extern "C" fn(*mut c_void);
|
||||||
type InitCCtxSetCustomMemFn = unsafe extern "C" fn(*mut c_void, *const c_void);
|
type InitCCtxSetCustomMemFn = unsafe extern "C" fn(*mut c_void, *const c_void);
|
||||||
type InitCCtxResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
type InitCCtxResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
||||||
@@ -11897,6 +12105,202 @@ mod tests {
|
|||||||
const ZSTD_BTULTRA2: c_int = 9;
|
const ZSTD_BTULTRA2: c_int = 9;
|
||||||
const ZSTD_F_ZSTD1_MAGICLESS: c_int = 1;
|
const ZSTD_F_ZSTD1_MAGICLESS: c_int = 1;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CwkspTestContext {
|
||||||
|
events: Vec<&'static str>,
|
||||||
|
allocation: *mut c_void,
|
||||||
|
requested_size: usize,
|
||||||
|
released_workspace: *mut c_void,
|
||||||
|
released_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CwkspTestSlots {
|
||||||
|
workspace: *mut c_void,
|
||||||
|
workspace_end: *mut c_void,
|
||||||
|
object_end: *mut c_void,
|
||||||
|
table_end: *mut c_void,
|
||||||
|
table_valid_end: *mut c_void,
|
||||||
|
alloc_start: *mut c_void,
|
||||||
|
init_once_start: *mut c_void,
|
||||||
|
alloc_failed: u8,
|
||||||
|
workspace_oversized_duration: c_int,
|
||||||
|
phase: c_int,
|
||||||
|
is_static: c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_test_clear(context: *mut c_void) {
|
||||||
|
let context = unsafe { &mut *context.cast::<CwkspTestContext>() };
|
||||||
|
context.events.push("clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_test_allocate(context: *mut c_void, size: usize) -> *mut c_void {
|
||||||
|
let context = unsafe { &mut *context.cast::<CwkspTestContext>() };
|
||||||
|
context.events.push("allocate");
|
||||||
|
context.requested_size = size;
|
||||||
|
context.allocation
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_test_zero(context: *mut c_void) {
|
||||||
|
let context = unsafe { &mut *context.cast::<CwkspTestContext>() };
|
||||||
|
context.events.push("zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_test_release(
|
||||||
|
context: *mut c_void,
|
||||||
|
workspace: *mut c_void,
|
||||||
|
workspace_size: usize,
|
||||||
|
) {
|
||||||
|
let context = unsafe { &mut *context.cast::<CwkspTestContext>() };
|
||||||
|
context.events.push("release");
|
||||||
|
context.released_workspace = workspace;
|
||||||
|
context.released_size = workspace_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cwksp_test_state(
|
||||||
|
context: &mut CwkspTestContext,
|
||||||
|
slots: &mut CwkspTestSlots,
|
||||||
|
) -> ZSTD_rust_cwkspState {
|
||||||
|
let context = (context as *mut CwkspTestContext).cast();
|
||||||
|
ZSTD_rust_cwkspState {
|
||||||
|
callback_context: context,
|
||||||
|
workspace: &mut slots.workspace,
|
||||||
|
workspace_end: &mut slots.workspace_end,
|
||||||
|
object_end: &mut slots.object_end,
|
||||||
|
table_end: &mut slots.table_end,
|
||||||
|
table_valid_end: &mut slots.table_valid_end,
|
||||||
|
alloc_start: &mut slots.alloc_start,
|
||||||
|
init_once_start: &mut slots.init_once_start,
|
||||||
|
alloc_failed: &mut slots.alloc_failed,
|
||||||
|
workspace_oversized_duration: &mut slots.workspace_oversized_duration,
|
||||||
|
phase: &mut slots.phase,
|
||||||
|
is_static: &mut slots.is_static,
|
||||||
|
allocator_context: context,
|
||||||
|
allocate: Some(cwksp_test_allocate),
|
||||||
|
release_context: context,
|
||||||
|
release: Some(cwksp_test_release),
|
||||||
|
clear: Some(cwksp_test_clear),
|
||||||
|
zero: Some(cwksp_test_zero),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cwksp_init_publishes_bounds_before_clear_and_resets_duration_afterward() {
|
||||||
|
let mut context = CwkspTestContext::default();
|
||||||
|
let mut slots = CwkspTestSlots::default();
|
||||||
|
let mut backing = [0usize; 32];
|
||||||
|
let start = backing.as_mut_ptr().cast::<c_void>();
|
||||||
|
let size = backing.len() * size_of::<usize>();
|
||||||
|
let state = cwksp_test_state(&mut context, &mut slots);
|
||||||
|
|
||||||
|
let result = unsafe { ZSTD_rust_cwkspInit(&state, start, size, 1) };
|
||||||
|
|
||||||
|
let workspace_end = unsafe { start.cast::<u8>().add(size).cast::<c_void>() };
|
||||||
|
let initial_alloc_start =
|
||||||
|
((workspace_end as usize) & !(ZSTD_CWKSP_ALIGNMENT_BYTES - 1)) as *mut c_void;
|
||||||
|
assert_eq!(result, 0);
|
||||||
|
assert_eq!(context.events, ["clear"]);
|
||||||
|
assert_eq!(slots.workspace, start);
|
||||||
|
assert_eq!(slots.workspace_end, workspace_end);
|
||||||
|
assert_eq!(slots.object_end, start);
|
||||||
|
assert_eq!(slots.table_valid_end, start);
|
||||||
|
assert_eq!(slots.init_once_start, initial_alloc_start);
|
||||||
|
assert_eq!(slots.phase, ZSTD_CWKSP_ALLOC_OBJECTS);
|
||||||
|
assert_eq!(slots.is_static, 1);
|
||||||
|
assert_eq!(slots.workspace_oversized_duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cwksp_create_allocates_before_initializing_and_preserves_failure_order() {
|
||||||
|
let mut backing = [0usize; 16];
|
||||||
|
let allocation = backing.as_mut_ptr().cast::<c_void>();
|
||||||
|
let mut context = CwkspTestContext {
|
||||||
|
allocation,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut slots = CwkspTestSlots::default();
|
||||||
|
let state = cwksp_test_state(&mut context, &mut slots);
|
||||||
|
|
||||||
|
let result = unsafe { ZSTD_rust_cwkspCreate(&state, 1234) };
|
||||||
|
|
||||||
|
assert_eq!(result, 0);
|
||||||
|
assert_eq!(context.events, ["allocate", "clear"]);
|
||||||
|
assert_eq!(context.requested_size, 1234);
|
||||||
|
assert_eq!(slots.workspace, allocation);
|
||||||
|
assert_eq!(slots.is_static, ZSTD_CWKSP_DYNAMIC_ALLOC);
|
||||||
|
|
||||||
|
let mut failure_context = CwkspTestContext::default();
|
||||||
|
let mut failure_slots = CwkspTestSlots::default();
|
||||||
|
let failure_state = cwksp_test_state(&mut failure_context, &mut failure_slots);
|
||||||
|
let result = unsafe { ZSTD_rust_cwkspCreate(&failure_state, 5678) };
|
||||||
|
|
||||||
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
||||||
|
assert_eq!(failure_context.events, ["allocate"]);
|
||||||
|
assert!(failure_slots.workspace.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cwksp_free_zeroes_metadata_before_releasing_the_owned_range() {
|
||||||
|
let mut backing = [0usize; 16];
|
||||||
|
let workspace = backing.as_mut_ptr().cast::<c_void>();
|
||||||
|
let workspace_size = backing.len() * size_of::<usize>();
|
||||||
|
let workspace_end = unsafe { workspace.cast::<u8>().add(workspace_size).cast() };
|
||||||
|
let mut context = CwkspTestContext::default();
|
||||||
|
let mut slots = CwkspTestSlots {
|
||||||
|
workspace,
|
||||||
|
workspace_end,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let state = cwksp_test_state(&mut context, &mut slots);
|
||||||
|
|
||||||
|
unsafe { ZSTD_rust_cwkspFree(&state) };
|
||||||
|
|
||||||
|
assert_eq!(context.events, ["zero", "release"]);
|
||||||
|
assert_eq!(context.released_workspace, workspace);
|
||||||
|
assert_eq!(context.released_size, workspace_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct CwkspMoveTestValue {
|
||||||
|
values: [usize; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_move_test_copy(destination: *mut c_void, source: *const c_void) {
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
source.cast::<u8>(),
|
||||||
|
destination.cast::<u8>(),
|
||||||
|
size_of::<CwkspMoveTestValue>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cwksp_move_test_zero(source: *mut c_void) {
|
||||||
|
unsafe {
|
||||||
|
ptr::write_bytes(source.cast::<u8>(), 0, size_of::<CwkspMoveTestValue>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cwksp_move_copies_metadata_then_invalidates_the_source() {
|
||||||
|
let mut destination = CwkspMoveTestValue { values: [0, 0] };
|
||||||
|
let mut source = CwkspMoveTestValue {
|
||||||
|
values: [0x1234, 0x5678],
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ZSTD_rust_cwkspMove(
|
||||||
|
(&mut destination as *mut CwkspMoveTestValue).cast(),
|
||||||
|
(&mut source as *mut CwkspMoveTestValue).cast(),
|
||||||
|
cwksp_move_test_copy,
|
||||||
|
cwksp_move_test_zero,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(destination.values, [0x1234, 0x5678]);
|
||||||
|
assert_eq!(source.values, [0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
struct EstimateCCtxSizeTestContext {
|
struct EstimateCCtxSizeTestContext {
|
||||||
compression_levels: Vec<c_int>,
|
compression_levels: Vec<c_int>,
|
||||||
source_size_hints: Vec<u64>,
|
source_size_hints: Vec<u64>,
|
||||||
|
|||||||
Reference in New Issue
Block a user