diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 4c0df881f..ca5389f0b 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -4528,6 +4528,11 @@ int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx) { ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)opaqueCctx; ZSTD_CCtx_params const* const params = cctx ? &cctx->requestedParams : NULL; + ZSTD_compressionParameters const cParams = params + ? ZSTD_getCParams_internal(params->compressionLevel, + ZSTD_CONTENTSIZE_UNKNOWN, 0, + ZSTD_cpm_noAttachDict) + : (ZSTD_compressionParameters){ 0 }; if (params == NULL || params->format != ZSTD_f_zstd1 || params->fParams.contentSizeFlag == 0 @@ -4579,6 +4584,12 @@ int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx) || cctx->localDict.cdict != NULL) { return (-2147483647 - 1); } + /* The Rust frame leaf currently implements only the fast and double-fast + * match finders. Keep lazy and optimal strategies on the stateful path, + * which owns their strategy-specific match-table lifecycle. */ + if (cParams.strategy != ZSTD_fast && cParams.strategy != ZSTD_dfast) { + return (-2147483647 - 1); + } return params->compressionLevel; } diff --git a/rust/src/zstd_opt.rs b/rust/src/zstd_opt.rs index 5a68a6622..52ae4cb73 100644 --- a/rust/src/zstd_opt.rs +++ b/rust/src/zstd_opt.rs @@ -446,7 +446,10 @@ unsafe fn downscale_stats( let mut sum: u32 = 0; for index in 0..=last_index { let old = unsafe { *table.add(index) }; - let value = u32::from(base_one) + (old >> shift); + /* `base_0possible` still gives every observed symbol a count of one. + * Only symbols which were absent from the source may remain at zero. */ + let base = if base_one { 1 } else { u32::from(old != 0) }; + let value = base + (old >> shift); unsafe { *table.add(index) = value }; sum = sum.wrapping_add(value); } @@ -1628,7 +1631,6 @@ unsafe fn compress_block_opt_generic( *reps.add(2) = path_rep[2]; } resolved_early = true; - cur = last_pos; break; } @@ -1675,7 +1677,7 @@ unsafe fn compress_block_opt_generic( cur += 1; } - if cur == last_pos && !resolved_early { + if !resolved_early { /* The previous loop reached its end without an early path. */ last_stretch = unsafe { *opt.add(last_pos as usize) }; let path_rep = unsafe { [*reps, *reps.add(1), *reps.add(2)] }; @@ -1736,9 +1738,19 @@ unsafe fn init_stats_ultra( *state.window_dict_limit = (*state.window_dict_limit).wrapping_add(src_size as u32); *state.window_low_limit = *state.window_dict_limit; *state.next_to_update = *state.window_dict_limit; + refresh_window_projection(state); } } +unsafe fn refresh_window_projection(state: &mut ZSTD_RustOptState) { + /* The C implementation rebuilds its projected match state for the + * second pass. Refresh the copied window fields before reusing this + * Rust projection for that pass. */ + state.base = unsafe { *state.window_base }; + state.dict_limit = unsafe { *state.window_dict_limit }; + state.low_limit = unsafe { *state.window_low_limit }; +} + #[no_mangle] pub unsafe extern "C" fn ZSTD_rust_opt_updateTree( state: *mut ZSTD_RustOptState, @@ -1834,3 +1846,64 @@ pub unsafe extern "C" fn ZSTD_rust_compressBlock_btultra2( ) } } + +#[cfg(test)] +mod tests { + use super::{downscale_stats, refresh_window_projection, ZSTD_RustOptState}; + use std::ptr; + + #[test] + fn ultra2_window_projection_tracks_the_second_pass_window() { + let mut base = 0x2000usize as *const u8; + let mut dict_limit = 128_u32; + let mut low_limit = 128_u32; + let mut next_to_update = 128_u32; + let mut state = ZSTD_RustOptState { + hash_table: ptr::null_mut(), + chain_table: ptr::null_mut(), + base: 0x1000usize as *const u8, + dict_base: ptr::null(), + dict_limit: 0, + low_limit: 0, + loaded_dict_end: 0, + next_to_update: &mut next_to_update, + hash_log: 0, + chain_log: 0, + search_log: 0, + window_log: 0, + use_c_predict: 0, + hash_table3: ptr::null_mut(), + hash_log3: 0, + next_src: ptr::null(), + min_match: 0, + target_length: 0, + opt: ptr::null_mut(), + dict_match_state: ptr::null(), + ldm_seq_store: ptr::null(), + window_base: &mut base, + window_dict_limit: &mut dict_limit, + window_low_limit: &mut low_limit, + huf_ctable: ptr::null(), + huf_repeat_valid: 0, + fse_litlength_ctable: ptr::null(), + fse_matchlength_ctable: ptr::null(), + fse_offcode_ctable: ptr::null(), + }; + + unsafe { refresh_window_projection(&mut state) }; + + assert_eq!(state.base, base); + assert_eq!(state.dict_limit, dict_limit); + assert_eq!(state.low_limit, low_limit); + } + + #[test] + fn downscale_stats_preserves_observed_symbols_with_base_zero_possible() { + let mut frequencies = [0_u32, 1, 255, 256, 511]; + + let sum = unsafe { downscale_stats(frequencies.as_mut_ptr(), 4, 8, false) }; + + assert_eq!(frequencies, [0, 1, 1, 2, 2]); + assert_eq!(sum, 6); + } +}