Skip to main content

rapidgzip_core/parallel/
marker.rs

1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4/// DEFLATE history-window size.
5pub const WINDOW_SIZE: usize = 32 * 1024;
6
7/// A speculative decoded symbol.
8///
9/// Values `0..=255` are literals. Values `32768..=65535` refer to a byte in
10/// the predecessor window, ordered from oldest to newest.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12#[repr(transparent)]
13pub struct Symbol(pub(crate) u16);
14
15impl Symbol {
16    /// Creates a literal byte symbol.
17    pub const fn literal(byte: u8) -> Self {
18        Self(byte as u16)
19    }
20
21    /// Creates a reference into the predecessor window.
22    pub fn marker(index: usize) -> Result<Self, MarkerError> {
23        if index >= WINDOW_SIZE {
24            return Err(MarkerError::IndexOutOfRange(index));
25        }
26        Ok(Self((WINDOW_SIZE + index) as u16))
27    }
28
29    /// Returns the encoded symbol.
30    pub const fn encoded(self) -> u16 {
31        self.0
32    }
33
34    /// Returns the literal value, when this is not a marker.
35    pub const fn as_literal(self) -> Option<u8> {
36        if self.0 <= u8::MAX as u16 {
37            Some(self.0 as u8)
38        } else {
39            None
40        }
41    }
42
43    fn marker_index(self) -> Option<usize> {
44        if self.0 >= WINDOW_SIZE as u16 {
45            Some(self.0 as usize - WINDOW_SIZE)
46        } else {
47            None
48        }
49    }
50
51    pub(crate) const fn from_encoded(encoded: u16) -> Self {
52        Self(encoded)
53    }
54}
55
56/// A predecessor window in oldest-to-newest order.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct Window(Vec<u8>);
59
60impl Window {
61    /// Creates a validated window of at most 32 KiB.
62    pub fn new(bytes: Vec<u8>) -> Result<Self, MarkerError> {
63        if bytes.len() > WINDOW_SIZE {
64            return Err(MarkerError::WindowTooLarge(bytes.len()));
65        }
66        Ok(Self(bytes))
67    }
68
69    /// Creates the empty history used at a gzip member boundary.
70    pub const fn empty() -> Self {
71        Self(Vec::new())
72    }
73
74    /// Returns the window bytes.
75    pub fn as_slice(&self) -> &[u8] {
76        &self.0
77    }
78
79    pub(crate) fn advanced_by(&self, bytes: &[u8]) -> Self {
80        if bytes.len() >= WINDOW_SIZE {
81            return Self(bytes[bytes.len() - WINDOW_SIZE..].to_vec());
82        }
83        let retained = WINDOW_SIZE.saturating_sub(bytes.len()).min(self.0.len());
84        let mut result = Vec::with_capacity(retained + bytes.len());
85        result.extend_from_slice(&self.0[self.0.len() - retained..]);
86        result.extend_from_slice(bytes);
87        Self(result)
88    }
89}
90
91/// Speculative output retaining unknown predecessor-window references.
92#[derive(Clone, Debug, Default, Eq, PartialEq)]
93pub struct MarkerBuffer {
94    symbols: Vec<Symbol>,
95}
96
97impl MarkerBuffer {
98    /// Creates a buffer from speculative symbols.
99    pub const fn new(symbols: Vec<Symbol>) -> Self {
100        Self { symbols }
101    }
102
103    /// Returns the stored symbols.
104    pub fn symbols(&self) -> &[Symbol] {
105        &self.symbols
106    }
107
108    pub(crate) fn len(&self) -> usize {
109        self.symbols.len()
110    }
111
112    pub(crate) fn append_resolved_range(
113        &self,
114        range: std::ops::Range<usize>,
115        output: &mut Vec<u8>,
116        window: &Window,
117    ) -> Result<(), MarkerError> {
118        let symbols = self
119            .symbols
120            .get(range)
121            .ok_or(MarkerError::IndexOutOfRange(self.symbols.len()))?;
122        output.reserve(symbols.len());
123        for &symbol in symbols {
124            output.push(resolve_symbol(symbol, window)?);
125        }
126        Ok(())
127    }
128
129    /// Resolves marker references without re-decoding the chunk.
130    pub fn resolve(self, window: &Window) -> Result<Vec<u8>, MarkerError> {
131        self.resolve_ref(window)
132    }
133
134    /// Resolves marker references while retaining the encoded buffer.
135    pub(crate) fn resolve_ref(&self, window: &Window) -> Result<Vec<u8>, MarkerError> {
136        if self.symbols.len() >= 128 * 1024 && window.0.len() == WINDOW_SIZE {
137            let output = resolve_lut(&self.symbols, window);
138            return Ok(output);
139        }
140        let mut output = vec![0_u8; self.len()];
141        #[cfg(target_arch = "x86_64")]
142        if std::arch::is_x86_feature_detected!("sse4.1") {
143            // SAFETY: runtime feature detection proves SSE4.1 availability.
144            // The function receives slices whose bounds it checks before every
145            // 128-bit load and 64-bit store.
146            unsafe { resolve_sse41(&self.symbols, &mut output, window)? };
147            return Ok(output);
148        }
149        #[cfg(target_arch = "aarch64")]
150        {
151            // SAFETY: Advanced SIMD is part of the baseline AArch64 ISA. The
152            // function bounds-checks each vector load/store through chunking.
153            unsafe { resolve_neon(&self.symbols, &mut output, window)? };
154            Ok(output)
155        }
156        // Advanced SIMD is unconditional on AArch64, so the scalar fallback is
157        // only reachable elsewhere: on x86-64 without SSE4.1, or on any other
158        // architecture.
159        #[cfg(not(target_arch = "aarch64"))]
160        {
161            resolve_scalar(&self.symbols, &mut output, window)?;
162            Ok(output)
163        }
164    }
165}
166
167/// Resolves a large marker buffer through a branch-free 16-bit lookup table.
168///
169/// The low 256 entries preserve literal bytes and the high 32 Ki entries map
170/// marker encodings directly into the full predecessor window. Speculative
171/// chunks are normally several MiB, so amortizing the 64 KiB table setup avoids
172/// a marker/literal branch for every decoded byte.
173fn resolve_lut(symbols: &[Symbol], window: &Window) -> Vec<u8> {
174    debug_assert_eq!(window.0.len(), WINDOW_SIZE);
175
176    let mut lookup = [0_u8; u16::MAX as usize + 1];
177    for (value, byte) in lookup[..=u8::MAX as usize].iter_mut().enumerate() {
178        *byte = value as u8;
179    }
180    lookup[WINDOW_SIZE..].copy_from_slice(&window.0);
181    let mut output = Vec::with_capacity(symbols.len());
182    for (target, symbol) in output.spare_capacity_mut().iter_mut().zip(symbols) {
183        target.write(lookup[usize::from(symbol.encoded())]);
184    }
185    // SAFETY: the loop above writes exactly one initialized byte for every
186    // symbol into distinct slots of the vector's allocated spare capacity.
187    unsafe { output.set_len(symbols.len()) };
188    output
189}
190
191fn resolve_symbol(symbol: Symbol, window: &Window) -> Result<u8, MarkerError> {
192    if let Some(literal) = symbol.as_literal() {
193        return Ok(literal);
194    }
195    let index = symbol
196        .marker_index()
197        .expect("all non-literal symbol encodings are markers");
198    let missing = WINDOW_SIZE.saturating_sub(window.0.len());
199    if index < missing {
200        return Err(MarkerError::WindowTooSmall {
201            required: WINDOW_SIZE - index,
202            actual: window.0.len(),
203        });
204    }
205    Ok(window.0[index - missing])
206}
207
208fn resolve_scalar(
209    symbols: &[Symbol],
210    output: &mut [u8],
211    window: &Window,
212) -> Result<(), MarkerError> {
213    for (target, &symbol) in output.iter_mut().zip(symbols) {
214        *target = resolve_symbol(symbol, window)?;
215    }
216    Ok(())
217}
218
219#[cfg(target_arch = "x86_64")]
220#[target_feature(enable = "sse4.1")]
221unsafe fn resolve_sse41(
222    symbols: &[Symbol],
223    output: &mut [u8],
224    window: &Window,
225) -> Result<(), MarkerError> {
226    use core::arch::x86_64::{
227        __m128i, _mm_and_si128, _mm_loadu_si128, _mm_packus_epi16, _mm_set1_epi16,
228        _mm_storel_epi64, _mm_testz_si128,
229    };
230
231    let vectorized = symbols.len() / 8 * 8;
232    let high_byte_mask = _mm_set1_epi16(0xFF00_u16 as i16);
233    for offset in (0..vectorized).step_by(8) {
234        // SAFETY: `offset + 8 <= symbols.len()` and `Symbol` is transparent
235        // over `u16`, so this unaligned 16-byte load is within the slice.
236        let values = unsafe { _mm_loadu_si128(symbols.as_ptr().add(offset).cast::<__m128i>()) };
237        if _mm_testz_si128(_mm_and_si128(values, high_byte_mask), high_byte_mask) != 0 {
238            let packed = _mm_packus_epi16(values, values);
239            // SAFETY: the loop invariant proves that eight output bytes remain.
240            unsafe {
241                _mm_storel_epi64(output.as_mut_ptr().add(offset).cast::<__m128i>(), packed);
242            }
243        } else {
244            resolve_scalar(
245                &symbols[offset..offset + 8],
246                &mut output[offset..offset + 8],
247                window,
248            )?;
249        }
250    }
251    resolve_scalar(&symbols[vectorized..], &mut output[vectorized..], window)
252}
253
254#[cfg(target_arch = "aarch64")]
255#[target_feature(enable = "neon")]
256unsafe fn resolve_neon(
257    symbols: &[Symbol],
258    output: &mut [u8],
259    window: &Window,
260) -> Result<(), MarkerError> {
261    use core::arch::aarch64::{
262        vandq_u16, vld1q_u16, vmaxvq_u16, vmovn_u16, vsetq_lane_u16, vst1_u8,
263    };
264
265    let vectorized = symbols.len() / 8 * 8;
266    // SAFETY: the pointer refers to a live eight-element `u16` array, which is
267    // exactly the width this load reads.
268    let mut mask = unsafe { vld1q_u16([0xFF00_u16; 8].as_ptr()) };
269    // Keep an explicit lane operation so compilers consistently materialize
270    // this as a vector constant across supported Rust/LLVM versions.
271    mask = vsetq_lane_u16(0xFF00, mask, 0);
272    for offset in (0..vectorized).step_by(8) {
273        // SAFETY: the chunk calculation proves eight `u16` inputs remain.
274        let values = unsafe { vld1q_u16(symbols.as_ptr().add(offset).cast::<u16>()) };
275        if vmaxvq_u16(vandq_u16(values, mask)) == 0 {
276            let packed = vmovn_u16(values);
277            // SAFETY: the chunk calculation proves eight output bytes remain.
278            unsafe { vst1_u8(output.as_mut_ptr().add(offset), packed) };
279        } else {
280            resolve_scalar(
281                &symbols[offset..offset + 8],
282                &mut output[offset..offset + 8],
283                window,
284            )?;
285        }
286    }
287    resolve_scalar(&symbols[vectorized..], &mut output[vectorized..], window)
288}
289
290/// Marker construction or resolution failure.
291#[derive(Clone, Debug, Eq, PartialEq)]
292pub enum MarkerError {
293    /// Marker index was not within a 32 KiB window.
294    IndexOutOfRange(usize),
295    /// A supplied history window exceeded DEFLATE's maximum.
296    WindowTooLarge(usize),
297    /// The supplied partial history did not contain a referenced byte.
298    WindowTooSmall {
299        /// Minimum number of newest history bytes required.
300        required: usize,
301        /// Number supplied.
302        actual: usize,
303    },
304}
305
306impl Display for MarkerError {
307    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
308        match self {
309            Self::IndexOutOfRange(index) => {
310                write!(formatter, "marker index {index} is out of range")
311            }
312            Self::WindowTooLarge(size) => write!(formatter, "window size {size} exceeds 32768"),
313            Self::WindowTooSmall { required, actual } => write!(
314                formatter,
315                "marker requires {required} predecessor bytes, but only {actual} were supplied"
316            ),
317        }
318    }
319}
320
321impl Error for MarkerError {}
322
323#[cfg(test)]
324mod tests {
325    use super::{MarkerBuffer, Symbol, Window, resolve_scalar};
326    use proptest::prelude::*;
327
328    #[test]
329    fn resolves_full_window_markers() {
330        let window = Window::new((0..=255).cycle().take(32 * 1024).collect()).unwrap();
331        let buffer = MarkerBuffer::new(vec![
332            Symbol::literal(b'x'),
333            Symbol::marker(0).unwrap(),
334            Symbol::marker(32 * 1024 - 1).unwrap(),
335        ]);
336        assert_eq!(buffer.resolve(&window).unwrap(), [b'x', 0, 255]);
337    }
338
339    #[test]
340    fn partial_window_uses_newest_alignment() {
341        let window = Window::new(vec![10, 11, 12]).unwrap();
342        let buffer = MarkerBuffer::new(vec![Symbol::marker(32 * 1024 - 3).unwrap()]);
343        assert_eq!(buffer.resolve(&window).unwrap(), [10]);
344    }
345
346    #[test]
347    fn dispatched_resolution_matches_scalar_for_mixed_symbols() {
348        let window = Window::new(
349            (0..super::WINDOW_SIZE)
350                .map(|index| (index.wrapping_mul(37)) as u8)
351                .collect(),
352        )
353        .unwrap();
354        let symbols: Vec<_> = (0..65_537)
355            .map(|index| {
356                if index % 11 == 0 {
357                    Symbol::marker(index % super::WINDOW_SIZE).unwrap()
358                } else {
359                    Symbol::literal(index as u8)
360                }
361            })
362            .collect();
363        let mut scalar = vec![0; symbols.len()];
364        resolve_scalar(&symbols, &mut scalar, &window).unwrap();
365        let dispatched = MarkerBuffer::new(symbols).resolve(&window).unwrap();
366        assert_eq!(dispatched, scalar);
367    }
368
369    proptest! {
370        #[test]
371        fn dispatched_resolution_matches_scalar_for_arbitrary_valid_symbols(
372            encoded in prop::collection::vec(any::<u16>(), 0..4096)
373        ) {
374            let window = Window::new(
375                (0..super::WINDOW_SIZE)
376                    .map(|index| (index.wrapping_mul(131)) as u8)
377                    .collect(),
378            )
379            .unwrap();
380            let symbols: Vec<_> = encoded
381                .into_iter()
382                .map(|value| {
383                    if value & 1 == 0 {
384                        Symbol::literal((value >> 1) as u8)
385                    } else {
386                        Symbol::marker(usize::from(value) % super::WINDOW_SIZE).unwrap()
387                    }
388                })
389                .collect();
390            let mut scalar = vec![0; symbols.len()];
391            resolve_scalar(&symbols, &mut scalar, &window).unwrap();
392            let dispatched = MarkerBuffer::new(symbols).resolve(&window).unwrap();
393            prop_assert_eq!(dispatched, scalar);
394        }
395    }
396}