feat(compress): move CDict table copies into Rust
Move the CDict-to-CCtx hash, chain, and row-match table copies into the Rust reset orchestration. The C bridge now exposes table-field addresses and scalar policy inputs while retaining private reset and hashTable3 operations; Rust loads destination pointers only after reset and strips short-cache tags through the existing Rust leaf. The destination chain policy must be read after the reset callback updates appliedParams. Reading it before reset skipped DFAST chain copies and caused the dictionary source-size zstream test to segfault in the double-fast external-dictionary matcher. Focused tests cover tagged hash/chain copies, row-match tag/salt copies, callback ordering, and reset-error short-circuiting. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; make -j1 - ulimit -v 41943040; make -j1 -C tests test-zstream ZSTREAM_TESTTIME=-T2s - ulimit -v 41943040; make -j1 -C tests test-fuzzer FUZZERTEST=-T3s FUZZER_FLAGS=--no-big-tests
This commit is contained in:
@@ -15,10 +15,12 @@ use crate::entropy_common::FSE_readNCount;
|
||||
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
||||
use crate::fse_compress::FSE_buildCTable_wksp;
|
||||
use crate::huf_compress::HUF_readCTable;
|
||||
use crate::zstd_compress::ZSTD_rust_copyCDictTableIntoCCtx;
|
||||
use crate::zstd_compress_params::{
|
||||
ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
|
||||
ZSTD_rust_params_defaultCLevel, ZSTD_rust_params_getCParams,
|
||||
ZSTD_rust_params_getParamsInternal, ZSTD_rust_params_shouldAttachDict,
|
||||
ZSTD_rust_params_allocateChainTable, ZSTD_rust_params_defaultCLevel,
|
||||
ZSTD_rust_params_getCParams, ZSTD_rust_params_getParamsInternal,
|
||||
ZSTD_rust_params_rowMatchFinderUsed, ZSTD_rust_params_shouldAttachDict,
|
||||
ZSTD_CONTENTSIZE_UNKNOWN, ZSTD_RUST_CPM_CREATE_CDICT, ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
||||
};
|
||||
use crate::zstd_compress_params_api::ZSTD_CCtx_params;
|
||||
@@ -1337,9 +1339,9 @@ const _: () = {
|
||||
|
||||
/// Projection for copying a prepared CDict into a working CCtx.
|
||||
///
|
||||
/// Rust owns the reset/copy ordering and stops on reset allocation failure.
|
||||
/// C retains the private CCtx/CDict layout, table-size policy, and table
|
||||
/// pointer arithmetic behind callbacks.
|
||||
/// Rust owns the reset/copy ordering, table-size policy, and table copies, and
|
||||
/// stops on reset allocation failure. C retains the private CCtx/CDict layout
|
||||
/// behind explicit table-field projections and callbacks.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_resetCCtxByCopyingCDictState {
|
||||
callback_context: *mut c_void,
|
||||
@@ -1348,9 +1350,23 @@ pub struct ZSTD_rust_resetCCtxByCopyingCDictState {
|
||||
pledged_src_size: u64,
|
||||
reset: Option<ResetCCtxByCopyingCDictResetFn>,
|
||||
mark_tables_dirty: Option<ResetCCtxByCopyingCDictMarkTablesFn>,
|
||||
copy_tables: Option<ResetCCtxByCopyingCDictStateFn>,
|
||||
zero_hash_table3: Option<ResetCCtxByCopyingCDictStateFn>,
|
||||
mark_tables_clean: Option<ResetCCtxByCopyingCDictMarkTablesFn>,
|
||||
destination_hash_table: *mut *mut c_uint,
|
||||
source_hash_table: *const c_uint,
|
||||
source_hash_log: *const c_uint,
|
||||
destination_chain_table: *mut *mut c_uint,
|
||||
source_chain_table: *const c_uint,
|
||||
source_chain_log: *const c_uint,
|
||||
source_strategy: *const c_int,
|
||||
source_use_row_match_finder: *const c_int,
|
||||
source_indices_tagged: *const c_int,
|
||||
destination_strategy: *const c_int,
|
||||
destination_use_row_match_finder: *const c_int,
|
||||
destination_tag_table: *mut *mut u8,
|
||||
source_tag_table: *const u8,
|
||||
destination_hash_salt: *mut u64,
|
||||
source_hash_salt: *const u64,
|
||||
destination_window: *mut c_void,
|
||||
source_window: *const c_void,
|
||||
destination_next_to_update: *mut c_uint,
|
||||
@@ -1383,7 +1399,7 @@ const _: () = {
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, zbuff)
|
||||
== 3 * size_of::<usize>() + size_of::<u64>() + size_of::<[usize; 17]>()
|
||||
== 3 * size_of::<usize>() + size_of::<u64>() + size_of::<[usize; 31]>()
|
||||
);
|
||||
assert!(
|
||||
size_of::<ZSTD_rust_resetCCtxByCopyingCDictState>()
|
||||
@@ -1394,7 +1410,8 @@ const _: () = {
|
||||
};
|
||||
|
||||
/// Run the private CDict-copy operation through C-owned layout callbacks and
|
||||
/// copy the compressed-block state directly in Rust.
|
||||
/// explicit table-field projections, then copy the compressed-block state
|
||||
/// directly in Rust.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_resetCCtxByCopyingCDict(
|
||||
state: *const ZSTD_rust_resetCCtxByCopyingCDictState,
|
||||
@@ -1403,25 +1420,29 @@ pub unsafe extern "C" fn ZSTD_rust_resetCCtxByCopyingCDict(
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
let state = unsafe { &*state };
|
||||
let (
|
||||
Some(reset),
|
||||
Some(mark_tables_dirty),
|
||||
Some(copy_tables),
|
||||
Some(zero_hash_table3),
|
||||
Some(mark_tables_clean),
|
||||
) = (
|
||||
let (Some(reset), Some(mark_tables_dirty), Some(zero_hash_table3), Some(mark_tables_clean)) = (
|
||||
state.reset,
|
||||
state.mark_tables_dirty,
|
||||
state.copy_tables,
|
||||
state.zero_hash_table3,
|
||||
state.mark_tables_clean,
|
||||
)
|
||||
else {
|
||||
) else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
if state.callback_context.is_null()
|
||||
|| state.cdict.is_null()
|
||||
|| state.params.is_null()
|
||||
|| state.destination_hash_table.is_null()
|
||||
|| state.source_hash_log.is_null()
|
||||
|| state.destination_chain_table.is_null()
|
||||
|| state.source_chain_log.is_null()
|
||||
|| state.source_strategy.is_null()
|
||||
|| state.source_use_row_match_finder.is_null()
|
||||
|| state.source_indices_tagged.is_null()
|
||||
|| state.destination_strategy.is_null()
|
||||
|| state.destination_use_row_match_finder.is_null()
|
||||
|| state.destination_tag_table.is_null()
|
||||
|| state.destination_hash_salt.is_null()
|
||||
|| state.source_hash_salt.is_null()
|
||||
|| state.destination_window.is_null()
|
||||
|| state.source_window.is_null()
|
||||
|| state.destination_next_to_update.is_null()
|
||||
@@ -1438,6 +1459,30 @@ pub unsafe extern "C" fn ZSTD_rust_resetCCtxByCopyingCDict(
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
let source_hash_log = unsafe { *state.source_hash_log };
|
||||
let source_chain_log = unsafe { *state.source_chain_log };
|
||||
let source_strategy = unsafe { *state.source_strategy };
|
||||
let source_use_row_match_finder = unsafe { *state.source_use_row_match_finder };
|
||||
let source_indices_tagged = unsafe { *state.source_indices_tagged };
|
||||
let Some(hash_table_size) = 1usize.checked_shl(source_hash_log) else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
let source_chain_table_size = if ZSTD_rust_params_allocateChainTable(
|
||||
source_strategy,
|
||||
source_use_row_match_finder,
|
||||
0,
|
||||
) != 0
|
||||
{
|
||||
let Some(size) = 1usize.checked_shl(source_chain_log) else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
size
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let row_match_finder_used =
|
||||
ZSTD_rust_params_rowMatchFinderUsed(source_strategy, source_use_row_match_finder) != 0;
|
||||
|
||||
unsafe {
|
||||
let reset_error = reset(
|
||||
state.callback_context,
|
||||
@@ -1449,8 +1494,52 @@ pub unsafe extern "C" fn ZSTD_rust_resetCCtxByCopyingCDict(
|
||||
if ERR_isError(reset_error) {
|
||||
return reset_error;
|
||||
}
|
||||
let destination_strategy = *state.destination_strategy;
|
||||
let destination_use_row_match_finder = *state.destination_use_row_match_finder;
|
||||
let destination_chain_table_size = if ZSTD_rust_params_allocateChainTable(
|
||||
destination_strategy,
|
||||
destination_use_row_match_finder,
|
||||
0,
|
||||
) != 0
|
||||
{
|
||||
source_chain_table_size
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let destination_hash_table = *state.destination_hash_table;
|
||||
let destination_chain_table = *state.destination_chain_table;
|
||||
let destination_tag_table = *state.destination_tag_table;
|
||||
if (destination_hash_table.is_null() || state.source_hash_table.is_null())
|
||||
|| (destination_chain_table_size != 0
|
||||
&& (destination_chain_table.is_null() || state.source_chain_table.is_null()))
|
||||
|| (row_match_finder_used
|
||||
&& (destination_tag_table.is_null() || state.source_tag_table.is_null()))
|
||||
{
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
mark_tables_dirty(state.callback_context);
|
||||
copy_tables(state.callback_context, state.cdict);
|
||||
ZSTD_rust_copyCDictTableIntoCCtx(
|
||||
destination_hash_table,
|
||||
state.source_hash_table,
|
||||
hash_table_size,
|
||||
source_indices_tagged,
|
||||
);
|
||||
if destination_chain_table_size != 0 {
|
||||
ZSTD_rust_copyCDictTableIntoCCtx(
|
||||
destination_chain_table,
|
||||
state.source_chain_table,
|
||||
destination_chain_table_size,
|
||||
source_indices_tagged,
|
||||
);
|
||||
}
|
||||
if row_match_finder_used {
|
||||
ptr::copy_nonoverlapping(
|
||||
state.source_tag_table,
|
||||
destination_tag_table,
|
||||
hash_table_size,
|
||||
);
|
||||
*state.destination_hash_salt = *state.source_hash_salt;
|
||||
}
|
||||
zero_hash_table3(state.callback_context, state.cdict);
|
||||
mark_tables_clean(state.callback_context);
|
||||
ptr::copy(
|
||||
@@ -2768,6 +2857,24 @@ mod tests {
|
||||
params: *const c_void,
|
||||
pledged_src_size: u64,
|
||||
zbuff: c_int,
|
||||
source_hash_table: [c_uint; 4],
|
||||
destination_hash_table: [c_uint; 4],
|
||||
destination_hash_table_slot: *mut c_uint,
|
||||
source_chain_table: [c_uint; 4],
|
||||
destination_chain_table: [c_uint; 4],
|
||||
destination_chain_table_slot: *mut c_uint,
|
||||
source_tag_table: [u8; 4],
|
||||
destination_tag_table: [u8; 4],
|
||||
destination_tag_table_slot: *mut u8,
|
||||
source_hash_salt: u64,
|
||||
destination_hash_salt: u64,
|
||||
source_hash_log: c_uint,
|
||||
source_chain_log: c_uint,
|
||||
source_strategy: c_int,
|
||||
source_use_row_match_finder: c_int,
|
||||
source_indices_tagged: c_int,
|
||||
destination_strategy: c_int,
|
||||
destination_use_row_match_finder: c_int,
|
||||
}
|
||||
|
||||
unsafe fn reset_cctx_by_copying_cdict_probe(
|
||||
@@ -2798,15 +2905,6 @@ mod tests {
|
||||
.push("dirty");
|
||||
}
|
||||
|
||||
unsafe extern "C" fn reset_cctx_by_copying_cdict_copy_tables(
|
||||
context: *mut c_void,
|
||||
_cdict: *const c_void,
|
||||
) {
|
||||
unsafe { reset_cctx_by_copying_cdict_probe(context) }
|
||||
.events
|
||||
.push("tables");
|
||||
}
|
||||
|
||||
unsafe extern "C" fn reset_cctx_by_copying_cdict_zero_hash_table3(
|
||||
context: *mut c_void,
|
||||
_cdict: *const c_void,
|
||||
@@ -2839,6 +2937,16 @@ mod tests {
|
||||
destination_block_state: *mut *mut ZSTD_compressedBlockState_t,
|
||||
source_block_state: *const ZSTD_compressedBlockState_t,
|
||||
) -> ZSTD_rust_resetCCtxByCopyingCDictState {
|
||||
probe.destination_hash_table_slot = probe.destination_hash_table.as_mut_ptr();
|
||||
probe.destination_chain_table_slot = probe.destination_chain_table.as_mut_ptr();
|
||||
probe.destination_tag_table_slot = probe.destination_tag_table.as_mut_ptr();
|
||||
probe.source_hash_log = 2;
|
||||
probe.source_chain_log = 2;
|
||||
probe.source_strategy = 2;
|
||||
probe.source_use_row_match_finder = 2;
|
||||
probe.source_indices_tagged = 1;
|
||||
probe.destination_strategy = 2;
|
||||
probe.destination_use_row_match_finder = 2;
|
||||
ZSTD_rust_resetCCtxByCopyingCDictState {
|
||||
callback_context: (probe as *mut ResetCCtxByCopyingCDictProbe).cast(),
|
||||
cdict,
|
||||
@@ -2846,9 +2954,23 @@ mod tests {
|
||||
pledged_src_size: 123,
|
||||
reset: Some(reset_cctx_by_copying_cdict_reset),
|
||||
mark_tables_dirty: Some(reset_cctx_by_copying_cdict_mark_dirty),
|
||||
copy_tables: Some(reset_cctx_by_copying_cdict_copy_tables),
|
||||
zero_hash_table3: Some(reset_cctx_by_copying_cdict_zero_hash_table3),
|
||||
mark_tables_clean: Some(reset_cctx_by_copying_cdict_mark_clean),
|
||||
destination_hash_table: &mut probe.destination_hash_table_slot,
|
||||
source_hash_table: probe.source_hash_table.as_ptr(),
|
||||
source_hash_log: &probe.source_hash_log,
|
||||
destination_chain_table: &mut probe.destination_chain_table_slot,
|
||||
source_chain_table: probe.source_chain_table.as_ptr(),
|
||||
source_chain_log: &probe.source_chain_log,
|
||||
source_strategy: &probe.source_strategy,
|
||||
source_use_row_match_finder: &probe.source_use_row_match_finder,
|
||||
source_indices_tagged: &probe.source_indices_tagged,
|
||||
destination_strategy: &probe.destination_strategy,
|
||||
destination_use_row_match_finder: &probe.destination_use_row_match_finder,
|
||||
destination_tag_table: &mut probe.destination_tag_table_slot,
|
||||
source_tag_table: probe.source_tag_table.as_ptr(),
|
||||
destination_hash_salt: &mut probe.destination_hash_salt,
|
||||
source_hash_salt: &probe.source_hash_salt,
|
||||
destination_window,
|
||||
source_window,
|
||||
destination_next_to_update,
|
||||
@@ -2867,7 +2989,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reset_cctx_by_copying_cdict_runs_callbacks_in_original_order() {
|
||||
let mut probe = ResetCCtxByCopyingCDictProbe::default();
|
||||
let mut probe = ResetCCtxByCopyingCDictProbe {
|
||||
source_hash_table: [0x0000_0101, 0x0000_0202, 0x0000_0303, 0x0000_0404],
|
||||
source_chain_table: [0x0000_1101, 0x0000_2202, 0x0000_3303, 0x0000_4404],
|
||||
..Default::default()
|
||||
};
|
||||
let cdict = 0x4000usize as *const c_void;
|
||||
let params = 0x3000usize as *const c_void;
|
||||
let mut source_block_state =
|
||||
@@ -2924,10 +3050,11 @@ mod tests {
|
||||
let result = unsafe { ZSTD_rust_resetCCtxByCopyingCDict(&state) };
|
||||
|
||||
assert_eq!(result, 0);
|
||||
assert_eq!(
|
||||
probe.events,
|
||||
["reset", "dirty", "tables", "zero-h3", "clean"]
|
||||
);
|
||||
assert_eq!(probe.events, ["reset", "dirty", "zero-h3", "clean"]);
|
||||
assert_eq!(probe.destination_hash_table, [1, 2, 3, 4]);
|
||||
assert_eq!(probe.destination_chain_table, [0x11, 0x22, 0x33, 0x44]);
|
||||
assert_eq!(probe.destination_tag_table, [0; 4]);
|
||||
assert_eq!(probe.destination_hash_salt, 0);
|
||||
assert_eq!(probe.cdict, cdict);
|
||||
assert_eq!(probe.params, params);
|
||||
assert_eq!(probe.pledged_src_size, 123);
|
||||
@@ -2946,6 +3073,27 @@ mod tests {
|
||||
destination_block_state.entropy.fse.offcode_repeatMode,
|
||||
source_block_state.entropy.fse.offcode_repeatMode
|
||||
);
|
||||
|
||||
probe.events.clear();
|
||||
probe.source_hash_table = [11, 22, 33, 44];
|
||||
probe.source_tag_table = [5, 6, 7, 8];
|
||||
probe.destination_hash_table = [0; 4];
|
||||
probe.destination_tag_table = [0; 4];
|
||||
probe.destination_hash_salt = 0;
|
||||
probe.source_hash_salt = 0x0123_4567_89ab_cdef;
|
||||
probe.source_strategy = 3;
|
||||
probe.source_use_row_match_finder = 1;
|
||||
probe.source_indices_tagged = 0;
|
||||
probe.destination_strategy = 3;
|
||||
probe.destination_use_row_match_finder = 1;
|
||||
|
||||
let result = unsafe { ZSTD_rust_resetCCtxByCopyingCDict(&state) };
|
||||
|
||||
assert_eq!(result, 0);
|
||||
assert_eq!(probe.events, ["reset", "dirty", "zero-h3", "clean"]);
|
||||
assert_eq!(probe.destination_hash_table, probe.source_hash_table);
|
||||
assert_eq!(probe.destination_tag_table, probe.source_tag_table);
|
||||
assert_eq!(probe.destination_hash_salt, probe.source_hash_salt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user