lzfse_rust/encode/
history.rs

1use crate::encode::constants::{Q1, Q3};
2use crate::encode::MatchUnit;
3use crate::types::Idx;
4
5use std::ops::Deref;
6
7#[cfg(test)]
8use crate::encode::constants::Q2;
9
10pub const HASH_BITS: u32 = 14;
11
12// Aligned/ power of two values. Minimum 4.
13pub const HASH_WIDTH: usize = 4;
14
15pub struct HistoryTable(Box<[History]>, #[cfg(test)] Ward);
16
17impl HistoryTable {
18    const SIZE: usize = 1 << HASH_BITS;
19
20    /// Push a new history item.
21    ///
22    /// Items must be pushed in strict sequential order and must not wrap around.
23    #[inline(always)]
24    pub fn push<M: MatchUnit>(&mut self, item: Item) -> History {
25        #[cfg(test)]
26        debug_assert!(self.1.push(item));
27        let queue = self.get_mut::<M>(item.val);
28        let copy = *queue;
29        queue.push(item);
30        copy
31    }
32
33    #[inline(always)]
34    fn get_mut<M: MatchUnit>(&mut self, val: u32) -> &mut History {
35        unsafe { self.0.get_unchecked_mut(index::<M>(val)) }
36    }
37
38    /// Clamp all history `idx` values to a maximum of `idx - Q1` with respect to the specified
39    /// `idx` value.
40    ///
41    /// Clamping removes old items which might otherwise wrap back around and corrupt our
42    /// history.
43    ///
44    /// Allows us to push a maximum of 0x8000_0000 items, with sequential `idx` values, without
45    /// additional clamping.
46    #[cold]
47    pub fn clamp(&mut self, idx: Idx) {
48        #[cfg(test)]
49        debug_assert!(self.1.clamp(idx));
50        self.0.iter_mut().for_each(|u| u.clamp_rebias(idx, 0));
51    }
52
53    /// Clamp all history `idx` values to a maximum of `idx - Q1` with respect to the specified
54    /// `idx` value and then subtract the specified `delta` offset.
55    ///  
56    /// Clamping removes old items which might otherwise wrap back around and corrupt our
57    /// history.
58    ///
59    /// Allows us to push a maximum of 0x8000_0000 items, with sequential `idx - delta ` values,
60    /// without additional clamping.
61    #[cold]
62    pub fn clamp_rebias(&mut self, idx: Idx, delta: u32) {
63        #[cfg(test)]
64        debug_assert!(self.1.clamp_rebias(idx, delta));
65        self.0.iter_mut().for_each(|u| u.clamp_rebias(idx, delta));
66    }
67
68    /// All history `idx` values are set to `Idx::Q3`.
69    ///
70    /// Allows us to push a maximum of 0x8000_0000 items, with sequential `idx` values starting from
71    /// `Idx::Q0`, without additional clamping.
72    pub fn reset(&mut self) {
73        self.reset_with_idx(Idx::Q0)
74    }
75
76    #[cold]
77    pub fn reset_with_idx(&mut self, idx: Idx) {
78        self.0.iter_mut().for_each(|u| *u = History::new(Item::new(0, idx - Q1)));
79        #[cfg(test)]
80        {
81            self.1 = Ward::new(idx);
82        }
83    }
84}
85
86impl Default for HistoryTable {
87    fn default() -> Self {
88        Self(
89            vec![History::default(); Self::SIZE].into_boxed_slice(),
90            #[cfg(test)]
91            Ward::default(),
92        )
93    }
94}
95
96/// Ordered (checked on push) history fixed length item queue.
97/// [ 0, 1, 2, ... , HASH_WIDTH - 1 ]
98///   ^ new          ^ old
99#[repr(align(32))]
100#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
101pub struct History([Item; HASH_WIDTH]);
102
103impl History {
104    #[inline(always)]
105    const fn new(item: Item) -> Self {
106        Self([item; HASH_WIDTH])
107    }
108
109    #[inline(always)]
110    fn push(&mut self, item: Item) {
111        debug_assert!(!is_wrapping(item.idx, self.0[HASH_WIDTH - 1].idx));
112        let mut i = HASH_WIDTH - 1;
113        while i != 0 {
114            self.0[i] = self.0[i - 1];
115            i -= 1;
116        }
117        self.0[0] = item;
118    }
119
120    #[inline(always)]
121    fn clamp_rebias(&mut self, idx: Idx, delta: u32) {
122        for item in self.0.iter_mut() {
123            debug_assert!(!is_wrapping(idx, item.idx));
124            if (idx - item.idx) as u32 > Q1 {
125                item.idx = idx - Q1 - delta;
126            } else {
127                item.idx -= delta;
128            }
129        }
130    }
131}
132
133impl Deref for History {
134    type Target = [Item];
135
136    #[inline(always)]
137    fn deref(&self) -> &Self::Target {
138        &self.0
139    }
140}
141
142#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
143#[repr(C, align(8))]
144pub struct Item {
145    pub val: u32,
146    pub idx: Idx,
147}
148
149impl Item {
150    #[inline(always)]
151    pub const fn new(val: u32, idx: Idx) -> Self {
152        Self { val, idx }
153    }
154}
155
156/// Ward. Test/ debug assistant.
157#[cfg(test)]
158#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
159struct Ward {
160    opt_val: Option<u32>,
161    idx: Idx,
162    clamp: Idx,
163}
164
165// Implementation notes:
166//
167// These checks enforce History usage constraints.
168//
169// They may be implemented/ duplicated in callees, however it's simple to add them here.
170
171#[cfg(test)]
172impl Ward {
173    fn new(idx: Idx) -> Self {
174        Self { opt_val: None, idx, clamp: idx + Q2 }
175    }
176
177    fn push(&mut self, item: Item) -> bool {
178        if self.idx != item.idx || self.clamp == item.idx {
179            return false;
180        }
181        if let Some(val) = self.opt_val {
182            #[cfg(target_endian = "little")]
183            if (val >> 8) != item.val & 0x00FF_FFFF {
184                return false;
185            }
186            #[cfg(target_endian = "big")]
187            if (val << 8) != item.val & 0xFFFF_FF00 {
188                return false;
189            }
190        }
191        self.opt_val = Some(item.val);
192        self.idx += 1;
193        true
194    }
195
196    fn clamp(&mut self, idx: Idx) -> bool {
197        if self.idx == idx {
198            self.clamp = idx + Q2;
199            true
200        } else {
201            false
202        }
203    }
204
205    fn clamp_rebias(&mut self, idx: Idx, delta: u32) -> bool {
206        if self.idx == idx {
207            self.idx -= delta;
208            self.clamp = idx + Q2;
209            true
210        } else {
211            false
212        }
213    }
214}
215
216// `a - b` >= Q3
217fn is_wrapping(a: Idx, b: Idx) -> bool {
218    ((a - b) as u32) >= Q3
219}
220
221#[inline(always)]
222fn index<M: MatchUnit>(u: u32) -> usize {
223    (M::hash_u(u) >> (32 - HASH_BITS)) as usize
224}
225
226#[cfg(test)]
227mod tests {
228    use crate::encode::constants::{Q0, Q2};
229    use crate::encode::dummy::Dummy;
230
231    use super::*;
232
233    #[test]
234    #[should_panic]
235    fn default_warden() {
236        let mut table = HistoryTable::default();
237        table.push::<Dummy>(Item::new(0, Idx::Q0));
238    }
239
240    #[test]
241    #[ignore = "expensive"]
242    fn clamp_rebias() {
243        let mut table = HistoryTable::default();
244        table.reset_with_idx(Idx::Q0);
245        for val in 0..Q2 {
246            // Bypass Ward protection as item values are not sequential.
247            table.get_mut::<Dummy>(val).push(Item::new(val, val.into()));
248            table.1.idx += 1;
249        }
250        table.clamp(Idx::Q2);
251        for history in table.0.iter() {
252            for &Item { val, idx } in history.0.iter() {
253                if val <= Q1 {
254                    assert_eq!(idx, Idx::Q1);
255                } else {
256                    assert!(!is_wrapping(idx, Idx::Q3));
257                    assert!((Idx::Q2 - idx) as u32 <= Q1);
258                }
259            }
260        }
261    }
262
263    #[test]
264    #[ignore = "expensive"]
265    fn clamp_rebias_q1() {
266        let mut table = HistoryTable::default();
267        table.reset_with_idx(Idx::Q0);
268        for val in 0..Q2 {
269            // Bypass Ward protection as item values are not sequential.
270            table.get_mut::<Dummy>(val).push(Item::new(val, val.into()));
271            table.1.idx += 1;
272        }
273        table.clamp_rebias(Idx::Q2, Q1);
274        for history in table.0.iter() {
275            for &Item { val, idx } in history.0.iter() {
276                if val <= Q1 {
277                    assert_eq!(idx, Idx::Q0);
278                } else {
279                    assert!(!is_wrapping(idx, Idx::Q3));
280                    assert!((Idx::Q1 - idx) as u32 <= Q1);
281                }
282            }
283        }
284    }
285
286    #[test]
287    fn history_clamp_rebias_q0_0() {
288        let mut history = History::default();
289        history.clamp_rebias(Idx::Q0, 0);
290        assert_eq!(history, History([Item::new(0, Idx::Q0); 4]));
291    }
292
293    #[test]
294    fn history_clamp_rebias_q0_q1() {
295        let mut history = History::default();
296        history.clamp_rebias(Idx::Q0, Q1);
297        assert_eq!(history, History([Item::new(0, Idx::Q0 - Q1); 4]));
298    }
299
300    #[test]
301    fn history_clamp_rebias_q1_0() {
302        let mut history = History::default();
303        history.clamp_rebias(Idx::Q1, 0);
304        assert_eq!(history, History([Item::new(0, Idx::Q0); 4]));
305    }
306
307    #[test]
308    fn history_clamp_rebias_q1_q1() {
309        let mut history = History::default();
310        history.clamp_rebias(Idx::Q1, Q1);
311        assert_eq!(history, History([Item::new(0, Idx::Q0 - Q1); 4]));
312    }
313
314    #[test]
315    fn history_clamp_rebias_q2_0() {
316        let mut history = History::default();
317        history.clamp_rebias(Idx::Q2, 0);
318        assert_eq!(history, History([Item::new(0, Idx::Q1); 4]));
319    }
320
321    #[test]
322    fn history_clamp_rebias_q2_q1() {
323        let mut history = History::default();
324        history.clamp_rebias(Idx::Q2, Q1);
325        assert_eq!(history, History([Item::new(0, Idx::Q1 - Q1); 4]));
326    }
327
328    #[test]
329    #[should_panic]
330    fn history_clamp_rebias_q3_0() {
331        let mut history = History::default();
332        history.clamp_rebias(Idx::Q3, 0);
333    }
334
335    #[test]
336    #[should_panic]
337    fn history_clamp_rebias_q3_q1() {
338        let mut history = History::default();
339        history.clamp_rebias(Idx::Q3, Q1);
340    }
341
342    #[test]
343    #[should_panic]
344    fn history_push_q0_sub_1() {
345        History::default().push(Item::new(0, Idx::Q0 - 1));
346    }
347
348    #[test]
349    fn history_push_q0() {
350        History::default().push(Item::new(0, Idx::Q0));
351    }
352
353    #[test]
354    fn history_push_q3_sub_1() {
355        History::default().push(Item::new(0, Idx::Q3 - 1));
356    }
357
358    #[test]
359    #[should_panic]
360    fn history_push_q3() {
361        History::default().push(Item::new(0, Idx::Q3));
362    }
363
364    #[test]
365    fn is_wrapping_q0_sub_1() {
366        (0..4).map(|u| Idx::new(u * Q1)).for_each(|idx| assert!(is_wrapping(idx - Q0 - 1, idx)));
367    }
368
369    #[test]
370    fn is_wrapping_q1() {
371        (0..4).map(|u| Idx::new(u * Q1)).for_each(|idx| assert!(is_wrapping(idx - Q1, idx)));
372    }
373
374    #[test]
375    fn not_wrapping_q1_sub_1() {
376        (0..4).map(|u| Idx::new(u * Q1)).for_each(|idx| assert!(!is_wrapping(idx - Q1 - 1, idx)));
377    }
378
379    #[test]
380    fn not_is_wrapping_q0() {
381        (0..4).map(|u| Idx::new(u * Q1)).for_each(|idx| assert!(!is_wrapping(idx - Q0, idx)));
382    }
383}