feat(cli): move patch-from parameter policy to Rust

Keep the public ZSTD_getCParams() selection and all DISPLAYLEVEL output in
programs/fileio.c, but pass its by-value compression-parameter snapshot to a
Rust policy leaf. Rust now preserves the original source-size highbit and
window clamping rules, validates and updates the patch memory limit before
success outputs, derives cycleLog, updates comprParams->windowLog, and applies
automatic LDM without overwriting the explicit-LDM diagnostic semantics.

The Rust ABI returns the raw file window and the two diagnostic predicates so
C can retain its existing messages and optimal-parser note ordering. C layout
assertions cover the seven-word compression-parameter snapshot on both
platform-width variants; rejected unknown and oversized inputs leave all
outputs unchanged.

Test Plan:
- Rust clippy pre/post nightly-format matrix for library compression and CLI
  cli,compression,decompression,benchmark features -- passed.
- `cargo test --manifest-path rust/cli/Cargo.toml --no-default-features
  --features cli,compression,decompression,benchmark` -- 137 passed.
- `make -C programs -j2 zstd` -- passed.
- `make -C tests -j2 test-cli-tests` -- 41 passed.
- `python3 tests/cli-tests/run.py decompression/pass-through.sh` -- passed.
- Focused patch-from round trips, stream-size failure ordering, and automatic
  long-mode diagnostics -- passed.
- `make -C tests -j2 test-rust-lib-smoke` -- passed.
- No i686 Rust target is installed; 32-bit policy bounds are covered by
  size_of-based constants and C/Rust ABI assertions.
This commit is contained in:
2026-07-18 15:29:02 +02:00
parent 841806dcce
commit 23450d8c3b
2 changed files with 252 additions and 77 deletions
+35 -28
View File
@@ -282,6 +282,12 @@ typedef char FIO_rust_ctx_total_output_offset[
typedef char FIO_rust_ctx_size[ typedef char FIO_rust_ctx_size[
(sizeof(FIO_ctx_t) (sizeof(FIO_ctx_t)
== offsetof(FIO_ctx_t, totalBytesOutput) + sizeof(size_t)) ? 1 : -1]; == offsetof(FIO_ctx_t, totalBytesOutput) + sizeof(size_t)) ? 1 : -1];
typedef char FIO_rust_compression_params_strategy_offset[
(offsetof(ZSTD_compressionParameters, strategy)
== 6 * sizeof(unsigned)) ? 1 : -1];
typedef char FIO_rust_compression_params_size[
(sizeof(ZSTD_compressionParameters)
== 7 * sizeof(unsigned)) ? 1 : -1];
int FIO_rust_shouldDisplayFileSummary(const FIO_ctx_t* fCtx); int FIO_rust_shouldDisplayFileSummary(const FIO_ctx_t* fCtx);
int FIO_rust_shouldDisplayMultipleFileSummary(const FIO_ctx_t* fCtx); int FIO_rust_shouldDisplayMultipleFileSummary(const FIO_ctx_t* fCtx);
@@ -328,11 +334,14 @@ static int FIO_shouldDisplayMultipleFileSummary(FIO_ctx_t const* fCtx)
int FIO_rust_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles); int FIO_rust_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles);
unsigned FIO_rust_highbit64(unsigned long long v); unsigned FIO_rust_highbit64(unsigned long long v);
unsigned long long FIO_rust_getLargestFileSize(const char** inFileNames, unsigned nbFiles); unsigned long long FIO_rust_getLargestFileSize(const char** inFileNames, unsigned nbFiles);
unsigned FIO_rust_cycleLog(unsigned hashLog, int strategy); int FIO_rust_adjustParamsForPatchFromMode(FIO_prefs_t* prefs,
void FIO_rust_patchFromWindowPolicy(unsigned fileWindowLog, ZSTD_compressionParameters* comprParams,
unsigned cycleLog, unsigned long long dictSize,
unsigned* windowLog, unsigned long long maxSrcFileSize,
int* enableLdm); ZSTD_compressionParameters cParams,
unsigned* fileWindowLog,
int* autoLdm,
int* optimalParser);
void FIO_rust_setInBuffer(ZSTD_inBuffer* output, const void* buf, size_t s, size_t pos); void FIO_rust_setInBuffer(ZSTD_inBuffer* output, const void* buf, size_t s, size_t pos);
void FIO_rust_setOutBuffer(ZSTD_outBuffer* output, void* buf, size_t s, size_t pos); void FIO_rust_setOutBuffer(ZSTD_outBuffer* output, void* buf, size_t s, size_t pos);
const char* FIO_rust_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix); const char* FIO_rust_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix);
@@ -865,38 +874,36 @@ typedef struct {
ReadPoolCtx_t *readCtx; ReadPoolCtx_t *readCtx;
} cRess_t; } cRess_t;
/** ZSTD_cycleLog() :
* condition for correct operation : hashLog > 1 */
static U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat)
{
assert(hashLog > 1);
return FIO_rust_cycleLog(hashLog, (int)strat);
}
static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs, static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs,
ZSTD_compressionParameters* comprParams, ZSTD_compressionParameters* comprParams,
unsigned long long const dictSize, unsigned long long const dictSize,
unsigned long long const maxSrcFileSize, unsigned long long const maxSrcFileSize,
int cLevel) int cLevel)
{ {
unsigned const fileWindowLog = FIO_highbit64(maxSrcFileSize) + 1; enum {
FIO_PATCH_MEM_LIMIT_SUCCESS = 0,
FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE = 1,
FIO_PATCH_MEM_LIMIT_TOO_LARGE = 2
};
ZSTD_compressionParameters const cParams = ZSTD_getCParams(cLevel, (size_t)maxSrcFileSize, (size_t)dictSize); ZSTD_compressionParameters const cParams = ZSTD_getCParams(cLevel, (size_t)maxSrcFileSize, (size_t)dictSize);
unsigned windowLog; unsigned fileWindowLog;
int enableLdm; int autoLdm;
FIO_adjustMemLimitForPatchFromMode(prefs, dictSize, maxSrcFileSize); int optimalParser;
FIO_rust_patchFromWindowPolicy(fileWindowLog, int const status = FIO_rust_adjustParamsForPatchFromMode(
ZSTD_cycleLog(cParams.chainLog, cParams.strategy), prefs, comprParams, dictSize, maxSrcFileSize, cParams,
&windowLog, &fileWindowLog, &autoLdm, &optimalParser);
&enableLdm); if (status == FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE)
EXM_THROW(42, "Using --patch-from with stdin requires --stream-size");
if (status == FIO_PATCH_MEM_LIMIT_TOO_LARGE) {
unsigned const maxWindowSize = (1U << ZSTD_WINDOWLOG_MAX);
EXM_THROW(42, "Can't handle files larger than %u GB\n", maxWindowSize/(1 GB));
}
assert(status == FIO_PATCH_MEM_LIMIT_SUCCESS);
if (fileWindowLog > ZSTD_WINDOWLOG_MAX) if (fileWindowLog > ZSTD_WINDOWLOG_MAX)
DISPLAYLEVEL(1, "Max window log exceeded by file (compression ratio will suffer)\n"); DISPLAYLEVEL(1, "Max window log exceeded by file (compression ratio will suffer)\n");
comprParams->windowLog = windowLog; if (autoLdm)
if (enableLdm) { DISPLAYLEVEL(2, "long mode automatically triggered\n");
if (!prefs->ldmFlag) if (optimalParser) {
DISPLAYLEVEL(2, "long mode automatically triggered\n");
FIO_setLdmFlag(prefs, 1);
}
if (cParams.strategy >= ZSTD_btopt) {
DISPLAYLEVEL(4, "[Optimal parser notes] Consider the following to improve patch size at the cost of speed:\n"); DISPLAYLEVEL(4, "[Optimal parser notes] Consider the following to improve patch size at the cost of speed:\n");
DISPLAYLEVEL(4, "- Set a larger targetLength (e.g. --zstd=targetLength=4096)\n"); DISPLAYLEVEL(4, "- Set a larger targetLength (e.g. --zstd=targetLength=4096)\n");
DISPLAYLEVEL(4, "- Set a larger chainLog (e.g. --zstd=chainLog=%u)\n", ZSTD_CHAINLOG_MAX); DISPLAYLEVEL(4, "- Set a larger chainLog (e.g. --zstd=chainLog=%u)\n", ZSTD_CHAINLOG_MAX);
+217 -49
View File
@@ -32,6 +32,7 @@ const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_WINDOWLOG_MIN: u32 = 10; const ZSTD_WINDOWLOG_MIN: u32 = 10;
const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 }; const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 };
const ZSTD_BTLAZY2: c_int = 6; const ZSTD_BTLAZY2: c_int = 6;
const ZSTD_BTOPT: c_int = 7;
static STDOUT_MARK: &[u8] = b"/*stdout*\\\0"; static STDOUT_MARK: &[u8] = b"/*stdout*\\\0";
static mut COMPRESSED_NAME_CAPACITY: usize = 0; static mut COMPRESSED_NAME_CAPACITY: usize = 0;
@@ -53,6 +54,23 @@ pub struct FIO_outBuffer {
pos: usize, pos: usize,
} }
/// ABI snapshot of C's public `ZSTD_compressionParameters`.
///
/// `programs/fileio.c` keeps selecting the parameters with `ZSTD_getCParams()`
/// and passes this value by value. Rust only consumes the policy fields; it
/// does not reach into a private compression context.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FIO_compressionParameters {
windowLog: c_uint,
chainLog: c_uint,
hashLog: c_uint,
searchLog: c_uint,
minMatch: c_uint,
targetLength: c_uint,
strategy: c_int,
}
/// C's private `fileInfo_t` from `programs/fileio.c`. /// C's private `fileInfo_t` from `programs/fileio.c`.
#[repr(C)] #[repr(C)]
pub struct FIO_fileInfo_t { pub struct FIO_fileInfo_t {
@@ -590,8 +608,8 @@ pub unsafe extern "C" fn FIO_determineHasStdinInput(
/// This mirrors `FIO_highbit64()` and deliberately keeps its zero-input /// This mirrors `FIO_highbit64()` and deliberately keeps its zero-input
/// assertion semantics. In builds where assertions are disabled, zero /// assertion semantics. In builds where assertions are disabled, zero
/// still produces the same result as the original shift loop. /// still produces the same result as the original shift loop.
#[no_mangle] #[inline]
pub extern "C" fn FIO_rust_highbit64(value: u64) -> c_uint { fn highbit64(value: u64) -> c_uint {
debug_assert!(value != 0); debug_assert!(value != 0);
if value == 0 { if value == 0 {
return 0; return 0;
@@ -599,6 +617,11 @@ pub extern "C" fn FIO_rust_highbit64(value: u64) -> c_uint {
(u64::BITS - 1 - value.leading_zeros()) as c_uint (u64::BITS - 1 - value.leading_zeros()) as c_uint
} }
#[no_mangle]
pub extern "C" fn FIO_rust_highbit64(value: u64) -> c_uint {
highbit64(value)
}
#[inline] #[inline]
fn cycle_log(hash_log: c_uint, strategy: c_int) -> c_uint { fn cycle_log(hash_log: c_uint, strategy: c_int) -> c_uint {
hash_log.wrapping_sub(c_uint::from(strategy >= ZSTD_BTLAZY2)) hash_log.wrapping_sub(c_uint::from(strategy >= ZSTD_BTLAZY2))
@@ -610,18 +633,60 @@ pub extern "C" fn FIO_rust_cycleLog(hash_log: c_uint, strategy: c_int) -> c_uint
cycle_log(hash_log, strategy) cycle_log(hash_log, strategy)
} }
/// Rust scalar ABI for patch-from window and automatic-LDM policy. /// Apply the deterministic patch-from parameter policy.
///
/// C intentionally selects `c_params` with its public `ZSTD_getCParams()` API
/// and keeps all user-facing diagnostics in the wrapper. Rust owns the
/// remaining policy: source-size window calculation, memory-limit validation,
/// cycle/window selection, and automatic LDM mutation. The three output
/// pointers return the predicates needed by C to preserve its diagnostics.
#[allow(clippy::too_many_arguments)]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn FIO_rust_patchFromWindowPolicy( pub unsafe extern "C" fn FIO_rust_adjustParamsForPatchFromMode(
file_window_log: c_uint, prefs: *mut FIO_prefs_t,
cycle_log: c_uint, compr_params: *mut FIO_compressionParameters,
window_log: *mut c_uint, dict_size: u64,
enable_ldm: *mut c_int, max_src_file_size: u64,
) { c_params: FIO_compressionParameters,
file_window_log: *mut c_uint,
auto_ldm: *mut c_int,
optimal_parser: *mut c_int,
) -> c_int {
/* Keep this calculation before memory-limit validation: the original C
* helper evaluates FIO_highbit64() before calling its mem-limit helper.
* `debug_assert!` and the zero fallback preserve that helper's debug and
* NDEBUG behavior. */
let file_window_log_value = highbit64(max_src_file_size).wrapping_add(1);
let current_mem_limit = unsafe { (*prefs).memLimit };
let mem_limit = match adjusted_patch_mem_limit(current_mem_limit, dict_size, max_src_file_size)
{
Ok(mem_limit) => mem_limit,
Err(status) => return status,
};
/* C's ZSTD_cycleLog() asserted this invariant before the scalar policy
* was moved here. Keep the assertion while retaining wrapping arithmetic
* in non-asserting builds. */
debug_assert!(c_params.chainLog > 1);
let cycle_log_value = cycle_log(c_params.chainLog, c_params.strategy);
let window_log = file_window_log_value.clamp(ZSTD_WINDOWLOG_MIN, ZSTD_WINDOWLOG_MAX);
let enable_ldm = file_window_log_value > cycle_log_value;
let was_ldm_disabled = unsafe { (*prefs).ldmFlag == 0 };
unsafe { unsafe {
window_log.write(file_window_log.clamp(ZSTD_WINDOWLOG_MIN, ZSTD_WINDOWLOG_MAX)); ptr::addr_of_mut!((*prefs).memLimit).write(mem_limit);
enable_ldm.write(c_int::from(file_window_log > cycle_log)); ptr::addr_of_mut!((*compr_params).windowLog).write(window_log);
if enable_ldm {
/* Match FIO_setLdmFlag(prefs, 1): an automatic trigger always
* normalizes the stored preference to exactly one. */
ptr::addr_of_mut!((*prefs).ldmFlag).write(1);
}
file_window_log.write(file_window_log_value);
auto_ldm.write(c_int::from(enable_ldm && was_ldm_disabled));
optimal_parser.write(c_int::from(c_params.strategy >= ZSTD_BTOPT));
} }
FIO_PATCH_MEM_LIMIT_SUCCESS
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -1083,6 +1148,50 @@ mod tests {
prefs prefs
} }
fn compression_params(chain_log: c_uint, strategy: c_int) -> FIO_compressionParameters {
FIO_compressionParameters {
windowLog: 19,
chainLog: chain_log,
hashLog: 18,
searchLog: 1,
minMatch: 4,
targetLength: 0,
strategy,
}
}
fn apply_patch_policy(
prefs: &mut FIO_prefs_t,
dict_size: u64,
max_src_file_size: u64,
c_params: FIO_compressionParameters,
) -> (c_int, FIO_compressionParameters, c_uint, c_int, c_int) {
let mut compr_params = c_params;
compr_params.windowLog = 99;
let mut file_window_log = c_uint::MAX;
let mut auto_ldm = -1;
let mut optimal_parser = -1;
let status = unsafe {
FIO_rust_adjustParamsForPatchFromMode(
prefs,
&mut compr_params,
dict_size,
max_src_file_size,
c_params,
&mut file_window_log,
&mut auto_ldm,
&mut optimal_parser,
)
};
(
status,
compr_params,
file_window_log,
auto_ldm,
optimal_parser,
)
}
#[test] #[test]
fn patch_mem_limit_preserves_existing_limit() { fn patch_mem_limit_preserves_existing_limit() {
let mut prefs = prefs_with_mem_limit(4096); let mut prefs = prefs_with_mem_limit(4096);
@@ -1194,52 +1303,111 @@ mod tests {
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 + 3), 19); assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 + 3), 19);
} }
fn patch_from_window_policy(file_window_log: c_uint, cycle_log: c_uint) -> (c_uint, c_int) {
let mut window_log = c_uint::MAX;
let mut enable_ldm = -1;
unsafe {
FIO_rust_patchFromWindowPolicy(
file_window_log,
cycle_log,
&mut window_log,
&mut enable_ldm,
);
}
(window_log, enable_ldm)
}
#[test] #[test]
fn patch_from_window_policy_clamps_to_lower_bound() { fn patch_policy_matches_cparams_abi_and_window_boundaries() {
assert_eq!( assert_eq!(
patch_from_window_policy(ZSTD_WINDOWLOG_MIN - 1, ZSTD_WINDOWLOG_MIN), size_of::<FIO_compressionParameters>(),
(ZSTD_WINDOWLOG_MIN, 0) 7 * size_of::<c_uint>()
); );
}
#[test]
fn patch_from_window_policy_clamps_to_upper_bound() {
assert_eq!( assert_eq!(
patch_from_window_policy(ZSTD_WINDOWLOG_MAX + 1, ZSTD_WINDOWLOG_MAX + 1), offset_of!(FIO_compressionParameters, strategy),
(ZSTD_WINDOWLOG_MAX, 0) 6 * size_of::<c_uint>()
); );
}
#[test] let mut prefs = prefs_with_mem_limit(0);
fn patch_from_window_policy_keeps_equal_cycle_log_out_of_ldm() { let (status, compr_params, file_window_log, auto_ldm, optimal_parser) =
assert_eq!(patch_from_window_policy(20, 20), (20, 0)); apply_patch_policy(&mut prefs, 0, 1, compression_params(10, 1));
} assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(compr_params.windowLog, ZSTD_WINDOWLOG_MIN);
assert_eq!(file_window_log, 1);
assert_eq!(auto_ldm, 0);
assert_eq!(optimal_parser, 0);
#[test] let mut prefs = prefs_with_mem_limit(0);
fn patch_from_window_policy_enables_ldm_one_above_cycle_log() { let source_size = 1u64 << (ZSTD_WINDOWLOG_MAX - 1);
assert_eq!(patch_from_window_policy(20, 19), (20, 1)); let (status, compr_params, file_window_log, auto_ldm, optimal_parser) = apply_patch_policy(
} &mut prefs,
0,
#[test] source_size,
fn patch_from_window_policy_uses_raw_value_above_upper_bound_for_ldm() { compression_params(ZSTD_WINDOWLOG_MAX, 1),
assert_eq!(
patch_from_window_policy(ZSTD_WINDOWLOG_MAX + 1, ZSTD_WINDOWLOG_MAX),
(ZSTD_WINDOWLOG_MAX, 1)
); );
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(compr_params.windowLog, ZSTD_WINDOWLOG_MAX);
assert_eq!(file_window_log, ZSTD_WINDOWLOG_MAX);
assert_eq!(auto_ldm, 0);
assert_eq!(optimal_parser, 0);
let mut prefs = prefs_with_mem_limit(0);
let (status, compr_params, file_window_log, auto_ldm, optimal_parser) = apply_patch_policy(
&mut prefs,
0,
1u64 << ZSTD_WINDOWLOG_MAX,
compression_params(ZSTD_WINDOWLOG_MAX, 1),
);
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(compr_params.windowLog, ZSTD_WINDOWLOG_MAX);
assert_eq!(file_window_log, ZSTD_WINDOWLOG_MAX + 1);
assert_eq!(auto_ldm, 1);
assert_eq!(optimal_parser, 0);
}
#[test]
fn patch_policy_returns_memory_statuses_before_mutating_outputs() {
let mut prefs = prefs_with_mem_limit(4096);
let c_params = compression_params(20, ZSTD_BTOPT);
let (status, compr_params, file_window_log, auto_ldm, optimal_parser) =
apply_patch_policy(&mut prefs, 0, UTIL_FILESIZE_UNKNOWN, c_params);
assert_eq!(status, FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE);
assert_eq!(prefs.memLimit, 4096);
assert_eq!(compr_params.windowLog, 99);
assert_eq!(file_window_log, c_uint::MAX);
assert_eq!(auto_ldm, -1);
assert_eq!(optimal_parser, -1);
let mut prefs = prefs_with_mem_limit(4096);
let (status, compr_params, file_window_log, auto_ldm, optimal_parser) =
apply_patch_policy(&mut prefs, 0, (1u64 << ZSTD_WINDOWLOG_MAX) + 1, c_params);
assert_eq!(status, FIO_PATCH_MEM_LIMIT_TOO_LARGE);
assert_eq!(prefs.memLimit, 4096);
assert_eq!(compr_params.windowLog, 99);
assert_eq!(file_window_log, c_uint::MAX);
assert_eq!(auto_ldm, -1);
assert_eq!(optimal_parser, -1);
}
#[test]
fn patch_policy_automatically_enables_ldm_only_when_needed() {
let mut prefs = prefs_with_mem_limit(0);
let (status, compr_params, file_window_log, auto_ldm, optimal_parser) =
apply_patch_policy(&mut prefs, 0, 1u64 << 13, compression_params(13, 1));
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(compr_params.windowLog, 14);
assert_eq!(file_window_log, 14);
assert_eq!(prefs.ldmFlag, 1);
assert_eq!(auto_ldm, 1);
assert_eq!(optimal_parser, 0);
let mut prefs = prefs_with_mem_limit(0);
prefs.ldmFlag = 1;
let (status, _, _, auto_ldm, _) =
apply_patch_policy(&mut prefs, 0, 1u64 << 13, compression_params(13, 1));
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(prefs.ldmFlag, 1);
assert_eq!(auto_ldm, 0);
}
#[test]
fn patch_policy_reports_optimal_parser_condition_from_cparams() {
let mut prefs = prefs_with_mem_limit(0);
let (status, _, _, auto_ldm, optimal_parser) = apply_patch_policy(
&mut prefs,
0,
1u64 << 10,
compression_params(20, ZSTD_BTOPT),
);
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
assert_eq!(auto_ldm, 0);
assert_eq!(optimal_parser, 1);
} }
#[test] #[test]