Skip to main content

zrip_encode/
strategy.rs

1#![forbid(unsafe_code)]
2
3/// Match-finding strategy.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Strategy {
6    /// Single hash table (levels -8 through 2).
7    Fast,
8    /// Short + long hash tables (levels 3-4).
9    DFast,
10}
11
12/// Parameters for Long Distance Matching.
13#[derive(Debug, Clone, Copy)]
14pub struct LdmParams {
15    pub hash_log: u32,
16    pub bucket_size_log: u32,
17    pub min_match_length: u32,
18    pub hash_rate_log: u32,
19}
20
21impl LdmParams {
22    pub fn default_for_window_log(window_log: u32) -> Self {
23        let hash_log = 20u32.min(window_log.saturating_sub(1));
24        let hash_rate_log = window_log.saturating_sub(hash_log).max(7);
25        Self {
26            hash_log,
27            bucket_size_log: 4,
28            min_match_length: 64,
29            hash_rate_log,
30        }
31    }
32}
33
34/// Compression parameters for a specific level.
35///
36/// Obtain via [`level_params`] or construct directly for custom tuning.
37/// Pass to [`compress_with_params`](crate::compress_with_params).
38#[derive(Debug, Clone, Copy)]
39pub struct LevelParams {
40    pub strategy: Strategy,
41    pub window_log: u32,
42    pub hash_log: u32,
43    /// DFast short table log. Same as hashLog for Fast strategy.
44    pub chain_log: u32,
45    pub search_log: u32,
46    pub min_match: u32,
47    pub target_length: u32,
48    pub search_strength: u32,
49    pub force_raw_literals: bool,
50    #[cfg(feature = "ldm")]
51    pub ldm_params: Option<LdmParams>,
52}
53
54impl LevelParams {
55    #[must_use]
56    pub fn with_window_log(mut self, window_log: u32) -> Self {
57        self.window_log = window_log;
58        self
59    }
60
61    #[cfg(feature = "ldm")]
62    #[must_use]
63    pub fn with_ldm(mut self, params: LdmParams) -> Self {
64        self.ldm_params = Some(params);
65        self
66    }
67}
68
69/// Default compression level used when level 0 is requested.
70pub const DEFAULT_LEVEL: i32 = 1;
71
72/// Returns the compression parameters for a given level, or `None` if out of range.
73///
74/// Level 0 is treated as "library default" and maps to level 1.
75/// Uses the large-input (>256 KB) parameter tier.
76pub fn level_params(level: i32) -> Option<LevelParams> {
77    level_params_for_size(level, usize::MAX)
78}
79
80/// Returns the compression parameters for a given level, sized for `src_len`.
81///
82/// Uses fixed parameters per level with log values clamped down for small inputs.
83///
84/// Level 0 is treated as "library default" and maps to level 1.
85pub fn level_params_for_size(level: i32, src_len: usize) -> Option<LevelParams> {
86    let mut params = level_params_inner(level)?;
87    params.hash_log = params.hash_log.clamp(HASH_LOG_MIN, HASH_LOG_MAX);
88    params.chain_log = params.chain_log.clamp(HASH_LOG_MIN, HASH_LOG_MAX);
89    params.window_log = params.window_log.clamp(WINDOW_LOG_MIN, WINDOW_LOG_MAX);
90    if (2..usize::MAX).contains(&src_len) {
91        let src_log = 32 - ((src_len as u32) - 1).leading_zeros();
92        params.hash_log = params.hash_log.min(src_log).max(HASH_LOG_MIN);
93        params.chain_log = params.chain_log.min(src_log).max(HASH_LOG_MIN);
94        params.window_log = params.window_log.min(src_log);
95    }
96    // Large-input L-7 acceleration skips too aggressively on tiny text slices.
97    if level == -7 && src_len <= 16 * 1024 {
98        params.hash_log = params.hash_log.min(13);
99        params.chain_log = params.chain_log.min(13);
100        params.target_length = 6;
101    }
102    if level == 3 && (32 * 1024..=128 * 1024).contains(&src_len) {
103        params.search_strength = 7;
104    }
105    Some(params)
106}
107
108pub const HASH_LOG_MIN: u32 = 6;
109pub const HASH_LOG_MAX: u32 = 30;
110pub const WINDOW_LOG_MIN: u32 = 10;
111pub const WINDOW_LOG_MAX: u32 = 27;
112
113pub fn apply_raw_literals_size_override(params: &mut LevelParams, input_len: usize) {
114    if params.strategy != Strategy::Fast || params.force_raw_literals {
115        return;
116    }
117    if params.min_match < 5 || params.target_length != 7 {
118        return;
119    }
120    if input_len <= 16384 {
121        params.force_raw_literals = true;
122    }
123}
124
125pub(crate) fn use_custom_sequence_tables(params: &LevelParams, input_len: usize) -> bool {
126    if params.strategy == Strategy::Fast && params.min_match >= 5 && params.hash_log <= 13 {
127        return false;
128    }
129
130    if (32768..=zrip_core::frame::MAX_BLOCK_SIZE).contains(&input_len)
131        && params.strategy == Strategy::DFast
132        && params.min_match == 4
133        && params.target_length == 1
134        && params.search_strength < 5
135    {
136        return false;
137    }
138    true
139}
140
141/// Returns the maximum hash_log for a given level.
142/// Used by CompressContext to pre-allocate hash tables.
143pub fn max_hash_log(level: i32) -> Option<u32> {
144    let p = level_params_inner(level)?;
145    Some(p.hash_log.max(p.chain_log))
146}
147
148fn level_params_inner(level: i32) -> Option<LevelParams> {
149    Some(match level {
150        0 => return level_params_inner(DEFAULT_LEVEL),
151        -8 => LevelParams {
152            strategy: Strategy::Fast,
153            window_log: 19,
154            hash_log: 13,
155            chain_log: 13,
156            search_log: 0,
157            min_match: 5,
158            target_length: 7,
159            search_strength: 7,
160            force_raw_literals: true,
161            #[cfg(feature = "ldm")]
162            ldm_params: None,
163        },
164        -7 => LevelParams {
165            strategy: Strategy::Fast,
166            window_log: 19,
167            hash_log: 14,
168            chain_log: 14,
169            search_log: 0,
170            min_match: 5,
171            target_length: 9,
172            search_strength: 7,
173            force_raw_literals: false,
174            #[cfg(feature = "ldm")]
175            ldm_params: None,
176        },
177        -6 => LevelParams {
178            strategy: Strategy::Fast,
179            window_log: 19,
180            hash_log: 14,
181            chain_log: 14,
182            search_log: 0,
183            min_match: 5,
184            target_length: 7,
185            search_strength: 7,
186            force_raw_literals: false,
187            #[cfg(feature = "ldm")]
188            ldm_params: None,
189        },
190        -5 => LevelParams {
191            strategy: Strategy::Fast,
192            window_log: 19,
193            hash_log: 14,
194            chain_log: 14,
195            search_log: 0,
196            min_match: 5,
197            target_length: 6,
198            search_strength: 7,
199            force_raw_literals: false,
200            #[cfg(feature = "ldm")]
201            ldm_params: None,
202        },
203        -4 => LevelParams {
204            strategy: Strategy::Fast,
205            window_log: 19,
206            hash_log: 14,
207            chain_log: 14,
208            search_log: 0,
209            min_match: 5,
210            target_length: 5,
211            search_strength: 7,
212            force_raw_literals: false,
213            #[cfg(feature = "ldm")]
214            ldm_params: None,
215        },
216        -3 => LevelParams {
217            strategy: Strategy::Fast,
218            window_log: 19,
219            hash_log: 14,
220            chain_log: 14,
221            search_log: 0,
222            min_match: 5,
223            target_length: 4,
224            search_strength: 7,
225            force_raw_literals: false,
226            #[cfg(feature = "ldm")]
227            ldm_params: None,
228        },
229        -2 => LevelParams {
230            strategy: Strategy::Fast,
231            window_log: 19,
232            hash_log: 14,
233            chain_log: 14,
234            search_log: 0,
235            min_match: 5,
236            target_length: 3,
237            search_strength: 7,
238            force_raw_literals: false,
239            #[cfg(feature = "ldm")]
240            ldm_params: None,
241        },
242        -1 => LevelParams {
243            strategy: Strategy::Fast,
244            window_log: 19,
245            hash_log: 14,
246            chain_log: 14,
247            search_log: 0,
248            min_match: 5,
249            target_length: 2,
250            search_strength: 7,
251            force_raw_literals: false,
252            #[cfg(feature = "ldm")]
253            ldm_params: None,
254        },
255        1 => LevelParams {
256            strategy: Strategy::Fast,
257            window_log: 19,
258            hash_log: 14,
259            chain_log: 14,
260            search_log: 0,
261            min_match: 4,
262            target_length: 1,
263            search_strength: 8,
264            force_raw_literals: false,
265            #[cfg(feature = "ldm")]
266            ldm_params: None,
267        },
268        2 => LevelParams {
269            strategy: Strategy::Fast,
270            window_log: 20,
271            hash_log: 17,
272            chain_log: 17,
273            search_log: 0,
274            min_match: 4,
275            target_length: 1,
276            search_strength: 8,
277            force_raw_literals: false,
278            #[cfg(feature = "ldm")]
279            ldm_params: None,
280        },
281        3 => LevelParams {
282            strategy: Strategy::DFast,
283            window_log: 21,
284            hash_log: 18,
285            chain_log: 18,
286            search_log: 1,
287            min_match: 4,
288            target_length: 1,
289            search_strength: 5,
290            force_raw_literals: false,
291            #[cfg(feature = "ldm")]
292            ldm_params: None,
293        },
294        4 => LevelParams {
295            strategy: Strategy::DFast,
296            window_log: 24,
297            hash_log: 20,
298            chain_log: 20,
299            search_log: 0,
300            min_match: 4,
301            target_length: 1,
302            search_strength: 8,
303            force_raw_literals: false,
304            #[cfg(feature = "ldm")]
305            ldm_params: None,
306        },
307        _ => return None,
308    })
309}
310
311/// Options for large-window and LDM compression, orthogonal to level.
312///
313/// Pass to [`compress_opts`](crate::compress_opts) or
314/// [`FrameEncoder::with_options`](crate::streaming::FrameEncoder::with_options).
315#[derive(Debug, Clone, Default)]
316pub struct Options {
317    pub(crate) window_log: Option<u32>,
318    #[cfg_attr(not(feature = "ldm"), allow(dead_code))]
319    pub(crate) ldm: bool,
320}
321
322impl Options {
323    #[must_use]
324    pub fn window_log(mut self, log: u32) -> Self {
325        self.window_log = Some(log);
326        self
327    }
328
329    #[cfg(feature = "ldm")]
330    #[must_use]
331    pub fn ldm(mut self, enable: bool) -> Self {
332        self.ldm = enable;
333        self
334    }
335}
336
337pub fn apply_options(params: &mut LevelParams, opts: &Options) {
338    if let Some(wl) = opts.window_log {
339        params.window_log = wl.clamp(WINDOW_LOG_MIN, WINDOW_LOG_MAX);
340    }
341    #[cfg(feature = "ldm")]
342    if opts.ldm {
343        params.ldm_params = Some(LdmParams::default_for_window_log(params.window_log));
344    }
345}