feat(compress): move Fast dictionary table load to Rust

The dictionary-content orchestrator still routed the Fast hash-table fill
through a C callback even though the matching leaf already lived in Rust.
Replace that callback slot with a pointer-sized C/Rust projection containing
only the Fast leaf inputs, including a by-value copy of nextToUpdate and the
normalized CDict/full-load flags.  Keep the dtlm/tfp invariant assertion in C
and retain all bridge offsets and the double-Fast callback.

A broader MatchState projection would expose private layout and adding fields
would change the established 34-word bridge.  The narrow projection keeps
that ABI stable while making the Fast branch call ZSTD_rust_fillHashTable
directly.

Test Plan:
- `git diff --cached --check`
- `rustfmt --check --edition 2021 rust/src/zstd_compress_dictionary.rs` (reports only pre-existing formatting drift outside this change)
- Cargo, make, and full tests intentionally not run per task instructions.
This commit is contained in:
2026-07-21 20:42:04 +02:00
parent cc43ebac4e
commit a3ffd36a63
2 changed files with 152 additions and 20 deletions
+105 -8
View File
@@ -28,6 +28,7 @@ use crate::zstd_compress_params_api::ZSTD_CCtx_params;
use crate::zstd_compress_stats::{
ZSTD_compressedBlockState_t, ZSTD_rust_resetCompressedBlockState,
};
use crate::zstd_fast::ZSTD_rust_fillHashTable;
use std::ffi::c_void;
use std::mem::{offset_of, size_of};
use std::os::raw::{c_int, c_short, c_uint};
@@ -138,6 +139,23 @@ type LoadDictionaryContentFillLdmFn =
unsafe extern "C" fn(context: *mut c_void, ip: *const c_void, iend: *const c_void);
type LoadDictionaryContentOverflowCorrectFn =
unsafe extern "C" fn(context: *mut c_void, ip: *const c_void, iend: *const c_void);
/// Direct projection for the Fast dictionary-table leaf.
///
/// C copies `nextToUpdate` rather than exposing a pointer into the private
/// match state. `fullTableLoad` and `forCDict` are normalized from the C enum
/// inputs after their original validity assertion has run.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ZSTD_rust_loadDictionaryContentFastTableState {
hash_table: *mut c_uint,
base: *const u8,
next_to_update: c_uint,
hash_log: c_uint,
min_match: c_uint,
full_table_load: c_int,
for_cdict: c_int,
}
type LoadDictionaryContentFillTableFn =
unsafe extern "C" fn(context: *mut c_void, iend: *const c_void, dtlm: c_int, tfp: c_int);
type LoadDictionaryContentLoadMatchFn =
@@ -149,9 +167,10 @@ type LoadDictionaryContentPublishFinalIndexFn =
/// Rust-owned policy projection for `ZSTD_loadDictionaryContent()`.
///
/// All configuration-dependent C layouts stay behind callbacks. The scalar
/// fields are copied by the C adapter so Rust owns suffix selection, window
/// and LDM ordering, index publication, and strategy dispatch without seeing
/// All remaining configuration-dependent C layouts stay behind callbacks. The
/// Fast table leaf uses the explicit projection above; the scalar fields are
/// copied by the C adapter so Rust owns suffix selection, window and LDM
/// ordering, index publication, and strategy dispatch without seeing
/// `ZSTD_MatchState_t`, `ldmState_t`, or workspace internals.
#[repr(C)]
pub struct ZSTD_rust_loadDictionaryContentState {
@@ -180,7 +199,7 @@ pub struct ZSTD_rust_loadDictionaryContentState {
publish_match_state: LoadDictionaryContentPublishMatchStateFn,
fill_ldm: LoadDictionaryContentFillLdmFn,
overflow_correct: LoadDictionaryContentOverflowCorrectFn,
fill_hash_table: LoadDictionaryContentFillTableFn,
fast_table: *const ZSTD_rust_loadDictionaryContentFastTableState,
fill_double_hash_table: LoadDictionaryContentFillTableFn,
load_dedicated: LoadDictionaryContentLoadMatchFn,
load_row: LoadDictionaryContentLoadMatchFn,
@@ -200,6 +219,39 @@ const _: () = {
assert!(size_of::<LoadDictionaryContentFillLdmFn>() == size_of::<usize>());
assert!(size_of::<LoadDictionaryContentOverflowCorrectFn>() == size_of::<usize>());
assert!(size_of::<LoadDictionaryContentFillTableFn>() == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, hash_table) == 0);
assert!(offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, base) == size_of::<usize>());
assert!(
offset_of!(
ZSTD_rust_loadDictionaryContentFastTableState,
next_to_update
) == 2 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, hash_log)
== 2 * size_of::<usize>() + size_of::<c_uint>()
);
assert!(
offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, min_match)
== 2 * size_of::<usize>() + 2 * size_of::<c_uint>()
);
assert!(
offset_of!(
ZSTD_rust_loadDictionaryContentFastTableState,
full_table_load
) == 2 * size_of::<usize>() + 3 * size_of::<c_uint>()
);
assert!(
offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, for_cdict)
== 2 * size_of::<usize>() + 3 * size_of::<c_uint>() + size_of::<c_int>()
);
assert!(
size_of::<ZSTD_rust_loadDictionaryContentFastTableState>()
== (offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, for_cdict)
+ size_of::<c_int>())
.div_ceil(size_of::<usize>())
* size_of::<usize>()
);
assert!(size_of::<LoadDictionaryContentLoadMatchFn>() == size_of::<usize>());
assert!(size_of::<LoadDictionaryContentLoadTreeFn>() == size_of::<usize>());
assert!(size_of::<LoadDictionaryContentPublishFinalIndexFn>() == size_of::<usize>());
@@ -209,6 +261,9 @@ const _: () = {
offset_of!(ZSTD_rust_loadDictionaryContentState, assert_c_params)
== 18 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_loadDictionaryContentState, fast_table) == 25 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_loadDictionaryContentState, publish_final_index)
== 31 * size_of::<usize>()
@@ -390,11 +445,16 @@ unsafe fn load_dictionary_content(
state.use_row_match_finder,
) {
DictionaryTablePolicy::Fast => unsafe {
(state.fill_hash_table)(
state.callback_context,
let fast_table = &*state.fast_table;
ZSTD_rust_fillHashTable(
fast_table.hash_table,
fast_table.base,
fast_table.next_to_update,
iend.cast(),
state.dtlm as c_int,
state.tfp as c_int,
fast_table.hash_log,
fast_table.min_match,
fast_table.full_table_load,
fast_table.for_cdict,
)
},
DictionaryTablePolicy::DoubleFast => unsafe {
@@ -6398,4 +6458,41 @@ mod tests {
DictionaryTablePolicy::Tree
);
}
#[test]
fn fast_dictionary_table_projection_dispatches_to_rust_leaf() {
let hash_log = 4u32;
let mut hash_table = vec![0u32; 1usize << (hash_log + 8)];
let mut input = [0u8; 64];
for (index, byte) in input.iter_mut().enumerate() {
*byte = (index as u8).wrapping_mul(37).wrapping_add(11);
}
let projection = ZSTD_rust_loadDictionaryContentFastTableState {
hash_table: hash_table.as_mut_ptr(),
base: input.as_ptr(),
next_to_update: 17,
hash_log,
min_match: 4,
full_table_load: 1,
for_cdict: 1,
};
unsafe {
ZSTD_rust_fillHashTable(
projection.hash_table,
projection.base,
projection.next_to_update,
input.as_ptr().wrapping_add(input.len()).cast(),
projection.hash_log,
projection.min_match,
projection.full_table_load,
projection.for_cdict,
)
};
assert!(hash_table.iter().any(|&entry| entry != 0));
assert_eq!(projection.next_to_update, 17);
assert_eq!(projection.full_table_load, 1);
assert_eq!(projection.for_cdict, 1);
}
}