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