feat(compress): move CCtx parameter bounds to Rust

Route public CCtx parameter bounds through the Rust policy implementation while keeping build-specific MT limits supplied by the C configuration. Preserve clamping and validation behavior for migrated parameters and add focused Rust coverage.

Test Plan: cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression; cargo clippy --manifest-path rust/Cargo.toml; cargo clippy --manifest-path rust/Cargo.toml --benches; cargo clippy --manifest-path rust/Cargo.toml --tests; make -B -C lib -j2 lib; make -B -C tests -j2 test-cli-tests; make -B -C tests -j2 test-zstream
This commit is contained in:
2026-07-18 03:38:56 +02:00
parent 65bfc6bb2b
commit 6aaabce208
2 changed files with 259 additions and 251 deletions
+235 -39
View File
@@ -12,12 +12,10 @@
//! default-resolution policy.
use crate::errors::{ZstdErrorCode, ERROR};
#[cfg(test)]
use crate::zstd_compress_params::ZSTD_bounds;
use crate::zstd_compress_params::{
ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
ZSTD_rust_params_checkCParams, ZSTD_rust_params_resolveMaxBlockSize, ZSTD_RUST_PS_DISABLE,
ZSTD_RUST_PS_ENABLE,
ZSTD_bounds, ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
ZSTD_rust_params_checkCParams, ZSTD_rust_params_getBounds,
ZSTD_rust_params_resolveMaxBlockSize, ZSTD_RUST_PS_DISABLE, ZSTD_RUST_PS_ENABLE,
};
use std::mem::size_of;
use std::os::raw::{c_int, c_void};
@@ -125,9 +123,7 @@ pub struct ZSTD_CCtx_params {
const DEFAULT_CLEVEL: c_int = 3;
const NO_CLEVEL: c_int = 0;
#[cfg(test)]
const PS_AUTO: c_int = 0;
#[cfg(test)]
const PS_DISABLE: c_int = 2;
/* Private strategy values used by the existing C policy helpers. */
@@ -135,9 +131,7 @@ const STRATEGY_GREEDY: c_int = 3;
const STRATEGY_LAZY2: c_int = 5;
const STRATEGY_BTOPT: c_int = 7;
#[cfg(test)]
const BLOCKSIZE_MAX: usize = 1 << 17;
#[cfg(test)]
const BLOCKSIZE_MAX_MIN: c_int = 1 << 10;
const TARGET_C_BLOCK_SIZE_MIN: c_int = 1340;
@@ -184,34 +178,85 @@ const C_BLOCK_SPLITTER_LEVEL: c_int = 1017;
#[cfg(not(test))]
unsafe extern "C" {
fn ZSTD_rust_cctx_params_clamp_bounds(param: c_int, value: *mut c_int) -> usize;
fn ZSTD_rust_cctx_params_within_bounds(param: c_int, value: c_int) -> c_int;
fn ZSTD_rust_cctx_params_is_multithreaded() -> c_int;
fn ZSTD_rust_cctx_params_nb_workers_max() -> c_int;
fn ZSTD_rust_cctx_params_job_size_min() -> c_int;
fn ZSTD_rust_cctx_params_job_size_max() -> c_int;
}
#[derive(Clone, Copy)]
struct BoundsConfig {
multithreaded: bool,
nb_workers_max: c_int,
job_size_max: c_int,
}
#[cfg(not(test))]
unsafe fn current_bounds_config() -> BoundsConfig {
BoundsConfig {
multithreaded: unsafe { ZSTD_rust_cctx_params_is_multithreaded() != 0 },
nb_workers_max: unsafe { ZSTD_rust_cctx_params_nb_workers_max() },
job_size_max: unsafe { ZSTD_rust_cctx_params_job_size_max() },
}
}
#[cfg(test)]
fn fallback_bounds(param: c_int) -> ZSTD_bounds {
unsafe fn current_bounds_config() -> BoundsConfig {
BoundsConfig {
multithreaded: false,
nb_workers_max: 0,
job_size_max: 0,
}
}
fn bounds_for(param: c_int, config: BoundsConfig) -> ZSTD_bounds {
let (lower, upper) = match param {
C_COMPRESSION_LEVEL | C_WINDOW_LOG | C_HASH_LOG | C_CHAIN_LOG | C_SEARCH_LOG
| C_MIN_MATCH | C_TARGET_LENGTH | C_STRATEGY => {
return crate::zstd_compress_params::ZSTD_rust_params_getBounds(param)
}
| C_MIN_MATCH | C_TARGET_LENGTH | C_STRATEGY => return ZSTD_rust_params_getBounds(param),
C_FORMAT => (0, 1),
C_FORCE_ATTACH_DICT => (0, 3),
C_LITERAL_COMPRESSION_MODE => (0, 2),
C_CONTENT_SIZE_FLAG | C_CHECKSUM_FLAG | C_DICT_ID_FLAG => (0, 1),
C_NB_WORKERS | C_JOB_SIZE | C_OVERLAP_LOG => (0, 0),
C_NB_WORKERS => (
0,
if config.multithreaded {
config.nb_workers_max
} else {
0
},
),
C_JOB_SIZE => (
0,
if config.multithreaded {
config.job_size_max
} else {
0
},
),
C_OVERLAP_LOG => {
if config.multithreaded {
(0, 9)
} else {
(0, 0)
}
}
C_ENABLE_DEDICATED_DICT_SEARCH => (0, 1),
C_ENABLE_LDM
| C_SPLIT_AFTER_SEQUENCES
| C_USE_ROW_MATCH_FINDER
| C_PREFETCH_CDICT_TABLES
| C_REPCODE_RESOLUTION => (PS_AUTO, PS_DISABLE),
C_LDM_HASH_LOG => (6, 30),
C_LDM_HASH_LOG => {
let bounds = ZSTD_rust_params_getBounds(C_HASH_LOG);
(bounds.lowerBound, bounds.upperBound)
}
C_LDM_MIN_MATCH => (4, 4096),
C_LDM_BUCKET_SIZE_LOG => (1, 8),
C_LDM_HASH_RATE_LOG => (0, if size_of::<usize>() == 4 { 24 } else { 25 }),
C_LDM_HASH_RATE_LOG => {
let window_log = ZSTD_rust_params_getBounds(C_WINDOW_LOG);
let hash_log = ZSTD_rust_params_getBounds(C_HASH_LOG);
(0, window_log.upperBound - hash_log.lowerBound)
}
C_RSYNCABLE
| C_FORCE_MAX_WINDOW
| C_VALIDATE_SEQUENCES
@@ -237,34 +282,53 @@ fn fallback_bounds(param: c_int) -> ZSTD_bounds {
}
}
#[inline]
unsafe fn current_bounds(param: c_int) -> ZSTD_bounds {
bounds_for(param, unsafe { current_bounds_config() })
}
#[inline]
unsafe fn within_bounds(param: c_int, value: c_int) -> bool {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_within_bounds(param, value) != 0 }
}
#[cfg(test)]
{
let bounds = fallback_bounds(param);
bounds.error == 0 && value >= bounds.lowerBound && value <= bounds.upperBound
within_bounds_for(param, value, unsafe { current_bounds_config() })
}
#[inline]
fn within_bounds_for(param: c_int, value: c_int, config: BoundsConfig) -> bool {
let bounds = bounds_for(param, config);
bounds.error == 0 && value >= bounds.lowerBound && value <= bounds.upperBound
}
#[inline]
fn clamp_bounds_for(param: c_int, value: &mut c_int, config: BoundsConfig) -> usize {
let bounds = bounds_for(param, config);
if bounds.error != 0 {
return bounds.error;
}
*value = (*value).clamp(bounds.lowerBound, bounds.upperBound);
0
}
#[inline]
unsafe fn clamp_bounds(param: c_int, value: &mut c_int) -> usize {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_clamp_bounds(param, value) }
}
#[cfg(test)]
{
let bounds = fallback_bounds(param);
if bounds.error != 0 {
return bounds.error;
}
*value = (*value).clamp(bounds.lowerBound, bounds.upperBound);
0
}
clamp_bounds_for(param, value, unsafe { current_bounds_config() })
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_cctx_params_get_bounds(param: c_int) -> ZSTD_bounds {
unsafe { current_bounds(param) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_cctx_params_clamp_bounds(
param: c_int,
value: *mut c_int,
) -> usize {
unsafe { clamp_bounds(param, &mut *value) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_cctx_params_within_bounds(param: c_int, value: c_int) -> c_int {
unsafe { within_bounds(param, value) as c_int }
}
#[inline]
@@ -1006,6 +1070,138 @@ mod tests {
);
}
#[test]
fn migrated_non_core_bounds_preserve_pointer_and_thread_configuration() {
let single_threaded = BoundsConfig {
multithreaded: false,
nb_workers_max: 0,
job_size_max: 0,
};
let expected = [
(C_FORMAT, 0, 1),
(C_FORCE_ATTACH_DICT, 0, 3),
(C_LITERAL_COMPRESSION_MODE, 0, 2),
(C_CONTENT_SIZE_FLAG, 0, 1),
(C_CHECKSUM_FLAG, 0, 1),
(C_DICT_ID_FLAG, 0, 1),
(C_NB_WORKERS, 0, 0),
(C_JOB_SIZE, 0, 0),
(C_OVERLAP_LOG, 0, 0),
(C_ENABLE_DEDICATED_DICT_SEARCH, 0, 1),
(C_ENABLE_LDM, PS_AUTO, PS_DISABLE),
(C_LDM_HASH_LOG, 6, 30),
(C_LDM_MIN_MATCH, 4, 4096),
(C_LDM_BUCKET_SIZE_LOG, 1, 8),
(
C_LDM_HASH_RATE_LOG,
0,
if size_of::<usize>() == 4 { 24 } else { 25 },
),
(C_RSYNCABLE, 0, 1),
(C_FORCE_MAX_WINDOW, 0, 1),
(C_VALIDATE_SEQUENCES, 0, 1),
(C_SPLIT_AFTER_SEQUENCES, PS_AUTO, PS_DISABLE),
(C_USE_ROW_MATCH_FINDER, PS_AUTO, PS_DISABLE),
(C_DETERMINISTIC_REF_PREFIX, 0, 1),
(C_PREFETCH_CDICT_TABLES, PS_AUTO, PS_DISABLE),
(C_ENABLE_SEQ_PRODUCER_FALLBACK, 0, 1),
(
C_TARGET_C_BLOCK_SIZE,
TARGET_C_BLOCK_SIZE_MIN,
BLOCKSIZE_MAX as c_int,
),
(C_SRC_SIZE_HINT, 0, c_int::MAX),
(C_STABLE_IN_BUFFER, 0, 1),
(C_STABLE_OUT_BUFFER, 0, 1),
(C_BLOCK_DELIMITERS, 0, 1),
(C_BLOCK_SPLITTER_LEVEL, 0, 6),
(C_MAX_BLOCK_SIZE, BLOCKSIZE_MAX_MIN, BLOCKSIZE_MAX as c_int),
(C_REPCODE_RESOLUTION, PS_AUTO, PS_DISABLE),
];
for (param, lower, upper) in expected {
let bounds = bounds_for(param, single_threaded);
assert_eq!(bounds.error, 0, "param {param} returned an error");
assert_eq!((bounds.lowerBound, bounds.upperBound), (lower, upper));
assert!(within_bounds_for(param, lower, single_threaded));
assert!(within_bounds_for(param, upper, single_threaded));
if lower > c_int::MIN {
assert!(!within_bounds_for(param, lower - 1, single_threaded));
}
if upper < c_int::MAX {
assert!(!within_bounds_for(param, upper + 1, single_threaded));
}
}
let unsupported = bounds_for(12345, single_threaded);
assert_eq!(
unsupported.error,
ERROR(ZstdErrorCode::ParameterUnsupported)
);
let multithreaded = BoundsConfig {
multithreaded: true,
nb_workers_max: 17,
job_size_max: 987_654_321,
};
assert_eq!(
(
bounds_for(C_NB_WORKERS, multithreaded).lowerBound,
bounds_for(C_NB_WORKERS, multithreaded).upperBound
),
(0, 17)
);
assert_eq!(
(
bounds_for(C_JOB_SIZE, multithreaded).lowerBound,
bounds_for(C_JOB_SIZE, multithreaded).upperBound
),
(0, 987_654_321)
);
assert_eq!(
(
bounds_for(C_OVERLAP_LOG, multithreaded).lowerBound,
bounds_for(C_OVERLAP_LOG, multithreaded).upperBound
),
(0, 9)
);
}
#[test]
fn migrated_clamp_bounds_clamps_and_reports_unsupported_parameters() {
let multithreaded = BoundsConfig {
multithreaded: true,
nb_workers_max: 17,
job_size_max: 987_654_321,
};
let mut value = -1;
assert_eq!(clamp_bounds_for(C_FORMAT, &mut value, multithreaded), 0);
assert_eq!(value, 0);
value = c_int::MAX;
assert_eq!(
clamp_bounds_for(C_TARGET_C_BLOCK_SIZE, &mut value, multithreaded),
0
);
assert_eq!(value, BLOCKSIZE_MAX as c_int);
value = c_int::MAX;
assert_eq!(clamp_bounds_for(C_NB_WORKERS, &mut value, multithreaded), 0);
assert_eq!(value, 17);
value = -1;
assert_eq!(clamp_bounds_for(C_JOB_SIZE, &mut value, multithreaded), 0);
assert_eq!(value, 0);
value = -1;
assert_eq!(
clamp_bounds_for(12345, &mut value, multithreaded),
ERROR(ZstdErrorCode::ParameterUnsupported)
);
assert_eq!(value, -1);
}
#[test]
fn advanced_init_resolves_policy_and_resets_internal_fields() {
let cases = [