From bbe3a5dd74ec341ccbbc70b25285a9cc4508beec Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 20 Jul 2026 04:49:35 +0200 Subject: [PATCH] refactor(fileio): move compression parameter policy to Rust Move the non-threaded compression parameter sequence and adaptive window policy out of FIO_createCResources. C continues to own context and resource allocation, dictionary and pool setup, and the multithread-specific settings; Rust drives the ordered parameter callbacks and stops on the first error. The projection mirrors the C ABI and has focused order and short-circuit tests. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml create_c_resources --lib - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; make -j1 --- programs/fileio.c | 86 ++++++++----- rust/src/fileio_prefs.rs | 271 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 32 deletions(-) diff --git a/programs/fileio.c b/programs/fileio.c index 0acef6e8b..4c3626bc5 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1729,6 +1729,51 @@ static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs, } } +typedef size_t (*FIO_rust_createCResources_set_parameter_f)(void* context, + int parameter, + int value); +typedef int (*FIO_rust_createCResources_is_error_f)(size_t result); +typedef struct { + void* callbackContext; + const FIO_prefs_t* prefs; + ZSTD_compressionParameters comprParams; + int cLevel; + FIO_rust_createCResources_set_parameter_f setParameter; + FIO_rust_createCResources_is_error_f isError; +} FIO_rust_createCResourcesState; +typedef char FIO_rust_create_c_resources_state_context_offset[ + (offsetof(FIO_rust_createCResourcesState, callbackContext) == 0) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_prefs_offset[ + (offsetof(FIO_rust_createCResourcesState, prefs) == sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_params_offset[ + (offsetof(FIO_rust_createCResourcesState, comprParams) == 2 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_level_offset[ + (offsetof(FIO_rust_createCResourcesState, cLevel) + == 2 * sizeof(void*) + sizeof(ZSTD_compressionParameters)) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_set_parameter_offset[ + (offsetof(FIO_rust_createCResourcesState, setParameter) + == ((2 * sizeof(void*) + sizeof(ZSTD_compressionParameters) + sizeof(int) + + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_is_error_offset[ + (offsetof(FIO_rust_createCResourcesState, isError) + == offsetof(FIO_rust_createCResourcesState, setParameter) + sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_create_c_resources_state_size[ + (sizeof(FIO_rust_createCResourcesState) + == offsetof(FIO_rust_createCResourcesState, setParameter) + 2 * sizeof(void*)) ? 1 : -1]; +size_t FIO_rust_createCResources(const FIO_rust_createCResourcesState* state); + +static size_t FIO_rust_createCResources_setParameter(void* context, + int parameter, + int value) +{ + return ZSTD_CCtx_setParameter((ZSTD_CCtx*)context, (ZSTD_cParameter)parameter, value); +} + +static int FIO_rust_createCResources_isError(size_t result) +{ + return ZSTD_isError(result); +} + static cRess_t FIO_createCResources(FIO_prefs_t* const prefs, const char* dictFileName, unsigned long long const maxSrcFileSize, int cLevel, ZSTD_compressionParameters comprParams) { @@ -1766,39 +1811,16 @@ static cRess_t FIO_createCResources(FIO_prefs_t* const prefs, EXM_THROW(32, "allocation error : can't create dictBuffer"); ress.dictFileName = dictFileName; - if (prefs->adaptiveMode && !prefs->ldmFlag && !comprParams.windowLog) - comprParams.windowLog = ADAPT_WINDOWLOG_DEFAULT; + FIO_rust_createCResourcesState const policy = { + ress.cctx, + prefs, + comprParams, + cLevel, + FIO_rust_createCResources_setParameter, + FIO_rust_createCResources_isError + }; + CHECK( FIO_rust_createCResources(&policy) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_contentSizeFlag, prefs->contentSize) ); /* always enable content size when available (note: supposed to be default) */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_dictIDFlag, prefs->dictIDFlag) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_checksumFlag, prefs->checksumFlag) ); - /* compression level */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, cLevel) ); - /* max compressed block size */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetCBlockSize, (int)prefs->targetCBlockSize) ); - /* source size hint */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_srcSizeHint, (int)prefs->srcSizeHint) ); - /* long distance matching */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableLongDistanceMatching, prefs->ldmFlag) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashLog, prefs->ldmHashLog) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmMinMatch, prefs->ldmMinMatch) ); - if (prefs->ldmBucketSizeLog != FIO_LDM_PARAM_NOTSET) { - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmBucketSizeLog, prefs->ldmBucketSizeLog) ); - } - if (prefs->ldmHashRateLog != FIO_LDM_PARAM_NOTSET) { - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashRateLog, prefs->ldmHashRateLog) ); - } - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_useRowMatchFinder, prefs->useRowMatchFinder)); - /* compression parameters */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_windowLog, (int)comprParams.windowLog) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_chainLog, (int)comprParams.chainLog) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_hashLog, (int)comprParams.hashLog) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_searchLog, (int)comprParams.searchLog) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_minMatch, (int)comprParams.minMatch) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetLength, (int)comprParams.targetLength) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_strategy, (int)comprParams.strategy) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_literalCompressionMode, (int)prefs->literalCompressionMode) ); - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableDedicatedDictSearch, 1) ); /* multi-threading */ #ifdef ZSTD_MULTITHREAD DISPLAYLEVEL(5,"set nb workers = %u \n", prefs->nbWorkers); diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index f3da5b522..839c30bc7 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -31,10 +31,32 @@ const FIO_MULTI_FILES_ACTION_CONFIRM: c_int = 5; const FIO_LIST_INFO_SUCCESS: c_int = 0; const FIO_LIST_INFO_FRAME_ERROR: c_int = 1; const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX; +const FIO_ADAPT_WINDOWLOG_DEFAULT: c_uint = 23; const ZSTD_WINDOWLOG_MIN: u32 = 10; const ZSTD_WINDOWLOG_MAX: u32 = if size_of::() == 4 { 30 } else { 31 }; const ZSTD_BTLAZY2: c_int = 6; const ZSTD_BTOPT: c_int = 7; +const FIO_ZSTD_C_WINDOW_LOG: c_int = 101; +const FIO_ZSTD_C_HASH_LOG: c_int = 102; +const FIO_ZSTD_C_CHAIN_LOG: c_int = 103; +const FIO_ZSTD_C_SEARCH_LOG: c_int = 104; +const FIO_ZSTD_C_MIN_MATCH: c_int = 105; +const FIO_ZSTD_C_TARGET_LENGTH: c_int = 106; +const FIO_ZSTD_C_STRATEGY: c_int = 107; +const FIO_ZSTD_C_COMPRESSION_LEVEL: c_int = 100; +const FIO_ZSTD_C_TARGET_C_BLOCK_SIZE: c_int = 130; +const FIO_ZSTD_C_ENABLE_LDM: c_int = 160; +const FIO_ZSTD_C_LDM_HASH_LOG: c_int = 161; +const FIO_ZSTD_C_LDM_MIN_MATCH: c_int = 162; +const FIO_ZSTD_C_LDM_BUCKET_SIZE_LOG: c_int = 163; +const FIO_ZSTD_C_LDM_HASH_RATE_LOG: c_int = 164; +const FIO_ZSTD_C_CONTENT_SIZE_FLAG: c_int = 200; +const FIO_ZSTD_C_CHECKSUM_FLAG: c_int = 201; +const FIO_ZSTD_C_DICT_ID_FLAG: c_int = 202; +const FIO_ZSTD_C_LITERAL_COMPRESSION_MODE: c_int = 1002; +const FIO_ZSTD_C_SRC_SIZE_HINT: c_int = 1004; +const FIO_ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH: c_int = 1005; +const FIO_ZSTD_C_USE_ROW_MATCH_FINDER: c_int = 1011; static STDOUT_MARK: &[u8] = b"/*stdout*\\\0"; static mut COMPRESSED_NAME_CAPACITY: usize = 0; @@ -147,6 +169,137 @@ pub unsafe extern "C" fn FIO_rust_freeCResources( } } +pub type FIO_createCResourcesSetParameterFn = + unsafe extern "C" fn(*mut c_void, c_int, c_int) -> usize; +pub type FIO_createCResourcesIsErrorFn = unsafe extern "C" fn(usize) -> c_int; + +/// C-owned compression-resource parameter policy projection. +/// +/// Rust owns the ordered, short-circuiting parameter policy. C keeps the +/// `ZSTD_CCtx` layout private and exposes only the parameter callback; resource +/// allocation, dictionary/pool setup, and dictionary loading remain in C. +#[repr(C)] +pub struct FIO_rust_createCResourcesState { + callback_context: *mut c_void, + prefs: *const FIO_prefs_t, + compression_params: FIO_compressionParameters, + compression_level: c_int, + set_parameter: FIO_createCResourcesSetParameterFn, + is_error: FIO_createCResourcesIsErrorFn, +} + +const _: () = { + let callback_offset = (2 * size_of::() + + size_of::() + + size_of::()) + .div_ceil(size_of::()) + * size_of::(); + assert!(offset_of!(FIO_rust_createCResourcesState, callback_context) == 0); + assert!(offset_of!(FIO_rust_createCResourcesState, prefs) == size_of::()); + assert!( + offset_of!(FIO_rust_createCResourcesState, compression_params) + == 2 * size_of::() + ); + assert!( + offset_of!(FIO_rust_createCResourcesState, compression_level) + == 2 * size_of::() + size_of::() + ); + assert!(offset_of!(FIO_rust_createCResourcesState, set_parameter) == callback_offset); + assert!(offset_of!(FIO_rust_createCResourcesState, is_error) == callback_offset + size_of::()); + assert!( + size_of::() == callback_offset + 2 * size_of::() + ); +}; + +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_createCResources( + state: *const FIO_rust_createCResourcesState, +) -> usize { + if state.is_null() { + return usize::MAX; + } + let state = unsafe { &*state }; + if state.prefs.is_null() { + return usize::MAX; + } + let prefs = unsafe { &*state.prefs }; + let mut compression_params = state.compression_params; + if prefs.adaptiveMode != 0 && prefs.ldmFlag == 0 && compression_params.windowLog == 0 { + compression_params.windowLog = FIO_ADAPT_WINDOWLOG_DEFAULT; + } + + let parameters = [ + (FIO_ZSTD_C_CONTENT_SIZE_FLAG, prefs.contentSize), + (FIO_ZSTD_C_DICT_ID_FLAG, prefs.dictIDFlag), + (FIO_ZSTD_C_CHECKSUM_FLAG, prefs.checksumFlag), + (FIO_ZSTD_C_COMPRESSION_LEVEL, state.compression_level), + ( + FIO_ZSTD_C_TARGET_C_BLOCK_SIZE, + prefs.targetCBlockSize as c_int, + ), + (FIO_ZSTD_C_SRC_SIZE_HINT, prefs.srcSizeHint), + (FIO_ZSTD_C_ENABLE_LDM, prefs.ldmFlag), + (FIO_ZSTD_C_LDM_HASH_LOG, prefs.ldmHashLog), + (FIO_ZSTD_C_LDM_MIN_MATCH, prefs.ldmMinMatch), + ]; + for (parameter, value) in parameters { + let result = unsafe { (state.set_parameter)(state.callback_context, parameter, value) }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + } + if prefs.ldmBucketSizeLog != FIO_LDM_PARAM_NOTSET { + let result = unsafe { + (state.set_parameter)( + state.callback_context, + FIO_ZSTD_C_LDM_BUCKET_SIZE_LOG, + prefs.ldmBucketSizeLog, + ) + }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + } + if prefs.ldmHashRateLog != FIO_LDM_PARAM_NOTSET { + let result = unsafe { + (state.set_parameter)( + state.callback_context, + FIO_ZSTD_C_LDM_HASH_RATE_LOG, + prefs.ldmHashRateLog, + ) + }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + } + + let parameters = [ + (FIO_ZSTD_C_USE_ROW_MATCH_FINDER, prefs.useRowMatchFinder), + (FIO_ZSTD_C_WINDOW_LOG, compression_params.windowLog as c_int), + (FIO_ZSTD_C_CHAIN_LOG, compression_params.chainLog as c_int), + (FIO_ZSTD_C_HASH_LOG, compression_params.hashLog as c_int), + (FIO_ZSTD_C_SEARCH_LOG, compression_params.searchLog as c_int), + (FIO_ZSTD_C_MIN_MATCH, compression_params.minMatch as c_int), + ( + FIO_ZSTD_C_TARGET_LENGTH, + compression_params.targetLength as c_int, + ), + (FIO_ZSTD_C_STRATEGY, compression_params.strategy), + ( + FIO_ZSTD_C_LITERAL_COMPRESSION_MODE, + prefs.literalCompressionMode, + ), + (FIO_ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH, 1), + ]; + for (parameter, value) in parameters { + let result = unsafe { (state.set_parameter)(state.callback_context, parameter, value) }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + } + 0 +} + pub type FIO_listMultipleFilesIsStdinFn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_listMultipleFilesDisplayFn = unsafe extern "C" fn(*mut c_void); pub type FIO_listMultipleFilesListFileFn = unsafe extern "C" fn( @@ -1957,6 +2110,124 @@ mod tests { assert_eq!(context.events, ["dict", "write", "read", "cctx"]); } + #[derive(Default)] + struct CreateCResourcesTestState { + events: Vec<(c_int, c_int)>, + fail_parameter: Option, + } + + unsafe extern "C" fn create_c_resources_set_parameter_test( + context: *mut c_void, + parameter: c_int, + value: c_int, + ) -> usize { + let state = unsafe { &mut *context.cast::() }; + state.events.push((parameter, value)); + if state.fail_parameter == Some(parameter) { + usize::MAX + } else { + 0 + } + } + + unsafe extern "C" fn create_c_resources_is_error_test(result: usize) -> c_int { + c_int::from(result == usize::MAX) + } + + fn create_c_resources_test_prefs() -> FIO_prefs_t { + let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; + prefs.contentSize = 1; + prefs.dictIDFlag = 1; + prefs.checksumFlag = 1; + prefs.adaptiveMode = 1; + prefs.ldmBucketSizeLog = FIO_LDM_PARAM_NOTSET; + prefs.ldmHashRateLog = FIO_LDM_PARAM_NOTSET; + prefs.targetCBlockSize = 17; + prefs.srcSizeHint = 19; + prefs + } + + fn create_c_resources_test_state( + prefs: &FIO_prefs_t, + context: &mut CreateCResourcesTestState, + ) -> FIO_rust_createCResourcesState { + FIO_rust_createCResourcesState { + callback_context: context as *mut CreateCResourcesTestState as *mut c_void, + prefs, + compression_params: FIO_compressionParameters { + windowLog: 0, + chainLog: 1, + hashLog: 2, + searchLog: 3, + minMatch: 4, + targetLength: 5, + strategy: 6, + }, + compression_level: 7, + set_parameter: create_c_resources_set_parameter_test, + is_error: create_c_resources_is_error_test, + } + } + + #[test] + fn create_c_resources_preserves_parameter_order_and_adaptive_policy() { + let prefs = create_c_resources_test_prefs(); + let mut context = CreateCResourcesTestState::default(); + let state = create_c_resources_test_state(&prefs, &mut context); + + let result = unsafe { FIO_rust_createCResources(&state) }; + + assert_eq!(result, 0); + assert_eq!( + context.events, + vec![ + (FIO_ZSTD_C_CONTENT_SIZE_FLAG, 1), + (FIO_ZSTD_C_DICT_ID_FLAG, 1), + (FIO_ZSTD_C_CHECKSUM_FLAG, 1), + (FIO_ZSTD_C_COMPRESSION_LEVEL, 7), + (FIO_ZSTD_C_TARGET_C_BLOCK_SIZE, 17), + (FIO_ZSTD_C_SRC_SIZE_HINT, 19), + (FIO_ZSTD_C_ENABLE_LDM, 0), + (FIO_ZSTD_C_LDM_HASH_LOG, 0), + (FIO_ZSTD_C_LDM_MIN_MATCH, 0), + (FIO_ZSTD_C_USE_ROW_MATCH_FINDER, 0), + (FIO_ZSTD_C_WINDOW_LOG, FIO_ADAPT_WINDOWLOG_DEFAULT as c_int), + (FIO_ZSTD_C_CHAIN_LOG, 1), + (FIO_ZSTD_C_HASH_LOG, 2), + (FIO_ZSTD_C_SEARCH_LOG, 3), + (FIO_ZSTD_C_MIN_MATCH, 4), + (FIO_ZSTD_C_TARGET_LENGTH, 5), + (FIO_ZSTD_C_STRATEGY, 6), + (FIO_ZSTD_C_LITERAL_COMPRESSION_MODE, 0), + (FIO_ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH, 1), + ] + ); + } + + #[test] + fn create_c_resources_short_circuits_on_parameter_error() { + let prefs = create_c_resources_test_prefs(); + let mut context = CreateCResourcesTestState { + fail_parameter: Some(FIO_ZSTD_C_TARGET_C_BLOCK_SIZE), + ..CreateCResourcesTestState::default() + }; + let state = create_c_resources_test_state(&prefs, &mut context); + + let result = unsafe { FIO_rust_createCResources(&state) }; + + assert_eq!(result, usize::MAX); + assert_eq!( + context.events, + vec![ + (FIO_ZSTD_C_CONTENT_SIZE_FLAG, 1), + (FIO_ZSTD_C_DICT_ID_FLAG, 1), + (FIO_ZSTD_C_CHECKSUM_FLAG, 1), + (FIO_ZSTD_C_COMPRESSION_LEVEL, 7), + (FIO_ZSTD_C_TARGET_C_BLOCK_SIZE, 17), + ] + ); + } + fn temporary_file_path(name: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!("zstd-fileio-prefs-{}-{name}", std::process::id())) }