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    if (2..usize::MAX).contains(&src_len) {
90        let src_log = 32 - ((src_len as u32) - 1).leading_zeros();
91        params.hash_log = params.hash_log.min(src_log).max(HASH_LOG_MIN);
92        params.chain_log = params.chain_log.min(src_log).max(HASH_LOG_MIN);
93        params.window_log = params.window_log.min(src_log);
94    }
95    // Large-input L-7 acceleration skips too aggressively on tiny text slices.
96    if level == -7 && src_len <= 16 * 1024 {
97        params.hash_log = params.hash_log.min(13);
98        params.chain_log = params.chain_log.min(13);
99        params.target_length = 6;
100    }
101    if level == 3 && (32 * 1024..=128 * 1024).contains(&src_len) {
102        params.search_strength = 7;
103    }
104    Some(params)
105}
106
107pub const HASH_LOG_MIN: u32 = 6;
108pub const HASH_LOG_MAX: u32 = 30;
109
110pub fn apply_raw_literals_size_override(params: &mut LevelParams, input_len: usize) {
111    if params.strategy != Strategy::Fast || params.force_raw_literals {
112        return;
113    }
114    if params.min_match < 5 || params.target_length != 7 {
115        return;
116    }
117    if input_len <= 16384 {
118        params.force_raw_literals = true;
119    }
120}
121
122pub(crate) fn use_custom_sequence_tables(params: &LevelParams, input_len: usize) -> bool {
123    if params.strategy == Strategy::Fast && params.min_match >= 5 && params.hash_log <= 13 {
124        return false;
125    }
126
127    if (32768..=zrip_core::frame::MAX_BLOCK_SIZE).contains(&input_len)
128        && params.strategy == Strategy::DFast
129        && params.min_match == 4
130        && params.target_length == 1
131        && params.search_strength < 5
132    {
133        return false;
134    }
135    true
136}
137
138/// Returns the maximum hash_log for a given level.
139/// Used by CompressContext to pre-allocate hash tables.
140pub fn max_hash_log(level: i32) -> Option<u32> {
141    let p = level_params_inner(level)?;
142    Some(p.hash_log.max(p.chain_log))
143}
144
145fn level_params_inner(level: i32) -> Option<LevelParams> {
146    Some(match level {
147        0 => return level_params_inner(DEFAULT_LEVEL),
148        -8 => LevelParams {
149            strategy: Strategy::Fast,
150            window_log: 19,
151            hash_log: 13,
152            chain_log: 13,
153            search_log: 0,
154            min_match: 5,
155            target_length: 7,
156            search_strength: 7,
157            force_raw_literals: true,
158            #[cfg(feature = "ldm")]
159            ldm_params: None,
160        },
161        -7 => LevelParams {
162            strategy: Strategy::Fast,
163            window_log: 19,
164            hash_log: 14,
165            chain_log: 14,
166            search_log: 0,
167            min_match: 5,
168            target_length: 9,
169            search_strength: 7,
170            force_raw_literals: false,
171            #[cfg(feature = "ldm")]
172            ldm_params: None,
173        },
174        -6 => LevelParams {
175            strategy: Strategy::Fast,
176            window_log: 19,
177            hash_log: 14,
178            chain_log: 14,
179            search_log: 0,
180            min_match: 5,
181            target_length: 7,
182            search_strength: 7,
183            force_raw_literals: false,
184            #[cfg(feature = "ldm")]
185            ldm_params: None,
186        },
187        -5 => LevelParams {
188            strategy: Strategy::Fast,
189            window_log: 19,
190            hash_log: 14,
191            chain_log: 14,
192            search_log: 0,
193            min_match: 5,
194            target_length: 6,
195            search_strength: 7,
196            force_raw_literals: false,
197            #[cfg(feature = "ldm")]
198            ldm_params: None,
199        },
200        -4 => LevelParams {
201            strategy: Strategy::Fast,
202            window_log: 19,
203            hash_log: 14,
204            chain_log: 14,
205            search_log: 0,
206            min_match: 5,
207            target_length: 5,
208            search_strength: 7,
209            force_raw_literals: false,
210            #[cfg(feature = "ldm")]
211            ldm_params: None,
212        },
213        -3 => LevelParams {
214            strategy: Strategy::Fast,
215            window_log: 19,
216            hash_log: 14,
217            chain_log: 14,
218            search_log: 0,
219            min_match: 5,
220            target_length: 4,
221            search_strength: 7,
222            force_raw_literals: false,
223            #[cfg(feature = "ldm")]
224            ldm_params: None,
225        },
226        -2 => LevelParams {
227            strategy: Strategy::Fast,
228            window_log: 19,
229            hash_log: 14,
230            chain_log: 14,
231            search_log: 0,
232            min_match: 5,
233            target_length: 3,
234            search_strength: 7,
235            force_raw_literals: false,
236            #[cfg(feature = "ldm")]
237            ldm_params: None,
238        },
239        -1 => LevelParams {
240            strategy: Strategy::Fast,
241            window_log: 19,
242            hash_log: 14,
243            chain_log: 14,
244            search_log: 0,
245            min_match: 5,
246            target_length: 2,
247            search_strength: 7,
248            force_raw_literals: false,
249            #[cfg(feature = "ldm")]
250            ldm_params: None,
251        },
252        1 => LevelParams {
253            strategy: Strategy::Fast,
254            window_log: 19,
255            hash_log: 14,
256            chain_log: 14,
257            search_log: 0,
258            min_match: 4,
259            target_length: 1,
260            search_strength: 8,
261            force_raw_literals: false,
262            #[cfg(feature = "ldm")]
263            ldm_params: None,
264        },
265        2 => LevelParams {
266            strategy: Strategy::Fast,
267            window_log: 20,
268            hash_log: 17,
269            chain_log: 17,
270            search_log: 0,
271            min_match: 4,
272            target_length: 1,
273            search_strength: 8,
274            force_raw_literals: false,
275            #[cfg(feature = "ldm")]
276            ldm_params: None,
277        },
278        3 => LevelParams {
279            strategy: Strategy::DFast,
280            window_log: 21,
281            hash_log: 18,
282            chain_log: 18,
283            search_log: 1,
284            min_match: 4,
285            target_length: 1,
286            search_strength: 5,
287            force_raw_literals: false,
288            #[cfg(feature = "ldm")]
289            ldm_params: None,
290        },
291        4 => LevelParams {
292            strategy: Strategy::DFast,
293            window_log: 24,
294            hash_log: 20,
295            chain_log: 20,
296            search_log: 0,
297            min_match: 4,
298            target_length: 1,
299            search_strength: 8,
300            force_raw_literals: false,
301            #[cfg(feature = "ldm")]
302            ldm_params: None,
303        },
304        _ => return None,
305    })
306}
307
308/// Options for large-window and LDM compression, orthogonal to level.
309///
310/// Pass to [`compress_opts`](crate::compress_opts) or
311/// [`FrameEncoder::with_options`](crate::streaming::FrameEncoder::with_options).
312#[derive(Debug, Clone, Default)]
313pub struct Options {
314    pub(crate) window_log: Option<u32>,
315    #[cfg_attr(not(feature = "ldm"), allow(dead_code))]
316    pub(crate) ldm: bool,
317}
318
319impl Options {
320    #[must_use]
321    pub fn window_log(mut self, log: u32) -> Self {
322        self.window_log = Some(log);
323        self
324    }
325
326    #[cfg(feature = "ldm")]
327    #[must_use]
328    pub fn ldm(mut self, enable: bool) -> Self {
329        self.ldm = enable;
330        self
331    }
332}
333
334pub fn apply_options(params: &mut LevelParams, opts: &Options) {
335    if let Some(wl) = opts.window_log {
336        params.window_log = wl;
337    }
338    #[cfg(feature = "ldm")]
339    if opts.ldm {
340        params.ldm_params = Some(LdmParams::default_for_window_log(params.window_log));
341    }
342}