refactor(ldm): move block compressor policy to Rust

Move LDM block-compressor strategy validation, selection ordering, and callback error propagation into Rust while retaining dictionary-mode lookup, MatchState mutation, and the actual codec callback in C. Invalid preflight inputs now use the existing generic error path, and valid strategies preserve the original optimal-parser boundary.

Test Plan: ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings; cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check; make -j1; ./tests/rustLibSmoke; make -j1 -C tests test (all shell tests, large streaming tests, native tester, fuzzer phases, and zstream tester passed).
This commit is contained in:
2026-07-20 18:25:45 +02:00
parent 6caba856d4
commit 03d0d4edac
2 changed files with 124 additions and 15 deletions
+79 -2
View File
@@ -15,7 +15,7 @@ use crate::xxhash::XXH64;
use crate::zstd_compress_params::ZSTD_compressionParameters;
use crate::zstd_fast::store_seq_opaque;
use std::ffi::c_void;
use std::mem::size_of;
use std::mem::{offset_of, size_of};
use std::os::raw::c_int;
const LDM_BATCH_SIZE: usize = 64;
@@ -35,6 +35,23 @@ const LDM_FAST_TABLES_FAST: c_int = 1;
const LDM_FAST_TABLES_DFAST: c_int = 2;
const LDM_FAST_TABLES_INVALID: c_int = -1;
type LdmSelectBlockCompressorFn = unsafe extern "C" fn(*mut c_void, c_int) -> usize;
/// Prefix shared with the C-owned block context. The trailing compressor
/// function pointer remains private to C; Rust only invokes the selection
/// callback and consumes its status before entering block processing.
#[repr(C)]
struct LdmBlockContextProjection {
callback_context: *mut c_void,
select_block_compressor: Option<LdmSelectBlockCompressorFn>,
}
const _: () = {
assert!(offset_of!(LdmBlockContextProjection, callback_context) == 0);
assert!(offset_of!(LdmBlockContextProjection, select_block_compressor) == size_of::<usize>());
assert!(size_of::<LdmBlockContextProjection>() == 2 * size_of::<usize>());
};
#[repr(C)]
#[derive(Clone, Copy)]
struct LdmEntry {
@@ -105,6 +122,24 @@ fn use_optimal_parser(strategy: c_int) -> bool {
strategy >= ZSTD_BTOPT
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LdmBlockCompressionPath {
InterspersedSequences,
OptimalParser,
}
#[inline]
fn ldm_block_compression_path(strategy: c_int) -> Option<LdmBlockCompressionPath> {
if !(ZSTD_FAST_STRATEGY..=ZSTD_STRATEGY_MAX).contains(&strategy) {
return None;
}
Some(if use_optimal_parser(strategy) {
LdmBlockCompressionPath::OptimalParser
} else {
LdmBlockCompressionPath::InterspersedSequences
})
}
unsafe extern "C" {
fn ZSTD_ldm_rust_gearTable() -> *const u64;
fn ZSTD_ldm_rust_prepareBlock(context: *mut c_void, anchor: *const c_void);
@@ -974,11 +1009,31 @@ pub unsafe extern "C" fn ZSTD_rust_ldm_blockCompress(
src_size: usize,
min_match: u32,
strategy: c_int,
use_row_match_finder: c_int,
) -> usize {
let Some(path) = ldm_block_compression_path(strategy) else {
return ERROR(ZstdErrorCode::Generic);
};
if block_context.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let block_context_projection =
unsafe { &mut *block_context.cast::<LdmBlockContextProjection>() };
if block_context_projection.callback_context.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let Some(select_block_compressor) = block_context_projection.select_block_compressor else {
return ERROR(ZstdErrorCode::Generic);
};
let selection_result = unsafe { select_block_compressor(block_context, use_row_match_finder) };
if ERR_isError(selection_result) {
return selection_result;
}
let raw_seq_store = raw_seq_store.cast::<RawSeqStore>();
let input = src.cast::<u8>();
let input_end = input.wrapping_add(src_size);
if use_optimal_parser(strategy) {
if path == LdmBlockCompressionPath::OptimalParser {
unsafe { ZSTD_ldm_rust_setLdmSeqStore(block_context, raw_seq_store.cast::<c_void>()) };
let last_literals = unsafe {
ZSTD_ldm_rust_compressLiterals(block_context, seq_store, reps, src, src_size)
@@ -1121,6 +1176,28 @@ mod tests {
assert!(use_optimal_parser(9));
}
#[test]
fn block_compression_path_validates_strategy_and_preserves_parser_boundary() {
assert_eq!(ldm_block_compression_path(ZSTD_FAST_STRATEGY - 1), None);
assert_eq!(
ldm_block_compression_path(ZSTD_FAST_STRATEGY),
Some(LdmBlockCompressionPath::InterspersedSequences)
);
assert_eq!(
ldm_block_compression_path(ZSTD_BTOPT - 1),
Some(LdmBlockCompressionPath::InterspersedSequences)
);
assert_eq!(
ldm_block_compression_path(ZSTD_BTOPT),
Some(LdmBlockCompressionPath::OptimalParser)
);
assert_eq!(
ldm_block_compression_path(ZSTD_STRATEGY_MAX),
Some(LdmBlockCompressionPath::OptimalParser)
);
assert_eq!(ldm_block_compression_path(ZSTD_STRATEGY_MAX + 1), None);
}
#[test]
fn fast_table_kind_preserves_c_strategy_dispatch() {
assert_eq!(