Skip to main content

simoom_sys/
codec.rs

1//! Codec inner loops as unchecked pointer code (owned by the simoom-compress work).
2//!
3//! simoom-compress keeps a safe implementation of each loop as the reference; these are the
4//! fast versions it calls when [`fast`] says so. Every loop establishes its bounds before it
5//! starts (sizes, slack after the buffers) and checks per step exactly what the untrusted input
6//! controls (lengths, offsets), so no input can make it read or write outside the slices it was
7//! given. The tests and fuzzers of simoom-compress compare the two implementations on every
8//! input.
9
10use std::cell::Cell;
11
12thread_local! {
13    static SAFE_ONLY: Cell<bool> = const { Cell::new(false) };
14}
15
16/// Whether callers should use these loops (`false` after [`set_safe_only`] on this thread).
17#[inline]
18pub fn fast() -> bool {
19    !SAFE_ONLY.with(Cell::get)
20}
21
22/// Makes [`fast`] return `false` on this thread (`true`) or `true` again (`false`): for tests
23/// and fuzzing that compare the safe reference implementations with these loops.
24pub fn set_safe_only(on: bool) {
25    SAFE_ONLY.with(|c| c.set(on));
26}
27
28/// Bytes of slack the zstd loops need after the literals and after the output limit.
29pub const WILD: usize = 32;
30
31/// A zstd sequence decoding table entry: the code's base value and extra bits, and the next
32/// state's base and bits (zstd's `ZSTD_seqSymbol`).
33#[repr(C)]
34#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
35pub struct SeqEntry {
36    pub base: u32,
37    pub add: u8,
38    pub nbits: u8,
39    pub next: u16,
40}
41
42/// The inputs of [`zstd_sequences`].
43pub struct Sequences<'a> {
44    /// Literal lengths at 0, offsets at 512, match lengths at 1024 (next states absolute).
45    pub table: &'a [SeqEntry; 2048],
46    /// Accuracy logs of the three tables (each at most 9).
47    pub logs: [u32; 3],
48    /// The sequences bitstream; at least 8 bytes, the last one non-zero.
49    pub bits: &'a [u8],
50    /// Number of sequences, at least 1.
51    pub n: usize,
52    /// The block's literals followed by [`WILD`] bytes of slack.
53    pub lits: &'a [u8],
54    /// The output: decoding writes from `start` on; the last [`WILD`] bytes are slack, so the
55    /// block may produce `out.len() - WILD - start` bytes.
56    pub out: &'a mut [u8],
57    pub start: usize,
58    pub reps: [u32; 3],
59    /// The largest offset allowed (the window).
60    pub wmax: usize,
61    /// History before `start` that offsets may reach, relative to `start` (wrapping): an
62    /// offset must be at most `hist + (position - start)`.
63    pub hist: usize,
64}
65
66/// Why [`zstd_sequences`] stopped.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum SeqError {
69    Literals,
70    ZeroOffset,
71    Window,
72    Size,
73    Bitstream,
74}
75
76/// Decodes and executes a zstd block's sequences (RFC 8878 section 3.1.1.3.2 and 3.1.1.4).
77/// Returns the end of the output written (the remaining literals after the last sequence are
78/// left to the caller) and the repeat offsets; the bytes after the end up to the slack are
79/// garbage. Same results as simoom-compress's safe `execute`.
80///
81/// # Panics
82/// If the preconditions on [`Sequences`] do not hold (they are checked on entry).
83pub fn zstd_sequences(s: Sequences<'_>) -> Result<(usize, usize, [u32; 3]), SeqError> {
84    #[cfg(target_arch = "x86_64")]
85    if crate::bits::bmi2() {
86        // SAFETY: the CPU has BMI1, BMI2 and LZCNT (checked at run time by `bmi2()`), which is
87        // all `sequences_bmi2` requires beyond its safe signature.
88        return unsafe { sequences_bmi2(s) };
89    }
90    sequences(s)
91}
92
93/// [`sequences`] compiled with BMI1, BMI2 and LZCNT (variable shifts without `cl`).
94#[cfg(target_arch = "x86_64")]
95#[target_feature(enable = "bmi1,bmi2,lzcnt")]
96unsafe fn sequences_bmi2(s: Sequences<'_>) -> Result<(usize, usize, [u32; 3]), SeqError> {
97    sequences(s)
98}
99
100// (the last sequence's state updates are skipped, leaving assignments unread)
101#[allow(unused_assignments)]
102#[inline(always)]
103fn sequences(s: Sequences<'_>) -> Result<(usize, usize, [u32; 3]), SeqError> {
104    let Sequences { table, logs, bits, n, lits, out, start, mut reps, wmax, hist } = s;
105    assert!(bits.len() >= 8 && bits[bits.len() - 1] != 0 && n >= 1);
106    assert!(lits.len() >= WILD && out.len() >= start + WILD && logs.iter().all(|&l| l <= 9));
107    let lim = out.len() - WILD;
108    let tp = table.as_ptr();
109    let bp = bits.as_ptr();
110    // the bit reader (zstd's BIT_DStream): `c` holds the 8 bytes at `ptr`, `used` bits of
111    // them consumed from the top; `ptr` stays in 0..=bits.len() - 8
112    let mut ptr = bits.len() - 8;
113    let last = bits[bits.len() - 1];
114    let mut used = 8 - (31 - (last as u32).leading_zeros());
115    // SAFETY: `ptr + 8 <= bits.len()`.
116    let mut c = u64::from_le_bytes(unsafe { std::ptr::read_unaligned(bp.add(ptr) as *const [u8; 8]) });
117    macro_rules! reload {
118        () => {{
119            let step = ((used >> 3) as usize).min(ptr);
120            ptr -= step;
121            used -= 8 * step as u32;
122            // SAFETY: `ptr` only decreases and stays `<= bits.len() - 8`, so the 8 bytes are
123            // inside `bits`.
124            c = u64::from_le_bytes(unsafe { std::ptr::read_unaligned(bp.add(ptr) as *const [u8; 8]) });
125        }};
126    }
127    macro_rules! read {
128        ($nb:expr) => {{
129            let nb = $nb as u32;
130            let v = c.wrapping_shl(used) >> 1 >> (63u32.wrapping_sub(nb) & 63);
131            used += nb;
132            v
133        }};
134    }
135    macro_rules! entry {
136        ($s:expr) => {
137            // SAFETY: the index is masked to the table's 2048 entries.
138            unsafe { *tp.add($s & 2047) }
139        };
140    }
141    let mut s_ll = read!(logs[0]) as usize;
142    let mut s_of = 512 + read!(logs[1]) as usize;
143    let mut s_ml = 1024 + read!(logs[2]) as usize;
144    reload!();
145    let lits_n = lits.len() - WILD;
146    // the next literal and how many are left (the slice has WILD bytes of slack after them)
147    let mut litp = lits.as_ptr();
148    let mut lits_left = lits_n;
149    let o = out.as_mut_ptr();
150    let mut op = start;
151    // one bound for every offset: at most `lit_end` (inside the buffer) and at most
152    // `hist + lit_end` (the history); the second is the lower one exactly when `hist` is
153    // negative (as a wrapping number)
154    let h = if (hist as isize) < 0 { hist } else { 0 };
155    // (Decoding into a small batch first and executing it after, as libzstd's long-offset
156    // path does, was measured: the same instruction count and more mispredicted branches.)
157    // (Pipelining by one sequence with a prefetch of the next match source was measured too:
158    // no faster, more instructions.)
159    macro_rules! decode {
160        ($update:expr) => {{
161            let ll_e = entry!(s_ll);
162            let of_e = entry!(s_of);
163            let ml_e = entry!(s_ml);
164            let ofb = read!(of_e.add) as u32;
165            let ml = (ml_e.base + read!(ml_e.add) as u32) as usize;
166            if of_e.add as u32 + ml_e.add as u32 + ll_e.add as u32 >= 31 {
167                reload!();
168            }
169            let ll = (ll_e.base + read!(ll_e.add) as u32) as usize;
170            if $update {
171                s_ll = ll_e.next as usize + read!(ll_e.nbits) as usize;
172                s_ml = ml_e.next as usize + read!(ml_e.nbits) as usize;
173                s_of = of_e.next as usize + read!(of_e.nbits) as usize;
174                reload!();
175            }
176            let off = if of_e.add > 1 {
177                let x = of_e.base.wrapping_add(ofb);
178                reps = [x, reps[0], reps[1]];
179                x
180            } else {
181                let v = of_e.base + ofb + (ll == 0) as u32;
182                let x = match v {
183                    1 => reps[0],
184                    2 => reps[1],
185                    3 => reps[2],
186                    _ => reps[0].wrapping_sub(1),
187                };
188                if v != 1 {
189                    reps = [x, reps[0], if v == 2 { reps[2] } else { reps[1] }];
190                }
191                x
192            } as usize;
193            (ll, ml, off)
194        }};
195    }
196    macro_rules! execute {
197        ($ll:expr, $ml:expr, $off:expr) => {{
198            let (ll, ml, off) = ($ll, $ml, $off);
199            let lit_end = op + ll;
200            let end = lit_end + ml;
201            // everything the input controls, checked before any copy: the literals exist, the
202            // output stays under the limit, the offset is non-zero, inside the window, the
203            // history and the buffer
204            if (ll > lits_left) | (end > lim) | (off.wrapping_sub(1) >= wmax) | (off > lit_end.wrapping_add(h)) {
205                return Err(if ll > lits_left {
206                    SeqError::Literals
207                } else if off == 0 {
208                    SeqError::ZeroOffset
209                } else if off >= wmax.wrapping_add(1) || off > lit_end.wrapping_add(h) {
210                    SeqError::Window
211                } else {
212                    SeqError::Size
213                });
214            }
215            // SAFETY: literals: `ll <= lits_left`, so reading 16-byte chunks up to `ll + 15`
216            // bytes past `litp` stays in `lits` (WILD bytes of slack after the last literal);
217            // writing up to `lit_end + 15 < lim + WILD = out.len()` stays in `out`. The slices
218            // are distinct.
219            unsafe {
220                let d = o.add(op);
221                let mut i = 0;
222                loop {
223                    std::ptr::copy_nonoverlapping(litp.add(i), d.add(i), 16);
224                    i += 16;
225                    if i >= ll {
226                        break;
227                    }
228                }
229                litp = litp.add(ll);
230            }
231            lits_left -= ll;
232            // SAFETY: match: `off <= lit_end` (since `h <= 0` as a signed number), so the source
233            // `lit_end - off` is inside `out`; all writes end before `end + 16 <= lim + 16 <
234            // out.len()`, and reads trail the writes.
235            unsafe { copy_match(o, lit_end, off, ml) };
236            op = end;
237        }};
238    }
239    for _ in 1..n {
240        let (ll, ml, off) = decode!(true);
241        execute!(ll, ml, off);
242    }
243    let (ll, ml, off) = decode!(false);
244    execute!(ll, ml, off);
245    if !(ptr == 0 && used == 64) {
246        return Err(SeqError::Bitstream);
247    }
248    Ok((op, lits_n - lits_left, reps))
249}
250
251/// Copies `ml` (at least 1) bytes from `off` (at least 1) back to `o + op`, overlapping
252/// allowed (zstd's `ZSTD_execSequence`).
253///
254/// # Safety
255/// `o` points into a buffer with `off <= op` and at least `op + ml + 16` bytes.
256#[inline(always)]
257unsafe fn copy_match(o: *mut u8, op: usize, off: usize, ml: usize) {
258    let end = op + ml;
259    // SAFETY (for every access below): sources are at `>= op - off >= 0` and below the
260    // destination being written; destinations are below `end + 16`, inside the buffer by the
261    // caller's contract. Each 16- or 8-byte copy has source and destination at least that far
262    // apart, so they do not overlap.
263    unsafe {
264        if off >= 16 {
265            let (mut sp, mut dp) = (o.add(op - off), o.add(op));
266            let e = o.add(end);
267            loop {
268                std::ptr::copy_nonoverlapping(sp, dp, 16);
269                dp = dp.add(16);
270                sp = sp.add(16);
271                if dp >= e {
272                    break;
273                }
274            }
275        } else if off == 1 {
276            std::ptr::write_bytes(o.add(op), *o.add(op - 1), ml);
277        } else {
278            const D32: [usize; 8] = [0, 1, 2, 1, 4, 4, 4, 4];
279            const D64: [usize; 8] = [8, 8, 8, 7, 8, 9, 10, 11];
280            let mut d = op;
281            let mut s = op - off;
282            if off < 8 {
283                *o.add(d) = *o.add(s);
284                *o.add(d + 1) = *o.add(s + 1);
285                *o.add(d + 2) = *o.add(s + 2);
286                *o.add(d + 3) = *o.add(s + 3);
287                s += D32[off];
288                std::ptr::copy_nonoverlapping(o.add(s), o.add(d + 4), 4);
289                d += 8;
290                s = d - (off - D32[off] + D64[off]);
291            } else {
292                std::ptr::copy_nonoverlapping(o.add(s), o.add(d), 8);
293                d += 8;
294                s += 8;
295            }
296            while d < end {
297                std::ptr::copy_nonoverlapping(o.add(s), o.add(d), 8);
298                d += 8;
299                s += 8;
300            }
301        }
302    }
303}
304
305/// The state of zstd's four-stream Huffman fast loop (HUF_decompress4X1 "fast"): per stream a
306/// left-aligned 64-bit container with a marker bit below the unread bits, and its position in
307/// the input.
308pub struct Huf4<'a> {
309    /// Entries `symbol << 8 | bits`, indexed by the next 11 bits.
310    pub table: &'a [u16; 2048],
311    /// The four streams, one after another.
312    pub src: &'a [u8],
313    /// Where each stream starts in `src`.
314    pub starts: [usize; 4],
315    /// Each stream's position (the 8 bytes loaded last): `starts[j] <= pos[j]`,
316    /// `pos[j] + 8 <= src.len()`.
317    pub pos: [usize; 4],
318    pub bits: [u64; 4],
319    /// The output: stream `j` writes `out[j * seg + i]`; stream 3 has `out.len() - 3 * seg`.
320    pub out: &'a mut [u8],
321    pub seg: usize,
322    /// Symbols per stream done so far.
323    pub done: usize,
324}
325
326/// Runs the four-stream fast loop as far as it can go without reading before a stream's start
327/// or writing past stream 3's output (five symbols per stream per round); returns the updated
328/// positions, containers and symbol count. Same results as simoom-compress's safe loop.
329///
330/// # Panics
331/// If the position or size preconditions of [`Huf4`] do not hold (checked on entry).
332pub fn huf4(h: Huf4<'_>) -> ([usize; 4], [u64; 4], usize) {
333    #[cfg(target_arch = "x86_64")]
334    if crate::bits::bmi2() {
335        // SAFETY: the CPU has BMI1, BMI2 and LZCNT (checked at run time by `bmi2()`), which is
336        // all `huf4_bmi2` requires beyond its safe signature.
337        return unsafe { huf4_bmi2(h) };
338    }
339    huf4_loop(h)
340}
341
342#[cfg(target_arch = "x86_64")]
343#[target_feature(enable = "bmi1,bmi2,lzcnt")]
344unsafe fn huf4_bmi2(h: Huf4<'_>) -> ([usize; 4], [u64; 4], usize) {
345    huf4_loop(h)
346}
347
348#[inline(always)]
349fn huf4_loop(h: Huf4<'_>) -> ([usize; 4], [u64; 4], usize) {
350    let Huf4 { table, src, starts, mut pos, mut bits, out, seg, mut done } = h;
351    assert!(out.len() >= 3 * seg && done <= out.len() - 3 * seg && done <= seg);
352    for j in 0..4 {
353        assert!(starts[j] <= pos[j] && pos[j] + 8 <= src.len());
354    }
355    let n3 = out.len() - 3 * seg;
356    let t = table.as_ptr();
357    let sp = src.as_ptr();
358    let o = out.as_mut_ptr();
359    loop {
360        let mut rounds = (n3 - done) / 5;
361        for j in 0..4 {
362            rounds = rounds.min((pos[j] - starts[j]) / 7);
363        }
364        if rounds == 0 {
365            break;
366        }
367        for _ in 0..rounds {
368            for i in 0..5 {
369                for j in 0..4 {
370                    // SAFETY: the table index is a 64-bit value shifted right by 53, so below
371                    // 2048. The output index `j * seg + done + i` is below `3 * seg + n3 =
372                    // out.len()`: `done + 5 <= n3 <= seg` holds for every round counted above.
373                    unsafe {
374                        let e = *t.add((bits[j] >> 53) as usize);
375                        bits[j] <<= e & 63;
376                        *o.add(j * seg + done + i) = (e >> 8) as u8;
377                    }
378                }
379            }
380            done += 5;
381            for j in 0..4 {
382                let z = bits[j].trailing_zeros();
383                pos[j] -= (z >> 3) as usize;
384                // SAFETY: a round consumes at most 55 bits (7 bytes) per stream, and the round
385                // count keeps `pos[j] - 7 * rounds >= starts[j]`; `pos[j]` only decreases, so
386                // `pos[j] + 8 <= src.len()` still holds.
387                bits[j] = (u64::from_le_bytes(unsafe { std::ptr::read_unaligned(sp.add(pos[j]) as *const [u8; 8]) }) | 1) << (z & 7);
388            }
389        }
390    }
391    (pos, bits, done)
392}
393
394/// One match of an LZ77 parse: `lit` literals, then `len` bytes from `dist` back.
395#[repr(C)]
396#[derive(Clone, Copy, Debug, PartialEq, Eq)]
397pub struct Match {
398    pub lit: u32,
399    pub len: u32,
400    pub dist: u32,
401}
402
403/// The inputs of [`zstd_fast`].
404pub struct FastIn<'a> {
405    pub buf: &'a [u8],
406    /// Stream position of `buf[0]`.
407    pub base: u64,
408    pub from: usize,
409    /// End of the block (`from <= to <= buf.len()`); matches never reach past it.
410    pub to: usize,
411    /// Short-hash table (a power of two entries) over `mls` (5 to 8) bytes, `small_log` bits.
412    pub small: &'a mut [u32],
413    pub small_log: u32,
414    pub mls: u32,
415    /// Long-hash table over 8 bytes for double fast (`long_log > 0`, a power of two entries).
416    pub long: &'a mut [u32],
417    pub long_log: u32,
418    pub max_dist: usize,
419    pub rep: [u32; 2],
420    /// Skip one more byte for every `1 << strength` bytes without a match (zstd: 8).
421    pub strength: u32,
422    pub out: &'a mut Vec<Match>,
423}
424
425
426#[inline(always)]
427fn fhash(v: u64, mls: u32, log: u32) -> usize {
428    let p: u64 = match mls {
429        5 => 889_523_592_379,
430        6 => 227_718_039_650_203,
431        7 => 58_295_818_150_454_627,
432        _ => 0xCF1B_BCDC_B7A5_6463,
433    };
434    ((v << (64 - 8 * mls)).wrapping_mul(p) >> (64 - log)) as usize
435}
436
437/// Bytes equal at `a` and `b`, up to `max`.
438///
439/// # Safety
440/// `p` points to a buffer holding `a + max` and `b + max` bytes.
441#[inline(always)]
442unsafe fn count_raw(p: *const u8, a: usize, b: usize, max: usize) -> usize {
443    let mut n = 0;
444    // SAFETY: every read is below `a + max` or `b + max`, inside the buffer by the contract.
445    unsafe {
446        while n + 8 <= max {
447            let x = std::ptr::read_unaligned(p.add(a + n) as *const u64) ^ std::ptr::read_unaligned(p.add(b + n) as *const u64);
448            if x != 0 {
449                return n + (x.to_le().trailing_zeros() / 8) as usize;
450            }
451            n += 8;
452        }
453        while n < max && *p.add(a + n) == *p.add(b + n) {
454            n += 1;
455        }
456    }
457    n
458}
459
460/// zstd's fast (level 1: one table) and double-fast (levels 2-3: `long_log > 0`) match finders
461/// over `buf[from..to]`, appending matches to `out`; returns the literals left after the last
462/// match and the repeat offsets. Same results as simoom-compress's safe `Fast`.
463///
464/// Every read is at a position the loops keep at most `to - 8` (`to - 9` for double fast) or
465/// below a position already read, or inside a match count bounded by `to`; tables are indexed
466/// by masked hashes.
467///
468/// # Panics
469/// If `from <= to <= buf.len()` or the table sizes (non-zero powers of two) do not hold.
470pub fn zstd_fast(p: FastIn<'_>) -> (usize, [u32; 2]) {
471    #[cfg(target_arch = "x86_64")]
472    if crate::bits::bmi2() {
473        // SAFETY: the CPU has BMI1, BMI2 and LZCNT (checked at run time by `bmi2()`), which is
474        // all `fast_bmi2` requires beyond its safe signature.
475        return unsafe { fast_bmi2(p) };
476    }
477    fast_impl(p)
478}
479
480#[cfg(target_arch = "x86_64")]
481#[target_feature(enable = "bmi1,bmi2,lzcnt")]
482unsafe fn fast_bmi2(p: FastIn<'_>) -> (usize, [u32; 2]) {
483    fast_impl(p)
484}
485
486#[inline(always)]
487fn fast_impl(p: FastIn<'_>) -> (usize, [u32; 2]) {
488    let FastIn { buf, base, from, to, small, small_log: slog, mls, long, long_log: llog, max_dist, rep, strength, out } = p;
489    assert!((2..=12).contains(&strength));
490    // the block may not be longer than the window: the bound `low` on match sources is taken
491    // from the block's end, and every `ip - low` below assumes `low <= from <= ip`
492    assert!(from <= to && to <= buf.len() && to.saturating_sub(max_dist) <= from && small.len().is_power_of_two() && (5..=8).contains(&mls));
493    assert!(llog == 0 || long.len().is_power_of_two());
494    let bp = buf.as_ptr();
495    let sp = small.as_mut_ptr();
496    let lp = long.as_mut_ptr();
497    // SAFETY (the macros): reads are inside `buf` by the invariants in the function docs (every
498    // position read is at most `to - 8`, and `to <= buf.len()`); table indices are masked with
499    // `len - 1` of their power-of-two table.
500    macro_rules! r32 {
501        ($i:expr) => {
502            unsafe { std::ptr::read_unaligned(bp.add($i) as *const u32).to_le() }
503        };
504    }
505    macro_rules! r64 {
506        ($i:expr) => {
507            unsafe { std::ptr::read_unaligned(bp.add($i) as *const u64).to_le() }
508        };
509    }
510    macro_rules! b8 {
511        ($i:expr) => {
512            unsafe { *bp.add($i) }
513        };
514    }
515    macro_rules! cnt {
516        ($a:expr, $b:expr, $m:expr) => {
517            unsafe { count_raw(bp, $a, $b, $m) }
518        };
519    }
520    macro_rules! tget {
521        (small, $h:expr) => {
522            {
523                let h = $h;
524                unsafe { *sp.add(h) }
525            }
526        };
527        (long, $h:expr) => {
528            {
529                let h = $h;
530                unsafe { *lp.add(h) }
531            }
532        };
533    }
534    macro_rules! tset {
535        (small, $h:expr, $v:expr) => {
536            {
537                let (h, v) = ($h, $v);
538                unsafe { *sp.add(h) = v }
539            }
540        };
541        (long, $h:expr, $v:expr) => {
542            {
543                let (h, v) = ($h, $v);
544                unsafe { *lp.add(h) = v }
545            }
546        };
547    }
548    let _ = (&lp, &max_dist);
549    if llog > 0 {
550        let small = &mut *small;
551        let long = &mut *long;
552        let _ = (&small, &long);
553
554        
555            let (smask, lmask) = (small.len() - 1, long.len() - 1);
556        
557        
558            let [mut r0, mut r1] = rep;
559            let pos = |i: usize| (base + i as u64) as u32;
560            let lowest = |i: usize| i.saturating_sub(max_dist);
561            let mut anchor = from;
562            let mut ip = from + (from == 0) as usize;
563            if to < 9 {
564                return (to - from, rep);
565            }
566            let ilimit = to - 9;
567            while ip < ilimit {
568                let v = r64!(ip);
569                let hl = fhash(v, 8, llog) & lmask;
570                let hs = fhash(v, mls, slog) & smask;
571                let cl = tget!(long, hl);
572                let cs = tget!(small, hs);
573                tset!(long, hl, pos(ip));
574                tset!(small, hs, pos(ip));
575                let low = lowest(ip);
576                let start;
577                let ml;
578                let r = r0 as usize;
579                if r > 0 && ip + 1 - lowest(ip + 1) >= r && r32!(ip + 1 - r) == r32!(ip + 1) {
580                    start = ip + 1;
581                    ml = 4 + cnt!(start + 4 - r, start + 4, to - start - 4);
582                    out.push(Match { lit: (start - anchor) as u32, len: ml as u32, dist: r as u32 });
583                } else {
584                    let dl = pos(ip).wrapping_sub(cl) as usize;
585                    let ds = pos(ip).wrapping_sub(cs) as usize;
586                    let (mut s, d, mut len);
587                    if dl != 0 && dl <= ip - low && r64!(ip - dl) == v {
588                        s = ip;
589                        d = dl;
590                        len = 8 + cnt!(ip + 8 - dl, ip + 8, to - ip - 8);
591                    } else if ds != 0 && ds <= ip - low && r32!(ip - ds) == v as u32 {
592                        // a long match one byte ahead beats the short one
593                        let v1 = r64!(ip + 1);
594                        let hl1 = fhash(v1, 8, llog) & lmask;
595                        let cl1 = tget!(long, hl1);
596                        tset!(long, hl1, pos(ip + 1));
597                        let dl1 = pos(ip + 1).wrapping_sub(cl1) as usize;
598                        if dl1 != 0 && dl1 <= ip + 1 - lowest(ip + 1) && r64!(ip + 1 - dl1) == v1 {
599                            s = ip + 1;
600                            d = dl1;
601                            len = 8 + cnt!(ip + 9 - dl1, ip + 9, to - ip - 9);
602                        } else {
603                            s = ip;
604                            d = ds;
605                            len = 4 + cnt!(ip + 4 - ds, ip + 4, to - ip - 4);
606                        }
607                    } else {
608                        ip += ((ip - anchor) >> strength) + 1;
609                        continue;
610                    }
611                    let mut m = s - d;
612                    let low = lowest(s);
613                    while s > anchor && m > low && b8!(s - 1) == b8!(m - 1) {
614                        s -= 1;
615                        m -= 1;
616                        len += 1;
617                    }
618                    start = s;
619                    ml = len;
620                    r1 = r0;
621                    r0 = d as u32;
622                    out.push(Match { lit: (start - anchor) as u32, len: ml as u32, dist: d as u32 });
623                }
624                ip = start + ml;
625                anchor = ip;
626                if ip <= ilimit {
627                    // index positions of the match
628                    let p2 = start + 2;
629                    let v2 = r64!(p2);
630                    tset!(long, fhash(v2, 8, llog) & lmask, pos(p2));
631                    tset!(small, fhash(v2, mls, slog) & smask, pos(p2));
632                    let v3 = r64!(ip - 2);
633                    tset!(long, fhash(v3, 8, llog) & lmask, pos(ip - 2));
634                    tset!(small, fhash(v3, mls, slog) & smask, pos(ip - 2));
635                    tset!(long, fhash(r64!(ip - 1), 8, llog) & lmask, pos(ip - 1));
636                    while ip <= ilimit {
637                        let r = r1 as usize;
638                        if r == 0 || ip - lowest(ip) < r || r32!(ip - r) != r32!(ip) {
639                            break;
640                        }
641                        let ml = 4 + cnt!(ip + 4 - r, ip + 4, to - ip - 4);
642                        std::mem::swap(&mut r0, &mut r1);
643                        let v = r64!(ip);
644                        tset!(small, fhash(v, mls, slog) & smask, pos(ip));
645                        tset!(long, fhash(v, 8, llog) & lmask, pos(ip));
646                        out.push(Match { lit: 0, len: ml as u32, dist: r as u32 });
647                        ip += ml;
648                        anchor = ip;
649                    }
650                }
651            }
652            (to - anchor, [r0, r1])
653    } else {
654        let small = &mut *small;
655        let _ = &small;
656
657        
658            let smask = small.len() - 1;
659        
660            let pos = |i: usize| (base + i as u64) as u32;
661            let hash_at = |i: usize| fhash(r64!(i), mls, slog) & smask;
662            if to < from + 16 {
663                return (to - from, rep);
664            }
665            // matches start no lower than this (the window from the block's end, as zstd's prefix)
666            let low = to.saturating_sub(max_dist);
667            let ilimit = to - 8;
668            let mut anchor = from;
669            let mut ip0 = from + (from == low) as usize;
670            let [mut r0, mut r1] = rep;
671            let (mut saved0, mut saved1) = (0, 0);
672            let max_rep = (ip0 - low) as u32;
673            if r1 > max_rep {
674                saved1 = r1;
675                r1 = 0;
676            }
677            if r0 > max_rep {
678                saved0 = r0;
679                r0 = 0;
680            }
681            let found = |ip: usize, m: u32| {
682                let d = pos(ip).wrapping_sub(m) as usize;
683                d != 0 && d <= ip - low && r32!(ip - d) == r32!(ip)
684            };
685            'start: loop {
686                let mut step = 2;
687                let mut next_step = ip0 + (1 << (strength - 1));
688                let mut ip1 = ip0 + 1;
689                let mut ip2 = ip0 + step;
690                let mut ip3 = ip2 + 1;
691                if ip3 >= ilimit {
692                    break;
693                }
694                let mut h0 = hash_at(ip0);
695                let mut h1 = hash_at(ip1);
696                let mut midx = tget!(small, h0);
697                let cur0;
698                let mut m0;
699                let mut ml;
700                let rep_match;
701                loop {
702                    let mut c = ip0;
703                    tset!(small, h0, pos(ip0));
704                    if r0 > 0 && r32!(ip2 - r0 as usize) == r32!(ip2) {
705                        cur0 = c;
706                        ip0 = ip2;
707                        m0 = ip0 - r0 as usize;
708                        ml = (m0 > low && b8!(ip0 - 1) == b8!(m0 - 1)) as usize;
709                        ip0 -= ml;
710                        m0 -= ml;
711                        ml += 4;
712                        tset!(small, h1, pos(ip1));
713                        rep_match = true;
714                        break;
715                    }
716                    if found(ip0, midx) {
717                        cur0 = c;
718                        tset!(small, h1, pos(ip1));
719                        m0 = 0;
720                        ml = 0;
721                        rep_match = false;
722                        break;
723                    }
724                    midx = tget!(small, h1);
725                    h0 = h1;
726                    h1 = hash_at(ip2);
727                    ip0 = ip1;
728                    ip1 = ip2;
729                    ip2 = ip3;
730                    c = ip0;
731                    tset!(small, h0, pos(ip0));
732                    if found(ip0, midx) {
733                        cur0 = c;
734                        if step <= 4 {
735                            tset!(small, h1, pos(ip1));
736                        }
737                        m0 = 0;
738                        ml = 0;
739                        rep_match = false;
740                        break;
741                    }
742                    midx = tget!(small, h1);
743                    h0 = h1;
744                    h1 = hash_at(ip2);
745                    ip0 = ip1;
746                    ip1 = ip2;
747                    ip2 = ip0 + step;
748                    ip3 = ip1 + step;
749                    if ip2 >= next_step {
750                        step += 1;
751                        next_step += 1 << (strength - 1);
752                    }
753                    if ip3 >= ilimit {
754                        break 'start;
755                    }
756                }
757                if !rep_match {
758                    // a new offset; extend backwards over the pending literals
759                    let d = pos(ip0).wrapping_sub(midx) as usize;
760                    m0 = ip0 - d;
761                    r1 = r0;
762                    r0 = d as u32;
763                    ml = 4;
764                    while ip0 > anchor && m0 > low && b8!(ip0 - 1) == b8!(m0 - 1) {
765                        ip0 -= 1;
766                        m0 -= 1;
767                        ml += 1;
768                    }
769                }
770                ml += cnt!(m0 + ml, ip0 + ml, to - ip0 - ml);
771                out.push(Match { lit: (ip0 - anchor) as u32, len: ml as u32, dist: (ip0 - m0) as u32 });
772                ip0 += ml;
773                anchor = ip0;
774                if ip0 <= ilimit {
775                    tset!(small, hash_at(cur0 + 2), pos(cur0 + 2));
776                    tset!(small, hash_at(ip0 - 2), pos(ip0 - 2));
777                    // the second-to-last offset right away (no literals in between)
778                    while r1 > 0 && ip0 <= ilimit && r32!(ip0) == r32!(ip0 - r1 as usize) {
779                        let r = r1 as usize;
780                        let ml = 4 + cnt!(ip0 + 4 - r, ip0 + 4, to - ip0 - 4);
781                        std::mem::swap(&mut r0, &mut r1);
782                        tset!(small, hash_at(ip0), pos(ip0));
783                        out.push(Match { lit: 0, len: ml as u32, dist: r as u32 });
784                        ip0 += ml;
785                        anchor = ip0;
786                    }
787                }
788            }
789            // offsets set aside as out of the window come back (zstd's offsetSaved logic)
790            saved1 = if saved0 != 0 && r0 != 0 { saved0 } else { saved1 };
791            (to - anchor, [if r0 != 0 { r0 } else { saved0 }, if r1 != 0 { r1 } else { saved1 }])
792    }
793}
794
795/// An FSE encoding table entry (zstd's `FSE_symbolCompressionTransform`).
796#[repr(C)]
797#[derive(Clone, Copy, Default, Debug)]
798pub struct SymTT {
799    pub delta_nbits: u32,
800    pub delta_find: i32,
801}
802
803/// An FSE encoding table: next states (log at most 9) and per-symbol transforms (symbols
804/// below 64).
805#[derive(Clone, Copy)]
806pub struct FseEnc<'a> {
807    pub states: &'a [u16; 512],
808    pub tt: &'a [SymTT; 64],
809    pub log: u32,
810}
811
812/// One zstd sequence ready to code: the three codes, the extra-bit counts of the lengths, and
813/// the extra values (the offset value's extra bits are `of_code` of them).
814#[repr(C)]
815#[derive(Clone, Copy, Debug, Default)]
816pub struct SeqCode {
817    pub ll_code: u8,
818    pub of_code: u8,
819    pub ml_code: u8,
820    pub ll_bits: u8,
821    pub ml_bits: u8,
822    pub ll: u32,
823    pub ofv: u32,
824    pub mlb: u32,
825}
826
827/// Writes the sequences bitstream of a zstd block (RFC 8878 section 4.1.2: last sequence first,
828/// the three FSE states interleaved, extras, end marker) for `codes` (at least one) with tables
829/// `[literal lengths, offsets, match lengths]`, appending it to `out`. Same bytes as
830/// simoom-compress's safe writer.
831pub fn zstd_seq_stream(codes: &[SeqCode], t: [FseEnc<'_>; 3], out: &mut Vec<u8>) {
832    #[cfg(target_arch = "x86_64")]
833    if crate::bits::bmi2() {
834        // SAFETY: the CPU has BMI1, BMI2 and LZCNT (checked at run time by `bmi2()`), which is
835        // all `seq_stream_bmi2` requires beyond its safe signature.
836        return unsafe { seq_stream_bmi2(codes, t, out) };
837    }
838    seq_stream(codes, t, out)
839}
840
841#[cfg(target_arch = "x86_64")]
842#[target_feature(enable = "bmi1,bmi2,lzcnt")]
843unsafe fn seq_stream_bmi2(codes: &[SeqCode], t: [FseEnc<'_>; 3], out: &mut Vec<u8>) {
844    seq_stream(codes, t, out)
845}
846
847// (the final flush leaves its state updates unread)
848#[allow(unused_assignments)]
849#[inline(always)]
850fn seq_stream(codes: &[SeqCode], t: [FseEnc<'_>; 3], out: &mut Vec<u8>) {
851    assert!(!codes.is_empty() && t.iter().all(|x| x.log <= 9));
852    let n = codes.len();
853    let start = out.len();
854    // each sequence writes at most 26 + 32 + 31 bits; plus the states and the end marker
855    let cap = 13 * n + 64;
856    out.resize(start + cap + 8, 0);
857    let o = out.as_mut_ptr();
858    let mut pos = start;
859    let mut acc = 0u64;
860    let mut nb = 0u32;
861    macro_rules! add {
862        ($v:expr, $b:expr) => {{
863            let b = $b as u32;
864            acc |= ($v as u64 & ((1u64 << b) - 1)) << nb;
865            nb += b;
866        }};
867    }
868    macro_rules! flush {
869        () => {{
870            // SAFETY: `pos` grows by at most 13 bytes per sequence plus the fixed parts, so
871            // `pos + 8 <= start + cap + 8 = out.len()`.
872            unsafe { std::ptr::write_unaligned(o.add(pos) as *mut [u8; 8], acc.to_le_bytes()) };
873            let k = nb >> 3;
874            pos += k as usize;
875            acc = if k == 8 { 0 } else { acc >> (8 * k) };
876            nb &= 7;
877        }};
878    }
879    let init = |x: &FseEnc<'_>, s: u8| {
880        let tt = x.tt[s as usize & 63];
881        let b = (tt.delta_nbits.wrapping_add(1 << 15)) >> 16;
882        let v = (b << 16).wrapping_sub(tt.delta_nbits);
883        x.states[((v >> b) as i32).wrapping_add(tt.delta_find) as usize & 511] as u32
884    };
885    let [ll_t, of_t, ml_t] = t;
886    let last = codes[n - 1];
887    let mut ll_s = init(&ll_t, last.ll_code);
888    let mut of_s = init(&of_t, last.of_code);
889    let mut ml_s = init(&ml_t, last.ml_code);
890    macro_rules! enc {
891        ($x:expr, $st:expr, $sym:expr) => {{
892            let tt = $x.tt[$sym as usize & 63];
893            let b = $st.wrapping_add(tt.delta_nbits) >> 16;
894            add!($st, b);
895            $st = $x.states[(($st >> b) as i32).wrapping_add(tt.delta_find) as usize & 511] as u32;
896        }};
897    }
898    macro_rules! extras {
899        ($c:expr) => {{
900            let c = $c;
901            if nb + c.ll_bits as u32 + c.ml_bits as u32 > 63 {
902                flush!();
903            }
904            add!(c.ll, c.ll_bits);
905            add!(c.mlb, c.ml_bits);
906            if nb + c.of_code as u32 > 63 {
907                flush!();
908            }
909            add!(c.ofv, c.of_code);
910            flush!();
911        }};
912    }
913    extras!(last);
914    for c in codes[..n - 1].iter().rev() {
915        enc!(of_t, of_s, c.of_code);
916        enc!(ml_t, ml_s, c.ml_code);
917        enc!(ll_t, ll_s, c.ll_code);
918        flush!();
919        extras!(c);
920    }
921    add!(ml_s, ml_t.log);
922    flush!();
923    add!(of_s, of_t.log);
924    flush!();
925    add!(ll_s, ll_t.log);
926    add!(1u64, 1u32);
927    nb = (nb + 7) & !7;
928    flush!();
929    out.truncate(pos);
930}