feat(compress): move trace record construction to Rust

Keep the compression context extraction and the weak trace hook adapter in C,
where the private CCtx layout and platform-specific tracing declarations remain
available. Project only the scalar trace inputs and callback through an explicit
ABI-checked boundary so Rust owns the enabled check, stable record layout,
version field, compression-only null fields, and end-of-trace policy. The C
entry point still clears traceCtx after Rust returns, preserving the old
single-use lifecycle and keeping trace-disabled builds free of weak-hook calls.

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:
2026-07-21 19:09:02 +02:00
parent 42e6662a6d
commit 8c20f95010
2 changed files with 163 additions and 14 deletions
+63 -14
View File
@@ -7284,23 +7284,72 @@ size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel)
}
typedef void (*ZSTD_rust_traceEnd_f)(
void* context, unsigned long long traceCtx, const void* trace);
typedef struct {
void* callbackContext;
unsigned long long traceCtx;
int streaming;
unsigned dictionaryID;
size_t dictionarySize;
size_t uncompressedSize;
size_t compressedSize;
const void* params;
const void* cctx;
ZSTD_rust_traceEnd_f traceEnd;
} ZSTD_rust_traceEndState;
typedef char ZSTD_rust_trace_end_state_layout[
(offsetof(ZSTD_rust_traceEndState, callbackContext) == 0
&& offsetof(ZSTD_rust_traceEndState, traceCtx) == sizeof(void*)
&& offsetof(ZSTD_rust_traceEndState, streaming)
== sizeof(void*) + sizeof(unsigned long long)
&& offsetof(ZSTD_rust_traceEndState, dictionaryID)
== sizeof(void*) + sizeof(unsigned long long) + sizeof(int)
&& offsetof(ZSTD_rust_traceEndState, dictionarySize)
== sizeof(void*) + sizeof(unsigned long long) + 2 * sizeof(int)
&& offsetof(ZSTD_rust_traceEndState, uncompressedSize)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + sizeof(size_t)
&& offsetof(ZSTD_rust_traceEndState, compressedSize)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + 2 * sizeof(size_t)
&& offsetof(ZSTD_rust_traceEndState, params)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + 3 * sizeof(size_t)
&& offsetof(ZSTD_rust_traceEndState, cctx)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + 4 * sizeof(size_t)
&& offsetof(ZSTD_rust_traceEndState, traceEnd)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + 5 * sizeof(size_t)
&& sizeof(ZSTD_rust_traceEndState)
== offsetof(ZSTD_rust_traceEndState, dictionarySize) + 6 * sizeof(size_t))
? 1 : -1];
void ZSTD_rust_traceEnd(const ZSTD_rust_traceEndState* state);
#if ZSTD_TRACE
static void ZSTD_rust_traceEnd_callback(void* context,
unsigned long long traceCtx, const void* trace)
{
(void)context;
if (ZSTD_trace_compress_end != NULL) {
ZSTD_trace_compress_end((ZSTD_TraceCtx)traceCtx,
(ZSTD_Trace const*)trace);
}
}
#endif
void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize)
{
#if ZSTD_TRACE
if (cctx->traceCtx && ZSTD_trace_compress_end != NULL) {
int const streaming = cctx->inBuffSize > 0 || cctx->outBuffSize > 0 || cctx->appliedParams.nbWorkers > 0;
ZSTD_Trace trace;
ZSTD_memset(&trace, 0, sizeof(trace));
trace.version = ZSTD_VERSION_NUMBER;
trace.streaming = streaming;
trace.dictionaryID = cctx->dictID;
trace.dictionarySize = cctx->dictContentSize;
trace.uncompressedSize = cctx->consumedSrcSize;
trace.compressedSize = cctx->producedCSize + extraCSize;
trace.params = &cctx->appliedParams;
trace.cctx = cctx;
ZSTD_trace_compress_end(cctx->traceCtx, &trace);
}
ZSTD_rust_traceEndState state;
state.callbackContext = cctx;
state.traceCtx = cctx->traceCtx;
state.streaming = cctx->inBuffSize > 0 || cctx->outBuffSize > 0
|| cctx->appliedParams.nbWorkers > 0;
state.dictionaryID = cctx->dictID;
state.dictionarySize = cctx->dictContentSize;
state.uncompressedSize = cctx->consumedSrcSize;
state.compressedSize = cctx->producedCSize + extraCSize;
state.params = &cctx->appliedParams;
state.cctx = cctx;
state.traceEnd = ZSTD_rust_traceEnd_callback;
ZSTD_rust_traceEnd(&state);
cctx->traceCtx = 0;
#else
(void)cctx;
+100
View File
@@ -57,6 +57,7 @@ use crate::zstd_compress_stats::{
ZSTD_rust_validateSeqStore, ZSTD_LLT_LITERAL_LENGTH, ZSTD_LLT_MATCH_LENGTH,
};
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
use crate::zstd_common::ZSTD_VERSION_NUMBER;
use std::ffi::c_void;
use std::mem::{offset_of, size_of, MaybeUninit};
use std::os::raw::{c_int, c_longlong, c_uint};
@@ -2796,6 +2797,105 @@ pub unsafe extern "C" fn ZSTD_rust_compressContinue(
type CompressEndTraceFn = unsafe extern "C" fn(*mut c_void, usize);
type CompressTraceEndFn = unsafe extern "C" fn(*mut c_void, u64, *const c_void);
/// ABI-compatible compression trace record. The weak trace hook remains a C
/// callback, but Rust owns construction of the record and its enabled/reset
/// policy.
#[repr(C)]
struct ZSTD_Trace {
version: c_uint,
streaming: c_int,
dictionary_id: c_uint,
dictionary_is_cold: c_int,
dictionary_size: usize,
uncompressed_size: usize,
compressed_size: usize,
params: *const c_void,
cctx: *const c_void,
dctx: *const c_void,
}
const _: () = {
assert!(offset_of!(ZSTD_Trace, version) == 0);
assert!(offset_of!(ZSTD_Trace, streaming) == size_of::<c_uint>());
assert!(offset_of!(ZSTD_Trace, dictionary_id) == size_of::<c_uint>() + size_of::<c_int>());
assert!(
offset_of!(ZSTD_Trace, dictionary_is_cold)
== 2 * size_of::<c_uint>() + size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_Trace, dictionary_size)
== 2 * size_of::<c_uint>() + 2 * size_of::<c_int>()
);
assert!(offset_of!(ZSTD_Trace, uncompressed_size) == offset_of!(ZSTD_Trace, dictionary_size) + size_of::<usize>());
assert!(offset_of!(ZSTD_Trace, compressed_size) == offset_of!(ZSTD_Trace, dictionary_size) + 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_Trace, params) == offset_of!(ZSTD_Trace, dictionary_size) + 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_Trace, cctx) == offset_of!(ZSTD_Trace, dictionary_size) + 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_Trace, dctx) == offset_of!(ZSTD_Trace, dictionary_size) + 5 * size_of::<usize>());
assert!(size_of::<ZSTD_Trace>() == offset_of!(ZSTD_Trace, dictionary_size) + 6 * size_of::<usize>());
};
/// Inputs for Rust-owned compression trace construction.
#[repr(C)]
pub struct ZSTD_rust_traceEndState {
callback_context: *mut c_void,
trace_ctx: u64,
streaming: c_int,
dictionary_id: c_uint,
dictionary_size: usize,
uncompressed_size: usize,
compressed_size: usize,
params: *const c_void,
cctx: *const c_void,
trace_end: CompressTraceEndFn,
}
const _: () = {
assert!(size_of::<CompressTraceEndFn>() == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_traceEndState, trace_ctx) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, streaming) == size_of::<usize>() + size_of::<u64>());
assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_id) == size_of::<usize>() + size_of::<u64>() + size_of::<c_int>());
assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_size) == size_of::<usize>() + size_of::<u64>() + 2 * size_of::<c_int>());
assert!(offset_of!(ZSTD_rust_traceEndState, uncompressed_size) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, compressed_size) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, params) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, cctx) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_traceEndState, trace_end) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 5 * size_of::<usize>());
assert!(size_of::<ZSTD_rust_traceEndState>() == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 6 * size_of::<usize>());
};
/// Construct and publish a compression trace record, if tracing is enabled.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_traceEnd(state: *const ZSTD_rust_traceEndState) {
let Some(state) = (unsafe { state.as_ref() }) else {
return;
};
if state.trace_ctx == 0 {
return;
}
let trace = ZSTD_Trace {
version: ZSTD_VERSION_NUMBER as c_uint,
streaming: state.streaming,
dictionary_id: state.dictionary_id,
dictionary_is_cold: 0,
dictionary_size: state.dictionary_size,
uncompressed_size: state.uncompressed_size,
compressed_size: state.compressed_size,
params: state.params,
cctx: state.cctx,
dctx: ptr::null(),
};
unsafe {
(state.trace_end)(
state.callback_context,
state.trace_ctx,
(&trace as *const ZSTD_Trace).cast(),
);
}
}
/// Explicit projection for the public end-of-frame orchestration.
///
/// Rust owns callback ordering, output offset/capacity accounting, and