Skip to main content

libsais_rs/
lib.rs

1//! Rust translation of upstream [libsais](https://github.com/IlyaGrebnov/libsais)
2//! 2.10.4 by Ilya Grebnov.
3//!
4//! This module exposes the 32-bit suffix array, BWT, unBWT, PLCP and LCP entry
5//! points (mirroring `libsais.h`). The 16-bit (`libsais16`), 64-bit
6//! (`libsais64`) and 16-bit/64-bit (`libsais16x64`) variants live in the
7//! sibling modules.
8
9use std::marker::PhantomData;
10use std::mem;
11
12use rayon::prelude::*;
13
14pub mod libsais16;
15pub mod libsais16x64;
16pub mod libsais64;
17pub use libsais16::{libsais16, SaSint as SaSint16, SaUint as SaUint16};
18pub use libsais16x64::{libsais16x64, SaSint as SaSint16x64, SaUint as SaUint16x64};
19pub use libsais64::{libsais64, SaSint as SaSint64, SaUint as SaUint64};
20
21pub type SaSint = i32;
22pub type SaUint = u32;
23pub type FastSint = isize;
24pub type FastUint = usize;
25
26pub const SAINT_BIT: u32 = 32;
27pub const SAINT_MAX: SaSint = i32::MAX;
28pub const SAINT_MIN: SaSint = i32::MIN;
29
30pub const ALPHABET_SIZE: usize = 1usize << 8;
31pub const UNBWT_FASTBITS: usize = 17;
32
33/// Software prefetch for read. Mirrors C `__builtin_prefetch(p, 0, 3)` /
34/// `_mm_prefetch(p, _MM_HINT_T0)`. Hint-only: passing a pointer outside an
35/// allocation (or null) is safe, the CPU just ignores the hint.
36#[inline(always)]
37pub(crate) fn libsais_prefetchr<T>(ptr: *const T) {
38    #[cfg(target_arch = "x86_64")]
39    unsafe {
40        std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
41    }
42    #[cfg(not(target_arch = "x86_64"))]
43    {
44        let _ = ptr;
45    }
46}
47
48/// Software prefetch for write. Same backing as `_mm_prefetch(_MM_HINT_T0)`
49/// since `prefetchw` requires an extension not always present; the T0 hint is
50/// adequate for the libsais usage and keeps behavior portable across CPUs.
51#[inline(always)]
52pub(crate) fn libsais_prefetchw<T>(ptr: *const T) {
53    #[cfg(target_arch = "x86_64")]
54    unsafe {
55        std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
56    }
57    #[cfg(not(target_arch = "x86_64"))]
58    {
59        let _ = ptr;
60    }
61}
62
63pub const SUFFIX_GROUP_BIT: u32 = SAINT_BIT - 1;
64pub const SUFFIX_GROUP_MARKER: SaSint = 1_i32 << (SUFFIX_GROUP_BIT - 1);
65
66pub const LIBSAIS_LOCAL_BUFFER_SIZE: usize = 2000;
67pub const LIBSAIS_PER_THREAD_CACHE_SIZE: usize = 24_576;
68
69pub const LIBSAIS_FLAGS_NONE: SaSint = 0;
70pub const LIBSAIS_FLAGS_BWT: SaSint = 1;
71pub const LIBSAIS_FLAGS_GSA: SaSint = 2;
72
73/// Runs `f` on a rayon thread pool sized to `threads` workers.
74///
75/// Mirrors the OpenMP `num_threads(threads)` clause used throughout upstream
76/// libsais. If we are already executing inside a rayon worker (i.e. the
77/// caller invoked us from within another rayon `install`/`scope`), we reuse
78/// the ambient pool instead of building a nested one — building a nested
79/// pool from inside a worker can deadlock on the rayon job queue (the
80/// futex-deadlock symptom previously seen by downstream callers running
81/// libsais inside their own rayon pool).
82pub(crate) fn run_rayon_with_threads<R: Send>(threads: usize, f: impl FnOnce() -> R + Send) -> R {
83    if threads <= 1 || rayon::current_thread_index().is_some() {
84        return f();
85    }
86    match rayon::ThreadPoolBuilder::new().num_threads(threads).build() {
87        Ok(pool) => pool.install(f),
88        Err(_) => f(),
89    }
90}
91
92/// Raw `*mut T` wrapper that is `Send + Sync` so it can be moved into rayon
93/// closures without the borrow checker rejecting concurrent aliasing.
94///
95/// This exists to translate OpenMP-style parallel regions that take a single
96/// mutable buffer (e.g. the suffix array `SA`) and have each "thread" write
97/// to a disjoint slice of it. The C original implicitly shares the pointer
98/// across threads via OMP shared-memory semantics; the Rust port reproduces
99/// the same sharing through this raw-pointer alias.
100///
101/// SAFETY: every call to [`SyncMutPtr::as_slice`] hands out a `&mut [T]` that
102/// notionally covers the entire buffer. The caller MUST guarantee that
103/// concurrent threads only read and write disjoint indices — this is the
104/// same invariant OpenMP relies on, and is exactly what the `omp_block_start
105/// / omp_block_size` partitioning in libsais already establishes.
106#[derive(Copy, Clone)]
107pub(crate) struct SyncMutPtr<T> {
108    ptr: *mut T,
109    len: usize,
110}
111
112unsafe impl<T: Send> Send for SyncMutPtr<T> {}
113unsafe impl<T: Send> Sync for SyncMutPtr<T> {}
114
115impl<T> SyncMutPtr<T> {
116    pub(crate) fn new(slice: &mut [T]) -> Self {
117        Self {
118            ptr: slice.as_mut_ptr(),
119            len: slice.len(),
120        }
121    }
122
123    /// Reconstruct a `&mut [T]` view of the underlying buffer.
124    ///
125    /// SAFETY: caller must ensure no other live `&mut [T]` (whether obtained
126    /// through this method on another thread or via the original Rust
127    /// reference) accesses the same indices concurrently. Inside an OpenMP-
128    /// style parallel region this is upheld by the block partitioning logic.
129    #[allow(clippy::mut_from_ref)]
130    pub(crate) unsafe fn as_slice<'a>(&'a self) -> &'a mut [T] {
131        std::slice::from_raw_parts_mut(self.ptr, self.len)
132    }
133}
134
135#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
136pub struct ThreadCache {
137    pub symbol: SaSint,
138    pub index: SaSint,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct ThreadState {
143    pub position: FastSint,
144    pub count: FastSint,
145    pub m: FastSint,
146    pub last_lms_suffix: FastSint,
147    pub buckets: Vec<SaSint>,
148    pub cache: Vec<ThreadCache>,
149}
150
151impl ThreadState {
152    fn new() -> Self {
153        Self {
154            position: 0,
155            count: 0,
156            m: 0,
157            last_lms_suffix: 0,
158            buckets: vec![0; 4 * ALPHABET_SIZE],
159            cache: vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE],
160        }
161    }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
165pub struct Context {
166    pub buckets: Vec<SaSint>,
167    pub thread_state: Option<Vec<ThreadState>>,
168    pub threads: FastSint,
169}
170
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct UnbwtContext {
173    pub bucket2: Vec<SaUint>,
174    pub fastbits: Vec<u16>,
175    pub buckets: Option<Vec<SaUint>>,
176    pub threads: FastSint,
177}
178
179/// Internal helper: buckets index2.
180#[doc(hidden)]
181pub fn buckets_index2(c: FastUint, s: FastUint) -> FastUint {
182    (c << 1) + s
183}
184
185/// Internal helper: buckets index4.
186#[doc(hidden)]
187pub fn buckets_index4(c: FastUint, s: FastUint) -> FastUint {
188    (c << 2) + s
189}
190
191/// Internal helper: align up.
192#[doc(hidden)]
193pub fn align_up(value: usize, alignment: usize) -> usize {
194    debug_assert!(alignment.is_power_of_two());
195    (value + alignment - 1) & !(alignment - 1)
196}
197
198/// Internal helper: alloc thread state.
199#[doc(hidden)]
200pub fn alloc_thread_state(threads: SaSint) -> Option<Vec<ThreadState>> {
201    if threads <= 0 {
202        return None;
203    }
204
205    let len = usize::try_from(threads).ok()?;
206    Some((0..len).map(|_| ThreadState::new()).collect())
207}
208
209/// Internal helper: create ctx main.
210#[doc(hidden)]
211pub fn create_ctx_main(threads: SaSint) -> Option<Context> {
212    if threads <= 0 {
213        return None;
214    }
215
216    let thread_state = if threads > 1 {
217        Some(alloc_thread_state(threads)?)
218    } else {
219        None
220    };
221
222    Some(Context {
223        buckets: vec![0; 8 * ALPHABET_SIZE],
224        thread_state,
225        threads: threads as FastSint,
226    })
227}
228
229/// Creates the libsais context that allows reusing allocated memory with each libsais operation.
230/// In multi-threaded environments, use one context per thread for parallel executions.
231///
232/// # Returns
233/// the libsais context, NULL otherwise.
234pub fn create_ctx() -> Option<Context> {
235    create_ctx_main(1)
236}
237
238/// Destroys the libsass context and free previusly allocated memory.
239///
240/// # Arguments
241/// - `ctx`: The libsais context (can be NULL).
242pub fn free_ctx(_ctx: Context) {}
243
244/// Internal helper: unbwt create ctx main.
245#[doc(hidden)]
246pub fn unbwt_create_ctx_main(threads: SaSint) -> Option<UnbwtContext> {
247    if threads <= 0 {
248        return None;
249    }
250
251    let buckets = if threads > 1 {
252        let len = usize::try_from(threads).ok()? * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE);
253        Some(vec![0; len])
254    } else {
255        None
256    };
257
258    Some(UnbwtContext {
259        bucket2: vec![0; ALPHABET_SIZE * ALPHABET_SIZE],
260        fastbits: vec![0; 1 + (1 << UNBWT_FASTBITS)],
261        buckets,
262        threads: threads as FastSint,
263    })
264}
265
266/// Internal helper: unbwt free ctx main.
267#[doc(hidden)]
268pub fn unbwt_free_ctx_main(_ctx: UnbwtContext) {}
269
270/// Creates the libsais reverse BWT context that allows reusing allocated memory with each libsais_unbwt_* operation.
271/// In multi-threaded environments, use one context per thread for parallel executions.
272///
273/// # Returns
274/// the libsais context, NULL otherwise.
275pub fn unbwt_create_ctx() -> Option<UnbwtContext> {
276    unbwt_create_ctx_main(1)
277}
278
279/// Destroys the libsass reverse BWT context and free previusly allocated memory.
280///
281/// # Arguments
282/// - `ctx`: The libsais context (can be NULL).
283pub fn unbwt_free_ctx(_ctx: UnbwtContext) {}
284
285/// Internal helper: count negative marked suffixes.
286#[doc(hidden)]
287pub fn count_negative_marked_suffixes(
288    sa: &[SaSint],
289    block_start: FastSint,
290    block_size: FastSint,
291) -> SaSint {
292    block_slice(sa, block_start, block_size)
293        .iter()
294        .map(|&value| SaSint::from(value < 0))
295        .sum()
296}
297
298/// Internal helper: count zero marked suffixes.
299#[doc(hidden)]
300pub fn count_zero_marked_suffixes(
301    sa: &[SaSint],
302    block_start: FastSint,
303    block_size: FastSint,
304) -> SaSint {
305    block_slice(sa, block_start, block_size)
306        .iter()
307        .map(|&value| SaSint::from(value == 0))
308        .sum()
309}
310
311/// Internal helper: place cached suffixes.
312#[doc(hidden)]
313pub fn place_cached_suffixes(
314    sa: &mut [SaSint],
315    cache: &[ThreadCache],
316    block_start: FastSint,
317    block_size: FastSint,
318) {
319    let start = usize::try_from(block_start).expect("block_start must be non-negative");
320    let len = usize::try_from(block_size).expect("block_size must be non-negative");
321    let entries = if cache.len() >= start + len {
322        &cache[start..start + len]
323    } else {
324        &cache[..len]
325    };
326
327    for entry in entries {
328        let slot = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
329        sa[slot] = entry.index;
330    }
331}
332
333/// Internal helper: compact and place cached suffixes.
334#[doc(hidden)]
335pub fn compact_and_place_cached_suffixes(
336    sa: &mut [SaSint],
337    cache: &mut [ThreadCache],
338    block_start: FastSint,
339    block_size: FastSint,
340) {
341    let start = usize::try_from(block_start).expect("block_start must be non-negative");
342    let len = usize::try_from(block_size).expect("block_size must be non-negative");
343    let read_start = if cache.len() >= start + len { start } else { 0 };
344    let read_end = read_start + len;
345
346    let mut write = read_start;
347    for read in read_start..read_end {
348        let entry = cache[read];
349        if entry.symbol >= 0 {
350            cache[write] = entry;
351            write += 1;
352        }
353    }
354
355    place_cached_suffixes(sa, cache, block_start, (write - read_start) as FastSint);
356}
357
358/// Internal helper: flip suffix markers (OpenMP variant).
359#[doc(hidden)]
360pub fn flip_suffix_markers_omp(sa: &mut [SaSint], l: SaSint, threads: SaSint) {
361    let len = usize::try_from(l).expect("l must be non-negative");
362    let omp_num_threads = if threads > 1 && l >= 65_536 {
363        usize::try_from(threads).expect("threads must be non-negative")
364    } else {
365        1
366    };
367    if omp_num_threads > 1 {
368        let chunk_size = ((len / omp_num_threads) & !15usize).max(16);
369        run_rayon_with_threads(omp_num_threads, || {
370            sa[..len].par_chunks_mut(chunk_size).for_each(|chunk| {
371                for value in chunk {
372                    *value ^= SAINT_MIN;
373                }
374            });
375        });
376        return;
377    }
378
379    let omp_block_stride = (len / omp_num_threads) & !15usize;
380    for omp_thread_num in 0..omp_num_threads {
381        let omp_block_start = omp_thread_num * omp_block_stride;
382        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
383            omp_block_stride
384        } else {
385            len - omp_block_start
386        };
387        for value in &mut sa[omp_block_start..omp_block_start + omp_block_size] {
388            *value ^= SAINT_MIN;
389        }
390    }
391}
392
393/// Internal helper: gather lms suffixes 8u.
394#[doc(hidden)]
395pub fn gather_lms_suffixes_8u(
396    t: &[u8],
397    sa: &mut [SaSint],
398    n: SaSint,
399    mut m: FastSint,
400    omp_block_start: FastSint,
401    omp_block_size: FastSint,
402) {
403    if omp_block_size <= 0 {
404        return;
405    }
406
407    let n = usize::try_from(n).expect("n must be non-negative");
408    let block_start =
409        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
410    let block_size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
411
412    let mut j = block_start + block_size;
413    let mut c0 = t[block_start + block_size - 1] as FastSint;
414    let mut c1 = -1;
415    while j < n {
416        c1 = t[j] as FastSint;
417        if c1 != c0 {
418            break;
419        }
420        j += 1;
421    }
422
423    let mut f0 = usize::from(c0 >= c1);
424    let mut f1: usize;
425    let mut i = block_start + block_size - 2;
426    let limit = block_start + 3;
427
428    while i >= limit {
429        c1 = t[i] as FastSint;
430        f1 = usize::from(c1 > (c0 - f0 as FastSint));
431        sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
432        m -= (f1 & !f0) as FastSint;
433
434        c0 = t[i - 1] as FastSint;
435        f0 = usize::from(c0 > (c1 - f1 as FastSint));
436        sa[usize::try_from(m).expect("m must be non-negative")] = i as SaSint;
437        m -= (f0 & !f1) as FastSint;
438
439        c1 = t[i - 2] as FastSint;
440        f1 = usize::from(c1 > (c0 - f0 as FastSint));
441        sa[usize::try_from(m).expect("m must be non-negative")] = (i - 1) as SaSint;
442        m -= (f1 & !f0) as FastSint;
443
444        c0 = t[i - 3] as FastSint;
445        f0 = usize::from(c0 > (c1 - f1 as FastSint));
446        sa[usize::try_from(m).expect("m must be non-negative")] = (i - 2) as SaSint;
447        m -= (f0 & !f1) as FastSint;
448
449        if i < 4 {
450            break;
451        }
452        i -= 4;
453    }
454
455    let tail_limit = limit - 3;
456    while i >= tail_limit {
457        c1 = c0;
458        c0 = t[i] as FastSint;
459        f1 = f0;
460        f0 = usize::from(c0 > (c1 - f1 as FastSint));
461        sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
462        m -= (f0 & !f1) as FastSint;
463        if i == 0 {
464            break;
465        }
466        i -= 1;
467    }
468
469    sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
470}
471
472/// Internal helper: gather lms suffixes 8u (OpenMP variant).
473#[doc(hidden)]
474pub fn gather_lms_suffixes_8u_omp(
475    t: &[u8],
476    sa: &mut [SaSint],
477    n: SaSint,
478    threads: SaSint,
479    thread_state: &mut [ThreadState],
480) {
481    let n_usize = usize::try_from(n).expect("n must be non-negative");
482    let omp_num_threads = if threads > 1 && n >= 65_536 {
483        usize::try_from(threads)
484            .expect("threads must be non-negative")
485            .min(thread_state.len())
486            .max(1)
487    } else {
488        1
489    };
490    if omp_num_threads == 1 {
491        gather_lms_suffixes_8u(t, sa, n, n as FastSint - 1, 0, n as FastSint);
492        return;
493    }
494
495    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
496    let mut suffix_counts_after = vec![0 as FastSint; omp_num_threads];
497    let mut m = 0 as FastSint;
498    for omp_thread_num in (0..omp_num_threads).rev() {
499        suffix_counts_after[omp_thread_num] = m;
500        m += thread_state[omp_thread_num].m;
501    }
502
503    let sa_ptr = SyncMutPtr::new(sa);
504    let suffix_counts_after_slice: &[FastSint] = &suffix_counts_after;
505    let last_lms_suffixes: Vec<FastSint> = thread_state[..omp_num_threads]
506        .iter()
507        .map(|s| {
508            if s.m > 0 {
509                s.last_lms_suffix
510            } else {
511                FastSint::MIN
512            }
513        })
514        .collect();
515
516    run_rayon_with_threads(omp_num_threads, || {
517        (0..omp_num_threads)
518            .into_par_iter()
519            .for_each(|omp_thread_num| {
520                let omp_block_start = omp_thread_num * omp_block_stride;
521                let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
522                    omp_block_stride
523                } else {
524                    n_usize - omp_block_start
525                };
526                // SAFETY: disjoint per-thread block plus a single tail write to
527                // sa[n-1-suffix_counts_after[t]], which is unique per thread.
528                let sa = unsafe { sa_ptr.as_slice() };
529                gather_lms_suffixes_8u(
530                    t,
531                    sa,
532                    n,
533                    n as FastSint - 1 - suffix_counts_after_slice[omp_thread_num],
534                    omp_block_start as FastSint,
535                    omp_block_size as FastSint,
536                );
537            });
538    });
539
540    for omp_thread_num in 0..omp_num_threads {
541        if last_lms_suffixes[omp_thread_num] != FastSint::MIN {
542            let dst = usize::try_from(n as FastSint - 1 - suffix_counts_after[omp_thread_num])
543                .expect("destination must be non-negative");
544            sa[dst] = last_lms_suffixes[omp_thread_num] as SaSint;
545        }
546    }
547}
548
549/// Internal helper: gather lms suffixes 32s.
550#[doc(hidden)]
551pub fn gather_lms_suffixes_32s(t: &[SaSint], sa: &mut [SaSint], n: SaSint) -> SaSint {
552    let n_usize = usize::try_from(n).expect("n must be non-negative");
553    let mut i = n as FastSint - 2;
554    let mut m = n_usize - 1;
555    let mut f0 = 1usize;
556    let mut f1: usize;
557    let mut c0 = t[n_usize - 1] as FastSint;
558    let mut c1: FastSint;
559
560    while i >= 3 {
561        c1 = t[i as usize] as FastSint;
562        f1 = usize::from(c1 > (c0 - f0 as FastSint));
563        sa[m] = (i + 1) as SaSint;
564        m -= f1 & !f0;
565
566        c0 = t[(i - 1) as usize] as FastSint;
567        f0 = usize::from(c0 > (c1 - f1 as FastSint));
568        sa[m] = i as SaSint;
569        m -= f0 & !f1;
570
571        c1 = t[(i - 2) as usize] as FastSint;
572        f1 = usize::from(c1 > (c0 - f0 as FastSint));
573        sa[m] = (i - 1) as SaSint;
574        m -= f1 & !f0;
575
576        c0 = t[(i - 3) as usize] as FastSint;
577        f0 = usize::from(c0 > (c1 - f1 as FastSint));
578        sa[m] = (i - 2) as SaSint;
579        m -= f0 & !f1;
580
581        i -= 4;
582    }
583
584    while i >= 0 {
585        c1 = c0;
586        c0 = t[i as usize] as FastSint;
587        f1 = f0;
588        f0 = usize::from(c0 > (c1 - f1 as FastSint));
589        sa[m] = (i + 1) as SaSint;
590        m -= f0 & !f1;
591        i -= 1;
592    }
593
594    (n_usize - 1 - m) as SaSint
595}
596
597/// Internal helper: gather compacted lms suffixes 32s.
598#[doc(hidden)]
599pub fn gather_compacted_lms_suffixes_32s(t: &[SaSint], sa: &mut [SaSint], n: SaSint) -> SaSint {
600    let n_usize = usize::try_from(n).expect("n must be non-negative");
601    let mut i = n as FastSint - 2;
602    let mut m = n_usize - 1;
603    let mut f0 = 1usize;
604    let mut f1: usize;
605    let mut c0 = t[n_usize - 1] as FastSint;
606    let mut c1: FastSint;
607
608    while i >= 3 {
609        c1 = t[i as usize] as FastSint;
610        f1 = usize::from(c1 > (c0 - f0 as FastSint));
611        sa[m] = (i + 1) as SaSint;
612        m -= f1 & !f0 & usize::from(c0 >= 0);
613
614        c0 = t[(i - 1) as usize] as FastSint;
615        f0 = usize::from(c0 > (c1 - f1 as FastSint));
616        sa[m] = i as SaSint;
617        m -= f0 & !f1 & usize::from(c1 >= 0);
618
619        c1 = t[(i - 2) as usize] as FastSint;
620        f1 = usize::from(c1 > (c0 - f0 as FastSint));
621        sa[m] = (i - 1) as SaSint;
622        m -= f1 & !f0 & usize::from(c0 >= 0);
623
624        c0 = t[(i - 3) as usize] as FastSint;
625        f0 = usize::from(c0 > (c1 - f1 as FastSint));
626        sa[m] = (i - 2) as SaSint;
627        m -= f0 & !f1 & usize::from(c1 >= 0);
628
629        i -= 4;
630    }
631
632    while i >= 0 {
633        c1 = c0;
634        c0 = t[i as usize] as FastSint;
635        f1 = f0;
636        f0 = usize::from(c0 > (c1 - f1 as FastSint));
637        sa[m] = (i + 1) as SaSint;
638        m -= f0 & !f1 & usize::from(c1 >= 0);
639        i -= 1;
640    }
641
642    (n_usize - 1 - m) as SaSint
643}
644
645/// Internal helper: count lms suffixes 32s 4k.
646#[doc(hidden)]
647pub fn count_lms_suffixes_32s_4k(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
648    buckets.fill(0);
649    let n_usize = usize::try_from(n).expect("n must be non-negative");
650    let _k_usize = usize::try_from(k).expect("k must be non-negative");
651    let mut i = n as FastSint - 2;
652    let mut f0 = 1usize;
653    let mut f1: usize;
654    let mut c0 = t[n_usize - 1] as FastSint;
655    let mut c1: FastSint;
656
657    while i >= 3 {
658        c1 = t[i as usize] as FastSint;
659        f1 = usize::from(c1 > (c0 - f0 as FastSint));
660        buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
661
662        c0 = t[(i - 1) as usize] as FastSint;
663        f0 = usize::from(c0 > (c1 - f1 as FastSint));
664        buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
665
666        c1 = t[(i - 2) as usize] as FastSint;
667        f1 = usize::from(c1 > (c0 - f0 as FastSint));
668        buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
669
670        c0 = t[(i - 3) as usize] as FastSint;
671        f0 = usize::from(c0 > (c1 - f1 as FastSint));
672        buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
673
674        i -= 4;
675    }
676
677    while i >= 0 {
678        c1 = c0;
679        c0 = t[i as usize] as FastSint;
680        f1 = f0;
681        f0 = usize::from(c0 > (c1 - f1 as FastSint));
682        buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
683        i -= 1;
684    }
685
686    buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0)] += 1;
687}
688
689/// Internal helper: count lms suffixes 32s 2k.
690#[doc(hidden)]
691pub fn count_lms_suffixes_32s_2k(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
692    buckets.fill(0);
693    let n_usize = usize::try_from(n).expect("n must be non-negative");
694    let _k_usize = usize::try_from(k).expect("k must be non-negative");
695    let mut i = n as FastSint - 2;
696    let mut f0 = 1usize;
697    let mut f1: usize;
698    let mut c0 = t[n_usize - 1] as FastSint;
699    let mut c1: FastSint;
700
701    while i >= 3 {
702        c1 = t[i as usize] as FastSint;
703        f1 = usize::from(c1 > (c0 - f0 as FastSint));
704        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
705
706        c0 = t[(i - 1) as usize] as FastSint;
707        f0 = usize::from(c0 > (c1 - f1 as FastSint));
708        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
709
710        c1 = t[(i - 2) as usize] as FastSint;
711        f1 = usize::from(c1 > (c0 - f0 as FastSint));
712        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
713
714        c0 = t[(i - 3) as usize] as FastSint;
715        f0 = usize::from(c0 > (c1 - f1 as FastSint));
716        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
717
718        i -= 4;
719    }
720
721    while i >= 0 {
722        c1 = c0;
723        c0 = t[i as usize] as FastSint;
724        f1 = f0;
725        f0 = usize::from(c0 > (c1 - f1 as FastSint));
726        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
727        i -= 1;
728    }
729
730    buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, 0)] += 1;
731}
732
733/// Internal helper: count compacted lms suffixes 32s 2k.
734#[doc(hidden)]
735pub fn count_compacted_lms_suffixes_32s_2k(
736    t: &[SaSint],
737    n: SaSint,
738    k: SaSint,
739    buckets: &mut [SaSint],
740) {
741    buckets.fill(0);
742    let n_usize = usize::try_from(n).expect("n must be non-negative");
743    let _k_usize = usize::try_from(k).expect("k must be non-negative");
744    let mut i = n as FastSint - 2;
745    let mut f0 = 1usize;
746    let mut f1: usize;
747    let mut c0 = t[n_usize - 1] as FastSint;
748    let mut c1: FastSint;
749
750    while i >= 3 {
751        c1 = t[i as usize] as FastSint;
752        f1 = usize::from(c1 > (c0 - f0 as FastSint));
753        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
754
755        c0 = t[(i - 1) as usize] as FastSint;
756        f0 = usize::from(c0 > (c1 - f1 as FastSint));
757        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
758
759        c1 = t[(i - 2) as usize] as FastSint;
760        f1 = usize::from(c1 > (c0 - f0 as FastSint));
761        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
762
763        c0 = t[(i - 3) as usize] as FastSint;
764        f0 = usize::from(c0 > (c1 - f1 as FastSint));
765        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
766
767        i -= 4;
768    }
769
770    while i >= 0 {
771        c1 = c0;
772        c0 = t[i as usize] as FastSint;
773        f1 = f0;
774        f0 = usize::from(c0 > (c1 - f1 as FastSint));
775        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
776        i -= 1;
777    }
778
779    buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, 0)] += 1;
780}
781
782/// Internal helper: count and gather lms suffixes 8u.
783#[doc(hidden)]
784pub fn count_and_gather_lms_suffixes_8u(
785    t: &[u8],
786    sa: &mut [SaSint],
787    n: SaSint,
788    buckets: &mut [SaSint],
789    omp_block_start: FastSint,
790    omp_block_size: FastSint,
791) -> SaSint {
792    buckets.fill(0);
793    let n = n as FastSint;
794    let mut m = omp_block_start + omp_block_size - 1;
795
796    if omp_block_size > 0 {
797        let prefetch_distance = 256 as FastSint;
798        let mut j = m + 1;
799        let mut c0 = t[m as usize] as FastSint;
800        let mut c1 = -1;
801        while j < n {
802            c1 = t[j as usize] as FastSint;
803            if c1 != c0 {
804                break;
805            }
806            j += 1;
807        }
808
809        let mut f0 = usize::from(c0 >= c1);
810        let mut f1: usize;
811        let mut i = m - 1;
812        let limit = omp_block_start + 3;
813
814        while i >= limit {
815            let _prefetch_index = i - prefetch_distance;
816            c1 = t[i as usize] as FastSint;
817            f1 = usize::from(c1 > (c0 - f0 as FastSint));
818            sa[m as usize] = (i + 1) as SaSint;
819            m -= (f1 & !f0) as FastSint;
820            buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
821
822            c0 = t[(i - 1) as usize] as FastSint;
823            f0 = usize::from(c0 > (c1 - f1 as FastSint));
824            sa[m as usize] = i as SaSint;
825            m -= (f0 & !f1) as FastSint;
826            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
827
828            c1 = t[(i - 2) as usize] as FastSint;
829            f1 = usize::from(c1 > (c0 - f0 as FastSint));
830            sa[m as usize] = (i - 1) as SaSint;
831            m -= (f1 & !f0) as FastSint;
832            buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
833
834            c0 = t[(i - 3) as usize] as FastSint;
835            f0 = usize::from(c0 > (c1 - f1 as FastSint));
836            sa[m as usize] = (i - 2) as SaSint;
837            m -= (f0 & !f1) as FastSint;
838            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
839
840            i -= 4;
841        }
842
843        let tail_limit = limit - 3;
844        while i >= tail_limit {
845            c1 = c0;
846            c0 = t[i as usize] as FastSint;
847            f1 = f0;
848            f0 = usize::from(c0 > (c1 - f1 as FastSint));
849            sa[m as usize] = (i + 1) as SaSint;
850            m -= (f0 & !f1) as FastSint;
851            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
852            i -= 1;
853        }
854
855        c1 = if i >= 0 {
856            t[i as usize] as FastSint
857        } else {
858            -1
859        };
860        f1 = usize::from(c1 > (c0 - f0 as FastSint));
861        sa[m as usize] = (i + 1) as SaSint;
862        m -= (f1 & !f0) as FastSint;
863        buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
864    }
865
866    (omp_block_start + omp_block_size - 1 - m) as SaSint
867}
868
869/// Internal helper: count and gather lms suffixes 8u (OpenMP variant).
870#[doc(hidden)]
871pub fn count_and_gather_lms_suffixes_8u_omp(
872    t: &[u8],
873    sa: &mut [SaSint],
874    n: SaSint,
875    buckets: &mut [SaSint],
876    threads: SaSint,
877    thread_state: &mut [ThreadState],
878) -> SaSint {
879    let mut m = 0;
880    let n_usize = usize::try_from(n).expect("n must be non-negative");
881    let omp_num_threads = if threads > 1 && n >= 65_536 {
882        usize::try_from(threads)
883            .expect("threads must be non-negative")
884            .min(thread_state.len())
885            .max(1)
886    } else {
887        1
888    };
889    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
890
891    if omp_num_threads == 1 {
892        return count_and_gather_lms_suffixes_8u(t, sa, n, buckets, 0, n as FastSint);
893    }
894
895    let sa_ptr = SyncMutPtr::new(sa);
896    run_rayon_with_threads(omp_num_threads, || {
897        thread_state[..omp_num_threads]
898            .par_iter_mut()
899            .enumerate()
900            .for_each(|(omp_thread_num, state)| {
901                let omp_block_start = omp_thread_num * omp_block_stride;
902                let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
903                    omp_block_stride
904                } else {
905                    n_usize - omp_block_start
906                };
907
908                // SAFETY: each thread writes only to sa[omp_block_start..omp_block_start+omp_block_size]
909                // via count_and_gather_lms_suffixes_8u's m index walking backward from
910                // omp_block_start+omp_block_size-1; the regions are disjoint per OMP partitioning.
911                let sa = unsafe { sa_ptr.as_slice() };
912
913                state.position = FastSint::try_from(omp_block_start + omp_block_size)
914                    .expect("position must fit FastSint");
915                state.m = FastSint::try_from(count_and_gather_lms_suffixes_8u(
916                    t,
917                    sa,
918                    n,
919                    &mut state.buckets,
920                    FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
921                    FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
922                ))
923                .expect("m must fit FastSint");
924
925                if state.m > 0 {
926                    let position =
927                        usize::try_from(state.position).expect("position must be non-negative");
928                    state.last_lms_suffix =
929                        FastSint::try_from(sa[position - 1]).expect("suffix must fit FastSint");
930                }
931            });
932    });
933
934    buckets.fill(0);
935
936    for tnum in (0..omp_num_threads).rev() {
937        let state = &mut thread_state[tnum];
938        m += SaSint::try_from(state.m).expect("m must fit SaSint");
939
940        if tnum + 1 < omp_num_threads && state.m > 0 {
941            let position = usize::try_from(state.position).expect("position must be non-negative");
942            let count = usize::try_from(state.m).expect("m must be non-negative");
943            let dst = n_usize - usize::try_from(m).expect("m must be non-negative");
944            sa.copy_within(position - count..position, dst);
945        }
946
947        for s in 0..4 * ALPHABET_SIZE {
948            let a = buckets[s];
949            let b = state.buckets[s];
950            buckets[s] = a + b;
951            state.buckets[s] = a;
952        }
953    }
954
955    m
956}
957
958/// Internal helper: count and gather lms suffixes 32s 4k.
959#[doc(hidden)]
960pub fn count_and_gather_lms_suffixes_32s_4k(
961    t: &[SaSint],
962    sa: &mut [SaSint],
963    n: SaSint,
964    k: SaSint,
965    buckets: &mut [SaSint],
966    omp_block_start: FastSint,
967    omp_block_size: FastSint,
968) -> SaSint {
969    buckets.fill(0);
970    let n = n as FastSint;
971    let _k = k as FastSint;
972    let mut m = omp_block_start + omp_block_size - 1;
973
974    if omp_block_size > 0 {
975        let prefetch_distance = 64 as FastSint;
976        let mut j = m + 1;
977        let mut c0 = t[m as usize] as FastSint;
978        let mut c1 = -1;
979
980        while j < n {
981            c1 = t[j as usize] as FastSint;
982            if c1 != c0 {
983                break;
984            }
985            j += 1;
986        }
987
988        let mut f0 = usize::from(c0 >= c1);
989        let mut f1: usize;
990        let mut i = m - 1;
991        let limit = omp_block_start + prefetch_distance + 3;
992
993        while i >= limit {
994            let _prefetch_index = i - 2 * prefetch_distance;
995            c1 = t[i as usize] as FastSint;
996            f1 = usize::from(c1 > (c0 - f0 as FastSint));
997            sa[m as usize] = (i + 1) as SaSint;
998            m -= (f1 & !f0) as FastSint;
999            buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
1000
1001            c0 = t[(i - 1) as usize] as FastSint;
1002            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1003            sa[m as usize] = i as SaSint;
1004            m -= (f0 & !f1) as FastSint;
1005            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
1006
1007            c1 = t[(i - 2) as usize] as FastSint;
1008            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1009            sa[m as usize] = (i - 1) as SaSint;
1010            m -= (f1 & !f0) as FastSint;
1011            buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
1012
1013            c0 = t[(i - 3) as usize] as FastSint;
1014            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1015            sa[m as usize] = (i - 2) as SaSint;
1016            m -= (f0 & !f1) as FastSint;
1017            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
1018
1019            i -= 4;
1020        }
1021
1022        let tail_limit = omp_block_start;
1023        while i >= tail_limit {
1024            c1 = c0;
1025            c0 = t[i as usize] as FastSint;
1026            f1 = f0;
1027            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1028            sa[m as usize] = (i + 1) as SaSint;
1029            m -= (f0 & !f1) as FastSint;
1030            buckets[buckets_index4((c1 as SaSint & SAINT_MAX) as usize, f1 + f1 + f0)] += 1;
1031            i -= 1;
1032        }
1033
1034        c1 = if i >= 0 {
1035            t[i as usize] as FastSint
1036        } else {
1037            -1
1038        };
1039        f1 = usize::from(c1 > (c0 - f0 as FastSint));
1040        sa[m as usize] = (i + 1) as SaSint;
1041        m -= (f1 & !f0) as FastSint;
1042        buckets[buckets_index4((c0 as SaSint & SAINT_MAX) as usize, f0 + f0 + f1)] += 1;
1043    }
1044
1045    (omp_block_start + omp_block_size - 1 - m) as SaSint
1046}
1047
1048/// Internal helper: count and gather lms suffixes 32s 2k.
1049#[doc(hidden)]
1050pub fn count_and_gather_lms_suffixes_32s_2k(
1051    t: &[SaSint],
1052    sa: &mut [SaSint],
1053    n: SaSint,
1054    k: SaSint,
1055    buckets: &mut [SaSint],
1056    omp_block_start: FastSint,
1057    omp_block_size: FastSint,
1058) -> SaSint {
1059    buckets.fill(0);
1060    let n = n as FastSint;
1061    let _k = k as FastSint;
1062    let mut m = omp_block_start + omp_block_size - 1;
1063
1064    if omp_block_size > 0 {
1065        let prefetch_distance = 64 as FastSint;
1066        let mut j = m + 1;
1067        let mut c0 = t[m as usize] as FastSint;
1068        let mut c1 = -1;
1069
1070        while j < n {
1071            c1 = t[j as usize] as FastSint;
1072            if c1 != c0 {
1073                break;
1074            }
1075            j += 1;
1076        }
1077
1078        let mut f0 = usize::from(c0 >= c1);
1079        let mut f1: usize;
1080        let mut i = m - 1;
1081        let limit = omp_block_start + prefetch_distance + 3;
1082
1083        while i >= limit {
1084            let _prefetch_index = i - 2 * prefetch_distance;
1085            c1 = t[i as usize] as FastSint;
1086            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1087            sa[m as usize] = (i + 1) as SaSint;
1088            m -= (f1 & !f0) as FastSint;
1089            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1090
1091            c0 = t[(i - 1) as usize] as FastSint;
1092            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1093            sa[m as usize] = i as SaSint;
1094            m -= (f0 & !f1) as FastSint;
1095            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1096
1097            c1 = t[(i - 2) as usize] as FastSint;
1098            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1099            sa[m as usize] = (i - 1) as SaSint;
1100            m -= (f1 & !f0) as FastSint;
1101            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1102
1103            c0 = t[(i - 3) as usize] as FastSint;
1104            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1105            sa[m as usize] = (i - 2) as SaSint;
1106            m -= (f0 & !f1) as FastSint;
1107            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1108
1109            i -= 4;
1110        }
1111
1112        let tail_limit = omp_block_start;
1113        while i >= tail_limit {
1114            c1 = c0;
1115            c0 = t[i as usize] as FastSint;
1116            f1 = f0;
1117            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1118            sa[m as usize] = (i + 1) as SaSint;
1119            m -= (f0 & !f1) as FastSint;
1120            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1121            i -= 1;
1122        }
1123
1124        c1 = if i >= 0 {
1125            t[i as usize] as FastSint
1126        } else {
1127            -1
1128        };
1129        f1 = usize::from(c1 > (c0 - f0 as FastSint));
1130        sa[m as usize] = (i + 1) as SaSint;
1131        m -= (f1 & !f0) as FastSint;
1132        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1133    }
1134
1135    (omp_block_start + omp_block_size - 1 - m) as SaSint
1136}
1137
1138/// Internal helper: count and gather compacted lms suffixes 32s 2k.
1139#[doc(hidden)]
1140pub fn count_and_gather_compacted_lms_suffixes_32s_2k(
1141    t: &[SaSint],
1142    sa: &mut [SaSint],
1143    n: SaSint,
1144    k: SaSint,
1145    buckets: &mut [SaSint],
1146    omp_block_start: FastSint,
1147    omp_block_size: FastSint,
1148) -> SaSint {
1149    buckets.fill(0);
1150    let n_usize = usize::try_from(n).expect("n must be non-negative");
1151    let _k_usize = usize::try_from(k).expect("k must be non-negative");
1152    let block_start =
1153        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
1154    let block_size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
1155    let mut m = block_start + block_size - 1;
1156
1157    if omp_block_size > 0 {
1158        let mut j = m + 1;
1159        let mut c0 = t[m] as FastSint;
1160        let mut c1 = -1;
1161
1162        while j < n_usize {
1163            c1 = t[j] as FastSint;
1164            if c1 != c0 {
1165                break;
1166            }
1167            j += 1;
1168        }
1169
1170        let mut f0 = usize::from(c0 >= c1);
1171        let mut f1: usize;
1172        let mut i = m as FastSint - 1;
1173        let limit = block_start as FastSint + 3;
1174
1175        while i >= limit {
1176            c1 = t[i as usize] as FastSint;
1177            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1178            sa[m] = (i + 1) as SaSint;
1179            m -= f1 & !f0 & usize::from(c0 >= 0);
1180            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1181
1182            c0 = t[(i - 1) as usize] as FastSint;
1183            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1184            sa[m] = i as SaSint;
1185            m -= f0 & !f1 & usize::from(c1 >= 0);
1186            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1187
1188            c1 = t[(i - 2) as usize] as FastSint;
1189            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1190            sa[m] = (i - 1) as SaSint;
1191            m -= f1 & !f0 & usize::from(c0 >= 0);
1192            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1193
1194            c0 = t[(i - 3) as usize] as FastSint;
1195            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1196            sa[m] = (i - 2) as SaSint;
1197            m -= f0 & !f1 & usize::from(c1 >= 0);
1198            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1199
1200            i -= 4;
1201        }
1202
1203        let tail_limit = block_start as FastSint;
1204        while i >= tail_limit {
1205            c1 = c0;
1206            c0 = t[i as usize] as FastSint;
1207            f1 = f0;
1208            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1209            sa[m] = (i + 1) as SaSint;
1210            m -= f0 & !f1 & usize::from(c1 >= 0);
1211            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1212            i -= 1;
1213        }
1214
1215        c1 = if i >= 0 {
1216            t[i as usize] as FastSint
1217        } else {
1218            -1
1219        };
1220        f1 = usize::from(c1 > (c0 - f0 as FastSint));
1221        sa[m] = (i + 1) as SaSint;
1222        m -= f1 & !f0 & usize::from(c0 >= 0);
1223        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1224    }
1225
1226    (block_start + block_size - 1 - m) as SaSint
1227}
1228
1229/// Internal helper: get bucket stride.
1230#[doc(hidden)]
1231pub fn get_bucket_stride(
1232    free_space: FastSint,
1233    bucket_size: FastSint,
1234    num_buckets: FastSint,
1235) -> FastSint {
1236    let bucket_size_1024 = (bucket_size + 1023) & (-1024);
1237    if free_space / (num_buckets - 1) >= bucket_size_1024 {
1238        return bucket_size_1024;
1239    }
1240    let bucket_size_16 = (bucket_size + 15) & (-16);
1241    if free_space / (num_buckets - 1) >= bucket_size_16 {
1242        return bucket_size_16;
1243    }
1244    bucket_size
1245}
1246
1247/// Internal helper: count and gather lms suffixes 32s 4k nofs (OpenMP variant).
1248#[doc(hidden)]
1249pub fn count_and_gather_lms_suffixes_32s_4k_nofs_omp(
1250    t: &[SaSint],
1251    sa: &mut [SaSint],
1252    n: SaSint,
1253    k: SaSint,
1254    buckets: &mut [SaSint],
1255    threads: SaSint,
1256) -> SaSint {
1257    let m;
1258    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1259
1260    if omp_num_threads == 1 {
1261        m = count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint);
1262    } else {
1263        count_lms_suffixes_32s_4k(t, n, k, buckets);
1264        m = gather_lms_suffixes_32s(t, sa, n);
1265    }
1266
1267    m
1268}
1269
1270/// Internal helper: count and gather lms suffixes 32s 2k nofs (OpenMP variant).
1271#[doc(hidden)]
1272pub fn count_and_gather_lms_suffixes_32s_2k_nofs_omp(
1273    t: &[SaSint],
1274    sa: &mut [SaSint],
1275    n: SaSint,
1276    k: SaSint,
1277    buckets: &mut [SaSint],
1278    threads: SaSint,
1279) -> SaSint {
1280    let m;
1281    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1282
1283    if omp_num_threads == 1 {
1284        m = count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1285    } else {
1286        count_lms_suffixes_32s_2k(t, n, k, buckets);
1287        m = gather_lms_suffixes_32s(t, sa, n);
1288    }
1289
1290    m
1291}
1292
1293/// Internal helper: count and gather compacted lms suffixes 32s 2k nofs (OpenMP variant).
1294#[doc(hidden)]
1295pub fn count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
1296    t: &[SaSint],
1297    sa: &mut [SaSint],
1298    n: SaSint,
1299    k: SaSint,
1300    buckets: &mut [SaSint],
1301    threads: SaSint,
1302) -> SaSint {
1303    let m;
1304    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1305
1306    if omp_num_threads == 1 {
1307        m = count_and_gather_compacted_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1308    } else {
1309        count_compacted_lms_suffixes_32s_2k(t, n, k, buckets);
1310        m = gather_compacted_lms_suffixes_32s(t, sa, n);
1311    }
1312
1313    m
1314}
1315
1316/// Internal helper: count and gather lms suffixes 32s 4k fs (OpenMP variant).
1317#[doc(hidden)]
1318pub fn count_and_gather_lms_suffixes_32s_4k_fs_omp(
1319    t: &[SaSint],
1320    sa: &mut [SaSint],
1321    n: SaSint,
1322    k: SaSint,
1323    buckets: &mut [SaSint],
1324    local_buckets: SaSint,
1325    threads: SaSint,
1326    thread_state: &mut [ThreadState],
1327) -> SaSint {
1328    let n_usize = usize::try_from(n).expect("n must be non-negative");
1329    let k_usize = usize::try_from(k).expect("k must be non-negative");
1330    let omp_num_threads = usize::try_from(threads).expect("threads must be non-negative");
1331    let bucket_size = FastSint::try_from(4 * k_usize).expect("bucket size must fit FastSint");
1332
1333    if omp_num_threads <= 1 || n < 65_536 {
1334        return count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint);
1335    }
1336
1337    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
1338    let free_space = if local_buckets == 1 {
1339        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1340    } else if local_buckets > 1 {
1341        FastSint::try_from(local_buckets).expect("free space must fit FastSint")
1342    } else {
1343        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1344    };
1345    let bucket_stride = get_bucket_stride(
1346        free_space,
1347        bucket_size,
1348        FastSint::try_from(omp_num_threads).expect("thread count must fit FastSint"),
1349    );
1350    let bucket_size_usize = usize::try_from(bucket_size).expect("bucket size must be non-negative");
1351    let bucket_stride_usize =
1352        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1353    let workspace_len =
1354        bucket_size_usize + bucket_stride_usize.saturating_mul(omp_num_threads.saturating_sub(1));
1355    let mut workspace = vec![0; workspace_len];
1356
1357    {
1358        let sa_ptr = SyncMutPtr::new(sa);
1359        let ws_ptr = SyncMutPtr::new(&mut workspace);
1360        run_rayon_with_threads(omp_num_threads, || {
1361            thread_state[..omp_num_threads]
1362                .par_iter_mut()
1363                .enumerate()
1364                .for_each(|(omp_thread_num, state)| {
1365                    let omp_block_start = omp_thread_num * omp_block_stride;
1366                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1367                        omp_block_stride
1368                    } else {
1369                        n_usize - omp_block_start
1370                    };
1371                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1372                    let workspace_start = workspace_end - bucket_size_usize;
1373                    // SAFETY: disjoint sa[omp_block_start..omp_block_start+omp_block_size]
1374                    // and workspace[workspace_start..workspace_end] per thread.
1375                    let sa = unsafe { sa_ptr.as_slice() };
1376                    let workspace = unsafe { ws_ptr.as_slice() };
1377                    let count = count_and_gather_lms_suffixes_32s_4k(
1378                        t,
1379                        sa,
1380                        n,
1381                        k,
1382                        &mut workspace[workspace_start..workspace_end],
1383                        omp_block_start as FastSint,
1384                        omp_block_size as FastSint,
1385                    );
1386                    state.position = (omp_block_start + omp_block_size) as FastSint;
1387                    state.count = count as FastSint;
1388                });
1389        });
1390    }
1391
1392    let mut m = 0;
1393    for t in (0..omp_num_threads).rev() {
1394        m += thread_state[t].count as SaSint;
1395
1396        if t + 1 != omp_num_threads && thread_state[t].count > 0 {
1397            let src_end =
1398                usize::try_from(thread_state[t].position).expect("position must be non-negative");
1399            let src_start = src_end
1400                - usize::try_from(thread_state[t].count).expect("count must be non-negative");
1401            let dst_start = usize::try_from(n - m).expect("destination must be non-negative");
1402            sa.copy_within(src_start..src_end, dst_start);
1403        }
1404    }
1405
1406    let omp_num_threads = omp_num_threads - 1;
1407    let omp_block_stride = (bucket_size_usize / omp_num_threads) & !15usize;
1408    {
1409        let ws_ptr = SyncMutPtr::new(&mut workspace);
1410        run_rayon_with_threads(omp_num_threads, || {
1411            (0..omp_num_threads)
1412                .into_par_iter()
1413                .for_each(|omp_thread_num| {
1414                    let omp_block_start = omp_thread_num * omp_block_stride;
1415                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1416                        omp_block_stride
1417                    } else {
1418                        bucket_size_usize - omp_block_start
1419                    };
1420                    // SAFETY: accumulate_counts_s32 only writes to bucket00_start..
1421                    // bucket00_start+omp_block_size within its slice, and per-thread
1422                    // omp_block_start offsets make the absolute write ranges disjoint
1423                    // (same shape as the upstream OMP parallel region).
1424                    let workspace = unsafe { ws_ptr.as_slice() };
1425                    accumulate_counts_s32(
1426                        &mut workspace[omp_block_start..],
1427                        omp_block_size as FastSint,
1428                        bucket_stride,
1429                        FastSint::try_from(omp_num_threads + 1)
1430                            .expect("thread count must fit FastSint"),
1431                    );
1432                });
1433        });
1434    }
1435
1436    let accumulated_start = omp_num_threads * bucket_stride_usize;
1437    buckets[..bucket_size_usize]
1438        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size_usize]);
1439    m
1440}
1441
1442/// Internal helper: count and gather lms suffixes 32s 2k fs (OpenMP variant).
1443#[doc(hidden)]
1444pub fn count_and_gather_lms_suffixes_32s_2k_fs_omp(
1445    t: &[SaSint],
1446    sa: &mut [SaSint],
1447    n: SaSint,
1448    k: SaSint,
1449    buckets: &mut [SaSint],
1450    local_buckets: SaSint,
1451    threads: SaSint,
1452    thread_state: &mut [ThreadState],
1453) -> SaSint {
1454    let n_usize = usize::try_from(n).expect("n must be non-negative");
1455    let k_usize = usize::try_from(k).expect("k must be non-negative");
1456    let omp_num_threads = usize::try_from(threads).expect("threads must be non-negative");
1457    let bucket_size = FastSint::try_from(2 * k_usize).expect("bucket size must fit FastSint");
1458
1459    if omp_num_threads <= 1 || n < 65_536 {
1460        return count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1461    }
1462
1463    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
1464    let free_space = if local_buckets == 1 {
1465        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1466    } else if local_buckets > 1 {
1467        FastSint::try_from(local_buckets).expect("free space must fit FastSint")
1468    } else {
1469        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1470    };
1471    let bucket_stride = get_bucket_stride(
1472        free_space,
1473        bucket_size,
1474        FastSint::try_from(omp_num_threads).expect("thread count must fit FastSint"),
1475    );
1476    let bucket_size_usize = usize::try_from(bucket_size).expect("bucket size must be non-negative");
1477    let bucket_stride_usize =
1478        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1479    let workspace_len =
1480        bucket_size_usize + bucket_stride_usize.saturating_mul(omp_num_threads.saturating_sub(1));
1481    let mut workspace = vec![0; workspace_len];
1482
1483    {
1484        let sa_ptr = SyncMutPtr::new(sa);
1485        let ws_ptr = SyncMutPtr::new(&mut workspace);
1486        run_rayon_with_threads(omp_num_threads, || {
1487            thread_state[..omp_num_threads]
1488                .par_iter_mut()
1489                .enumerate()
1490                .for_each(|(omp_thread_num, state)| {
1491                    let omp_block_start = omp_thread_num * omp_block_stride;
1492                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1493                        omp_block_stride
1494                    } else {
1495                        n_usize - omp_block_start
1496                    };
1497                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1498                    let workspace_start = workspace_end - bucket_size_usize;
1499                    // SAFETY: disjoint sa block + disjoint workspace sub-range per thread.
1500                    let sa = unsafe { sa_ptr.as_slice() };
1501                    let workspace = unsafe { ws_ptr.as_slice() };
1502                    let count = count_and_gather_lms_suffixes_32s_2k(
1503                        t,
1504                        sa,
1505                        n,
1506                        k,
1507                        &mut workspace[workspace_start..workspace_end],
1508                        omp_block_start as FastSint,
1509                        omp_block_size as FastSint,
1510                    );
1511                    state.position = (omp_block_start + omp_block_size) as FastSint;
1512                    state.count = count as FastSint;
1513                });
1514        });
1515    }
1516
1517    let mut m = 0;
1518    for t in (0..omp_num_threads).rev() {
1519        m += thread_state[t].count as SaSint;
1520        if t + 1 != omp_num_threads && thread_state[t].count > 0 {
1521            let src_end =
1522                usize::try_from(thread_state[t].position).expect("position must be non-negative");
1523            let src_start = src_end
1524                - usize::try_from(thread_state[t].count).expect("count must be non-negative");
1525            let dst_start = usize::try_from(n - m).expect("destination must be non-negative");
1526            sa.copy_within(src_start..src_end, dst_start);
1527        }
1528    }
1529
1530    let omp_num_threads = omp_num_threads - 1;
1531    let omp_block_stride = (bucket_size_usize / omp_num_threads) & !15usize;
1532    {
1533        let ws_ptr = SyncMutPtr::new(&mut workspace);
1534        run_rayon_with_threads(omp_num_threads, || {
1535            (0..omp_num_threads)
1536                .into_par_iter()
1537                .for_each(|omp_thread_num| {
1538                    let omp_block_start = omp_thread_num * omp_block_stride;
1539                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1540                        omp_block_stride
1541                    } else {
1542                        bucket_size_usize - omp_block_start
1543                    };
1544                    // SAFETY: disjoint absolute write ranges via per-thread omp_block_start.
1545                    let workspace = unsafe { ws_ptr.as_slice() };
1546                    accumulate_counts_s32(
1547                        &mut workspace[omp_block_start..],
1548                        omp_block_size as FastSint,
1549                        bucket_stride,
1550                        FastSint::try_from(omp_num_threads + 1)
1551                            .expect("thread count must fit FastSint"),
1552                    );
1553                });
1554        });
1555    }
1556
1557    let accumulated_start = omp_num_threads * bucket_stride_usize;
1558    buckets[..bucket_size_usize]
1559        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size_usize]);
1560    m
1561}
1562
1563/// Internal helper: count and gather compacted lms suffixes 32s 2k fs (OpenMP variant).
1564#[doc(hidden)]
1565pub fn count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
1566    t: &[SaSint],
1567    sa: &mut [SaSint],
1568    n: SaSint,
1569    k: SaSint,
1570    buckets: &mut [SaSint],
1571    _local_buckets: SaSint,
1572    threads: SaSint,
1573    thread_state: &mut [ThreadState],
1574) {
1575    let n_usize = usize::try_from(n).expect("n must be non-negative");
1576    let k_usize = usize::try_from(k).expect("k must be non-negative");
1577    let thread_count = usize::try_from(threads).expect("threads must be non-negative");
1578    let bucket_size = 2 * k_usize;
1579
1580    if thread_count <= 1 || n < 65_536 {
1581        let _ =
1582            count_and_gather_compacted_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1583        return;
1584    }
1585
1586    if thread_state.len() < thread_count || sa.len() < 2 * n_usize {
1587        let _ =
1588            count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(t, sa, n, k, buckets, threads);
1589        return;
1590    }
1591
1592    let omp_block_stride = (n_usize / thread_count) & !15usize;
1593    let free_space = if _local_buckets != 0 {
1594        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1595    } else {
1596        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1597    };
1598    let bucket_stride = get_bucket_stride(
1599        free_space,
1600        FastSint::try_from(bucket_size).expect("bucket size must fit FastSint"),
1601        FastSint::try_from(thread_count).expect("thread count must fit FastSint"),
1602    );
1603    let bucket_stride_usize =
1604        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1605    let workspace_len =
1606        bucket_size + bucket_stride_usize.saturating_mul(thread_count.saturating_sub(1));
1607    let mut workspace = vec![0; workspace_len];
1608
1609    let usable_thread_state_len = thread_count.min(thread_state.len());
1610
1611    {
1612        let sa_ptr = SyncMutPtr::new(sa);
1613        let ws_ptr = SyncMutPtr::new(&mut workspace);
1614        let state_ptr = SyncMutPtr::new(thread_state);
1615        run_rayon_with_threads(thread_count, || {
1616            (0..thread_count)
1617                .into_par_iter()
1618                .for_each(|omp_thread_num| {
1619                    let omp_block_start = omp_thread_num * omp_block_stride;
1620                    let omp_block_size = if omp_thread_num + 1 < thread_count {
1621                        omp_block_stride
1622                    } else {
1623                        n_usize - omp_block_start
1624                    };
1625
1626                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1627                    let workspace_start = workspace_end - bucket_size;
1628                    // SAFETY: per-thread block_start partitions sa[n_usize..] into disjoint ranges,
1629                    // workspace[workspace_start..workspace_end] is unique per thread, and only
1630                    // thread_state[omp_thread_num] is touched (disjoint per thread).
1631                    let sa = unsafe { sa_ptr.as_slice() };
1632                    let workspace = unsafe { ws_ptr.as_slice() };
1633                    let count = count_and_gather_compacted_lms_suffixes_32s_2k(
1634                        t,
1635                        &mut sa[n_usize..],
1636                        n,
1637                        k,
1638                        &mut workspace[workspace_start..workspace_end],
1639                        omp_block_start as FastSint,
1640                        omp_block_size as FastSint,
1641                    );
1642
1643                    if omp_thread_num < usable_thread_state_len {
1644                        let states = unsafe { state_ptr.as_slice() };
1645                        states[omp_thread_num].position =
1646                            (omp_block_start + omp_block_size) as FastSint;
1647                        states[omp_thread_num].count = count as FastSint;
1648                    }
1649                });
1650        });
1651    }
1652
1653    let mut m = 0usize;
1654    for omp_thread_num in (0..thread_count).rev() {
1655        let count = usize::try_from(thread_state[omp_thread_num].count)
1656            .expect("count must be non-negative");
1657        m += count;
1658        if count > 0 {
1659            let position = usize::try_from(thread_state[omp_thread_num].position)
1660                .expect("position must be non-negative");
1661            let src_start = n_usize + position - count;
1662            let src_end = n_usize + position;
1663            let dst_start = n_usize - m;
1664            sa.copy_within(src_start..src_end, dst_start);
1665        }
1666    }
1667
1668    let accumulation_threads = thread_count;
1669    let omp_block_stride = (bucket_size / accumulation_threads) & !15usize;
1670    {
1671        let ws_ptr = SyncMutPtr::new(&mut workspace);
1672        run_rayon_with_threads(accumulation_threads, || {
1673            (0..accumulation_threads)
1674                .into_par_iter()
1675                .for_each(|omp_thread_num| {
1676                    let omp_block_start = omp_thread_num * omp_block_stride;
1677                    let omp_block_size = if omp_thread_num + 1 < accumulation_threads {
1678                        omp_block_stride
1679                    } else {
1680                        bucket_size - omp_block_start
1681                    };
1682                    // SAFETY: disjoint absolute write ranges via per-thread omp_block_start.
1683                    let workspace = unsafe { ws_ptr.as_slice() };
1684                    accumulate_counts_s32(
1685                        &mut workspace[omp_block_start..],
1686                        omp_block_size as FastSint,
1687                        bucket_stride,
1688                        FastSint::try_from(thread_count).expect("thread count must fit FastSint"),
1689                    );
1690                });
1691        });
1692    }
1693    let accumulated_start = (accumulation_threads - 1) * bucket_stride_usize;
1694    buckets[..bucket_size]
1695        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size]);
1696}
1697
1698/// Internal helper: count and gather lms suffixes 32s 4k (OpenMP variant).
1699#[doc(hidden)]
1700pub fn count_and_gather_lms_suffixes_32s_4k_omp(
1701    t: &[SaSint],
1702    sa: &mut [SaSint],
1703    n: SaSint,
1704    k: SaSint,
1705    buckets: &mut [SaSint],
1706    local_buckets: SaSint,
1707    threads: SaSint,
1708    thread_state: &mut [ThreadState],
1709) -> SaSint {
1710    let free_space = if local_buckets > 1 {
1711        local_buckets as FastSint
1712    } else if local_buckets != 0 {
1713        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1714    } else {
1715        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1716    };
1717    let threads_fast = threads as FastSint;
1718    let mut max_threads = (free_space / (((4 * k as FastSint) + 15) & -16)).min(threads_fast);
1719
1720    if max_threads > 1 && n >= 65_536 && n / k >= 2 {
1721        let thread_cap = (n / (16 * k)) as FastSint;
1722        if max_threads > thread_cap {
1723            max_threads = thread_cap;
1724        }
1725        return count_and_gather_lms_suffixes_32s_4k_fs_omp(
1726            t,
1727            sa,
1728            n,
1729            k,
1730            buckets,
1731            local_buckets,
1732            max_threads.max(2) as SaSint,
1733            thread_state,
1734        );
1735    }
1736
1737    if threads > 1 && n >= 65_536 {
1738        count_lms_suffixes_32s_4k(t, n, k, buckets);
1739        gather_lms_suffixes_32s(t, sa, n)
1740    } else {
1741        count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint)
1742    }
1743}
1744
1745/// Internal helper: count and gather lms suffixes 32s 2k (OpenMP variant).
1746#[doc(hidden)]
1747pub fn count_and_gather_lms_suffixes_32s_2k_omp(
1748    t: &[SaSint],
1749    sa: &mut [SaSint],
1750    n: SaSint,
1751    k: SaSint,
1752    buckets: &mut [SaSint],
1753    local_buckets: SaSint,
1754    threads: SaSint,
1755    thread_state: &mut [ThreadState],
1756) -> SaSint {
1757    let free_space = if local_buckets > 1 {
1758        local_buckets as FastSint
1759    } else if local_buckets != 0 {
1760        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1761    } else {
1762        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1763    };
1764    let threads_fast = threads as FastSint;
1765    let mut max_threads = (free_space / (((2 * k as FastSint) + 15) & -16)).min(threads_fast);
1766
1767    if max_threads > 1 && n >= 65_536 && n / k >= 2 {
1768        let thread_cap = (n / (8 * k)) as FastSint;
1769        if max_threads > thread_cap {
1770            max_threads = thread_cap;
1771        }
1772        return count_and_gather_lms_suffixes_32s_2k_fs_omp(
1773            t,
1774            sa,
1775            n,
1776            k,
1777            buckets,
1778            local_buckets,
1779            max_threads.max(2) as SaSint,
1780            thread_state,
1781        );
1782    }
1783
1784    if threads > 1 && n >= 65_536 {
1785        count_lms_suffixes_32s_2k(t, n, k, buckets);
1786        gather_lms_suffixes_32s(t, sa, n)
1787    } else {
1788        count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint)
1789    }
1790}
1791
1792/// Internal helper: count and gather compacted lms suffixes 32s 2k (OpenMP variant).
1793#[doc(hidden)]
1794pub fn count_and_gather_compacted_lms_suffixes_32s_2k_omp(
1795    t: &[SaSint],
1796    sa: &mut [SaSint],
1797    n: SaSint,
1798    k: SaSint,
1799    buckets: &mut [SaSint],
1800    local_buckets: SaSint,
1801    threads: SaSint,
1802    thread_state: &mut [ThreadState],
1803) {
1804    let free_space = if local_buckets != 0 {
1805        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1806    } else {
1807        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1808    };
1809    let threads_fast = threads as FastSint;
1810    let mut max_threads = (free_space / (((2 * k as FastSint) + 15) & -16)).min(threads_fast);
1811
1812    if local_buckets == 0 && max_threads > 1 && n >= 65_536 && n / k >= 2 {
1813        let thread_cap = (n / (8 * k)) as FastSint;
1814        if max_threads > thread_cap {
1815            max_threads = thread_cap;
1816        }
1817        count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
1818            t,
1819            sa,
1820            n,
1821            k,
1822            buckets,
1823            local_buckets,
1824            max_threads.max(2) as SaSint,
1825            thread_state,
1826        );
1827        return;
1828    }
1829
1830    let _ = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(t, sa, n, k, buckets, threads);
1831}
1832
1833/// Internal helper: count suffixes 32s.
1834#[doc(hidden)]
1835pub fn count_suffixes_32s(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
1836    let n_usize = usize::try_from(n).expect("n must be non-negative");
1837    let k_usize = usize::try_from(k).expect("k must be non-negative");
1838    buckets[..k_usize].fill(0);
1839
1840    let mut i = 0usize;
1841    let mut j = n_usize.saturating_sub(7);
1842    while i < j {
1843        buckets[t[i] as usize] += 1;
1844        buckets[t[i + 1] as usize] += 1;
1845        buckets[t[i + 2] as usize] += 1;
1846        buckets[t[i + 3] as usize] += 1;
1847        buckets[t[i + 4] as usize] += 1;
1848        buckets[t[i + 5] as usize] += 1;
1849        buckets[t[i + 6] as usize] += 1;
1850        buckets[t[i + 7] as usize] += 1;
1851        i += 8;
1852    }
1853
1854    j += 7;
1855    while i < j {
1856        buckets[t[i] as usize] += 1;
1857        i += 1;
1858    }
1859}
1860
1861/// Internal helper: initialize buckets start and end 8u.
1862#[doc(hidden)]
1863pub fn initialize_buckets_start_and_end_8u(
1864    buckets: &mut [SaSint],
1865    freq: Option<&mut [SaSint]>,
1866) -> SaSint {
1867    let start_offset = 6 * ALPHABET_SIZE;
1868    let end_offset = 7 * ALPHABET_SIZE;
1869    let mut k = -1isize;
1870    let mut sum = 0;
1871
1872    match freq {
1873        Some(freq) => {
1874            for j in 0..ALPHABET_SIZE {
1875                let i = buckets_index4(j, 0);
1876                let total = buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1877                buckets[start_offset + j] = sum;
1878                sum += total;
1879                buckets[end_offset + j] = sum;
1880                if total > 0 {
1881                    k = j as isize;
1882                }
1883                freq[j] = total;
1884            }
1885        }
1886        None => {
1887            for j in 0..ALPHABET_SIZE {
1888                let i = buckets_index4(j, 0);
1889                let total = buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1890                buckets[start_offset + j] = sum;
1891                sum += total;
1892                buckets[end_offset + j] = sum;
1893                if total > 0 {
1894                    k = j as isize;
1895                }
1896            }
1897        }
1898    }
1899
1900    (k + 1) as SaSint
1901}
1902
1903/// Internal helper: initialize buckets start and end 32s 6k.
1904#[doc(hidden)]
1905pub fn initialize_buckets_start_and_end_32s_6k(k: SaSint, buckets: &mut [SaSint]) {
1906    let k_usize = usize::try_from(k).expect("k must be non-negative");
1907    let start_offset = 4 * k_usize;
1908    let end_offset = 5 * k_usize;
1909    let mut sum = 0;
1910    for j in 0..k_usize {
1911        let i = buckets_index4(j, 0);
1912        buckets[start_offset + j] = sum;
1913        sum += buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1914        buckets[end_offset + j] = sum;
1915    }
1916}
1917
1918/// Internal helper: initialize buckets start and end 32s 4k.
1919#[doc(hidden)]
1920pub fn initialize_buckets_start_and_end_32s_4k(k: SaSint, buckets: &mut [SaSint]) {
1921    let k_usize = usize::try_from(k).expect("k must be non-negative");
1922    let start_offset = 2 * k_usize;
1923    let end_offset = 3 * k_usize;
1924    let mut sum = 0;
1925    for j in 0..k_usize {
1926        let i = buckets_index2(j, 0);
1927        buckets[start_offset + j] = sum;
1928        sum += buckets[i] + buckets[i + 1];
1929        buckets[end_offset + j] = sum;
1930    }
1931}
1932
1933/// Internal helper: initialize buckets end 32s 2k.
1934#[doc(hidden)]
1935pub fn initialize_buckets_end_32s_2k(k: SaSint, buckets: &mut [SaSint]) {
1936    let k_usize = usize::try_from(k).expect("k must be non-negative");
1937    let mut sum0 = 0;
1938    for j in 0..k_usize {
1939        let i = buckets_index2(j, 0);
1940        sum0 += buckets[i] + buckets[i + 1];
1941        buckets[i] = sum0;
1942    }
1943}
1944
1945/// Internal helper: initialize buckets start and end 32s 2k.
1946#[doc(hidden)]
1947pub fn initialize_buckets_start_and_end_32s_2k(k: SaSint, buckets: &mut [SaSint]) {
1948    let k_usize = usize::try_from(k).expect("k must be non-negative");
1949    for j in 0..k_usize {
1950        let i = buckets_index2(j, 0);
1951        buckets[j] = buckets[i];
1952    }
1953    buckets[k_usize] = 0;
1954    for j in 1..k_usize {
1955        buckets[k_usize + j] = buckets[j - 1];
1956    }
1957}
1958
1959/// Internal helper: initialize buckets start 32s 1k.
1960#[doc(hidden)]
1961pub fn initialize_buckets_start_32s_1k(k: SaSint, buckets: &mut [SaSint]) {
1962    let k_usize = usize::try_from(k).expect("k must be non-negative");
1963    let mut sum = 0;
1964    for bucket in buckets.iter_mut().take(k_usize) {
1965        let tmp = *bucket;
1966        *bucket = sum;
1967        sum += tmp;
1968    }
1969}
1970
1971/// Internal helper: initialize buckets end 32s 1k.
1972#[doc(hidden)]
1973pub fn initialize_buckets_end_32s_1k(k: SaSint, buckets: &mut [SaSint]) {
1974    let k_usize = usize::try_from(k).expect("k must be non-negative");
1975    let mut sum = 0;
1976    for bucket in buckets.iter_mut().take(k_usize) {
1977        sum += *bucket;
1978        *bucket = sum;
1979    }
1980}
1981
1982/// Internal helper: initialize buckets for lms suffixes radix sort 8u.
1983#[doc(hidden)]
1984pub fn initialize_buckets_for_lms_suffixes_radix_sort_8u(
1985    t: &[u8],
1986    buckets: &mut [SaSint],
1987    mut first_lms_suffix: SaSint,
1988) -> SaSint {
1989    let mut f0 = 0usize;
1990    let mut f1: usize;
1991    let mut c0 = t[first_lms_suffix as usize] as FastSint;
1992    let mut c1: FastSint;
1993
1994    while {
1995        first_lms_suffix -= 1;
1996        first_lms_suffix >= 0
1997    } {
1998        c1 = c0;
1999        c0 = t[first_lms_suffix as usize] as FastSint;
2000        f1 = f0;
2001        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2002        let idx = 4 * c1 as usize + (f1 + f1 + f0);
2003        buckets[idx] -= 1;
2004    }
2005    buckets[4 * c0 as usize + (f0 + f0)] -= 1;
2006
2007    let temp_offset = 4 * ALPHABET_SIZE;
2008    let mut sum = 0;
2009    for j in 0..ALPHABET_SIZE {
2010        let i = 4 * j;
2011        let tj = 2 * j;
2012        buckets[temp_offset + tj + 1] = sum;
2013        sum += buckets[i + 1] + buckets[i + 3];
2014        buckets[temp_offset + tj] = sum;
2015    }
2016    sum
2017}
2018
2019/// Internal helper: initialize buckets for lms suffixes radix sort 32s 2k.
2020#[doc(hidden)]
2021pub fn initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(
2022    t: &[SaSint],
2023    k: SaSint,
2024    buckets: &mut [SaSint],
2025    first_lms_suffix: SaSint,
2026) {
2027    let _k_usize = usize::try_from(k).expect("k must be non-negative");
2028    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 0)] += 1;
2029    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 1)] -= 1;
2030
2031    let mut sum0 = 0;
2032    let mut sum1 = 0;
2033    for j in 0..usize::try_from(k).unwrap() {
2034        let i = buckets_index2(j, 0);
2035        sum0 += buckets[i] + buckets[i + 1];
2036        sum1 += buckets[i + 1];
2037        buckets[i] = sum0;
2038        buckets[i + 1] = sum1;
2039    }
2040}
2041
2042/// Internal helper: initialize buckets for lms suffixes radix sort 32s 6k.
2043#[doc(hidden)]
2044pub fn initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(
2045    t: &[SaSint],
2046    k: SaSint,
2047    buckets: &mut [SaSint],
2048    mut first_lms_suffix: SaSint,
2049) -> SaSint {
2050    let mut f0 = 0usize;
2051    let mut f1: usize;
2052    let mut c0 = t[first_lms_suffix as usize] as FastSint;
2053    let mut c1: FastSint;
2054
2055    while {
2056        first_lms_suffix -= 1;
2057        first_lms_suffix >= 0
2058    } {
2059        c1 = c0;
2060        c0 = t[first_lms_suffix as usize] as FastSint;
2061        f1 = f0;
2062        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2063        buckets[4 * c1 as usize + (f1 + f1 + f0)] -= 1;
2064    }
2065    buckets[4 * c0 as usize + (f0 + f0)] -= 1;
2066
2067    let temp_offset = 4 * usize::try_from(k).unwrap();
2068    let mut sum = 0;
2069    for j in 0..usize::try_from(k).unwrap() {
2070        let i = 4 * j;
2071        sum += buckets[i + 1] + buckets[i + 3];
2072        buckets[temp_offset + j] = sum;
2073    }
2074    sum
2075}
2076
2077/// Internal helper: initialize buckets for radix and partial sorting 32s 4k.
2078#[doc(hidden)]
2079pub fn initialize_buckets_for_radix_and_partial_sorting_32s_4k(
2080    t: &[SaSint],
2081    k: SaSint,
2082    buckets: &mut [SaSint],
2083    first_lms_suffix: SaSint,
2084) {
2085    let k_usize = usize::try_from(k).expect("k must be non-negative");
2086    let start_offset = 2 * k_usize;
2087    let end_offset = 3 * k_usize;
2088
2089    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 0)] += 1;
2090    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 1)] -= 1;
2091
2092    let mut sum0 = 0;
2093    let mut sum1 = 0;
2094    for j in 0..k_usize {
2095        let i = buckets_index2(j, 0);
2096        buckets[start_offset + j] = sum1;
2097        sum0 += buckets[i + 1];
2098        sum1 += buckets[i] + buckets[i + 1];
2099        buckets[i + 1] = sum0;
2100        buckets[end_offset + j] = sum1;
2101    }
2102}
2103
2104/// Internal helper: radix sort lms suffixes 8u.
2105#[doc(hidden)]
2106pub fn radix_sort_lms_suffixes_8u(
2107    t: &[u8],
2108    sa: &mut [SaSint],
2109    induction_bucket: &mut [SaSint],
2110    omp_block_start: FastSint,
2111    omp_block_size: FastSint,
2112) {
2113    let prefetch_distance = 64 as FastSint;
2114    let mut i = omp_block_start + omp_block_size - 1;
2115    let mut j = omp_block_start + prefetch_distance + 3;
2116
2117    while i >= j {
2118        let p0 = sa[i as usize];
2119        let idx0 = buckets_index2(t[p0 as usize] as usize, 0);
2120        induction_bucket[idx0] -= 1;
2121        sa[induction_bucket[idx0] as usize] = p0;
2122
2123        let p1 = sa[(i - 1) as usize];
2124        let idx1 = buckets_index2(t[p1 as usize] as usize, 0);
2125        induction_bucket[idx1] -= 1;
2126        sa[induction_bucket[idx1] as usize] = p1;
2127
2128        let p2 = sa[(i - 2) as usize];
2129        let idx2 = buckets_index2(t[p2 as usize] as usize, 0);
2130        induction_bucket[idx2] -= 1;
2131        sa[induction_bucket[idx2] as usize] = p2;
2132
2133        let p3 = sa[(i - 3) as usize];
2134        let idx3 = buckets_index2(t[p3 as usize] as usize, 0);
2135        induction_bucket[idx3] -= 1;
2136        sa[induction_bucket[idx3] as usize] = p3;
2137
2138        i -= 4;
2139    }
2140
2141    j -= prefetch_distance + 3;
2142    while i >= j {
2143        let p = sa[i as usize];
2144        let idx = buckets_index2(t[p as usize] as usize, 0);
2145        induction_bucket[idx] -= 1;
2146        sa[induction_bucket[idx] as usize] = p;
2147        i -= 1;
2148    }
2149}
2150
2151/// Internal helper: radix sort lms suffixes 8u (OpenMP variant).
2152#[doc(hidden)]
2153pub fn radix_sort_lms_suffixes_8u_omp(
2154    t: &[u8],
2155    sa: &mut [SaSint],
2156    n: SaSint,
2157    m: SaSint,
2158    flags: SaSint,
2159    buckets: &mut [SaSint],
2160    threads: SaSint,
2161    thread_state: &mut [ThreadState],
2162) {
2163    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
2164        buckets[4 * ALPHABET_SIZE] -= 1;
2165    }
2166
2167    let omp_num_threads = if threads > 1 && n >= 65_536 && m >= 65_536 {
2168        usize::try_from(threads)
2169            .expect("threads must be non-negative")
2170            .min(thread_state.len())
2171            .max(1)
2172    } else {
2173        1
2174    };
2175
2176    if omp_num_threads == 1 {
2177        radix_sort_lms_suffixes_8u(
2178            t,
2179            sa,
2180            &mut buckets[4 * ALPHABET_SIZE..],
2181            n as FastSint - m as FastSint + 1,
2182            m as FastSint - 1,
2183        );
2184        return;
2185    }
2186
2187    let src_bucket: &[SaSint] = &buckets[4 * ALPHABET_SIZE..];
2188
2189    // Snapshot per-thread m values before the parallel region. The C version
2190    // computes each thread's omp_block_start from every thread's m, which is
2191    // an OMP shared read — the Rust port reads them into a Vec to satisfy the
2192    // borrow checker without changing semantics.
2193    let m_values: Vec<FastSint> = thread_state[..omp_num_threads]
2194        .iter()
2195        .map(|s| s.m)
2196        .collect();
2197    let m_total = m as FastSint;
2198    let sa_ptr = SyncMutPtr::new(sa);
2199
2200    run_rayon_with_threads(omp_num_threads, || {
2201        thread_state[..omp_num_threads]
2202            .par_iter_mut()
2203            .enumerate()
2204            .for_each(|(thread_num, state)| {
2205                // Prefix-sum init: each thread mutates only its own state.buckets,
2206                // reads from the shared immutable src_bucket.
2207                for (i, j) in (0..=buckets_index2(ALPHABET_SIZE - 1, 0))
2208                    .step_by(buckets_index2(1, 0))
2209                    .zip((buckets_index4(0, 1)..).step_by(buckets_index4(1, 0)))
2210                {
2211                    state.buckets[i] = src_bucket[i] - state.buckets[j];
2212                }
2213
2214                let mut omp_block_start: FastSint = 0;
2215                for &other_m in m_values[thread_num..omp_num_threads].iter().rev() {
2216                    omp_block_start += other_m;
2217                }
2218
2219                let mut omp_block_size = m_values[thread_num];
2220                if omp_block_start == m_total && omp_block_size > 0 {
2221                    omp_block_start -= 1;
2222                    omp_block_size -= 1;
2223                }
2224
2225                // SAFETY: each thread writes only to its own disjoint slice of sa,
2226                // determined by `n - omp_block_start` and `omp_block_size`, matching
2227                // the C version's omp_block partitioning.
2228                let sa = unsafe { sa_ptr.as_slice() };
2229                radix_sort_lms_suffixes_8u(
2230                    t,
2231                    sa,
2232                    &mut state.buckets,
2233                    n as FastSint - omp_block_start,
2234                    omp_block_size,
2235                );
2236            });
2237    });
2238}
2239
2240/// Internal helper: radix sort lms suffixes 32s 6k.
2241#[doc(hidden)]
2242pub fn radix_sort_lms_suffixes_32s_6k(
2243    t: &[SaSint],
2244    sa: &mut [SaSint],
2245    induction_bucket: &mut [SaSint],
2246    omp_block_start: FastSint,
2247    omp_block_size: FastSint,
2248) {
2249    let prefetch_distance = 64 as FastSint;
2250    let mut i = omp_block_start + omp_block_size - 1;
2251    let mut j = omp_block_start + 2 * prefetch_distance + 3;
2252
2253    while i >= j {
2254        let p0 = sa[i as usize];
2255        let idx0 = t[p0 as usize] as usize;
2256        induction_bucket[idx0] -= 1;
2257        sa[induction_bucket[idx0] as usize] = p0;
2258
2259        let p1 = sa[(i - 1) as usize];
2260        let idx1 = t[p1 as usize] as usize;
2261        induction_bucket[idx1] -= 1;
2262        sa[induction_bucket[idx1] as usize] = p1;
2263
2264        let p2 = sa[(i - 2) as usize];
2265        let idx2 = t[p2 as usize] as usize;
2266        induction_bucket[idx2] -= 1;
2267        sa[induction_bucket[idx2] as usize] = p2;
2268
2269        let p3 = sa[(i - 3) as usize];
2270        let idx3 = t[p3 as usize] as usize;
2271        induction_bucket[idx3] -= 1;
2272        sa[induction_bucket[idx3] as usize] = p3;
2273
2274        i -= 4;
2275    }
2276
2277    j -= 2 * prefetch_distance + 3;
2278    while i >= j {
2279        let p = sa[i as usize];
2280        let idx = t[p as usize] as usize;
2281        induction_bucket[idx] -= 1;
2282        sa[induction_bucket[idx] as usize] = p;
2283        i -= 1;
2284    }
2285}
2286
2287/// Internal helper: radix sort lms suffixes 32s 2k.
2288#[doc(hidden)]
2289pub fn radix_sort_lms_suffixes_32s_2k(
2290    t: &[SaSint],
2291    sa: &mut [SaSint],
2292    induction_bucket: &mut [SaSint],
2293    omp_block_start: FastSint,
2294    omp_block_size: FastSint,
2295) {
2296    let prefetch_distance = 64 as FastSint;
2297    let mut i = omp_block_start + omp_block_size - 1;
2298    let mut j = omp_block_start + 2 * prefetch_distance + 3;
2299
2300    while i >= j {
2301        let p0 = sa[i as usize];
2302        let idx0 = buckets_index2(t[p0 as usize] as usize, 0);
2303        induction_bucket[idx0] -= 1;
2304        sa[induction_bucket[idx0] as usize] = p0;
2305
2306        let p1 = sa[(i - 1) as usize];
2307        let idx1 = buckets_index2(t[p1 as usize] as usize, 0);
2308        induction_bucket[idx1] -= 1;
2309        sa[induction_bucket[idx1] as usize] = p1;
2310
2311        let p2 = sa[(i - 2) as usize];
2312        let idx2 = buckets_index2(t[p2 as usize] as usize, 0);
2313        induction_bucket[idx2] -= 1;
2314        sa[induction_bucket[idx2] as usize] = p2;
2315
2316        let p3 = sa[(i - 3) as usize];
2317        let idx3 = buckets_index2(t[p3 as usize] as usize, 0);
2318        induction_bucket[idx3] -= 1;
2319        sa[induction_bucket[idx3] as usize] = p3;
2320
2321        i -= 4;
2322    }
2323
2324    j -= 2 * prefetch_distance + 3;
2325    while i >= j {
2326        let p = sa[i as usize];
2327        let idx = buckets_index2(t[p as usize] as usize, 0);
2328        induction_bucket[idx] -= 1;
2329        sa[induction_bucket[idx] as usize] = p;
2330        i -= 1;
2331    }
2332}
2333
2334/// Internal helper: radix sort lms suffixes 32s block gather.
2335#[doc(hidden)]
2336pub fn radix_sort_lms_suffixes_32s_block_gather(
2337    t: &[SaSint],
2338    sa: &[SaSint],
2339    cache: &mut [ThreadCache],
2340    omp_block_start: FastSint,
2341    omp_block_size: FastSint,
2342) {
2343    let start = usize::try_from(omp_block_start).expect("block start must be non-negative");
2344    let mut i = omp_block_start;
2345    let mut j = omp_block_start + omp_block_size - 64 - 3;
2346
2347    while i < j {
2348        for current in [i, i + 1, i + 2, i + 3] {
2349            let ci = current as usize - start;
2350            let index = sa[current as usize];
2351            cache[ci].index = index;
2352            cache[ci].symbol = t[index as usize];
2353        }
2354        i += 4;
2355    }
2356
2357    j += 64 + 3;
2358    while i < j {
2359        let ci = i as usize - start;
2360        let index = sa[i as usize];
2361        cache[ci].index = index;
2362        cache[ci].symbol = t[index as usize];
2363        i += 1;
2364    }
2365}
2366
2367/// Internal helper: radix sort lms suffixes 32s 6k block sort.
2368#[doc(hidden)]
2369pub fn radix_sort_lms_suffixes_32s_6k_block_sort(
2370    induction_bucket: &mut [SaSint],
2371    cache: &mut [ThreadCache],
2372    omp_block_start: FastSint,
2373    omp_block_size: FastSint,
2374) {
2375    let start = usize::try_from(omp_block_start).expect("block start must be non-negative");
2376    let mut i = omp_block_start + omp_block_size - 1;
2377    let mut j = omp_block_start + 64 + 3;
2378
2379    while i >= j {
2380        for current in [i, i - 1, i - 2, i - 3] {
2381            let ci = current as usize - start;
2382            let v = cache[ci].symbol as usize;
2383            induction_bucket[v] -= 1;
2384            cache[ci].symbol = induction_bucket[v];
2385        }
2386        i -= 4;
2387    }
2388
2389    j -= 64 + 3;
2390    while i >= j {
2391        let ci = i as usize - start;
2392        let v = cache[ci].symbol as usize;
2393        induction_bucket[v] -= 1;
2394        cache[ci].symbol = induction_bucket[v];
2395        i -= 1;
2396    }
2397}
2398
2399/// Internal helper: radix sort lms suffixes 32s 2k block sort.
2400#[doc(hidden)]
2401pub fn radix_sort_lms_suffixes_32s_2k_block_sort(
2402    induction_bucket: &mut [SaSint],
2403    cache: &mut [ThreadCache],
2404    omp_block_start: FastSint,
2405    omp_block_size: FastSint,
2406) {
2407    let start = usize::try_from(omp_block_start).expect("block start must be non-negative");
2408    let mut i = omp_block_start + omp_block_size - 1;
2409    let mut j = omp_block_start + 64 + 3;
2410
2411    while i >= j {
2412        for current in [i, i - 1, i - 2, i - 3] {
2413            let ci = current as usize - start;
2414            let v = buckets_index2(cache[ci].symbol as usize, 0);
2415            induction_bucket[v] -= 1;
2416            cache[ci].symbol = induction_bucket[v];
2417        }
2418        i -= 4;
2419    }
2420
2421    j -= 64 + 3;
2422    while i >= j {
2423        let ci = i as usize - start;
2424        let v = buckets_index2(cache[ci].symbol as usize, 0);
2425        induction_bucket[v] -= 1;
2426        cache[ci].symbol = induction_bucket[v];
2427        i -= 1;
2428    }
2429}
2430
2431/// Internal helper: radix sort lms suffixes 32s 6k block (OpenMP variant).
2432#[doc(hidden)]
2433pub fn radix_sort_lms_suffixes_32s_6k_block_omp(
2434    t: &[SaSint],
2435    sa: &mut [SaSint],
2436    induction_bucket: &mut [SaSint],
2437    cache: &mut [ThreadCache],
2438    block_start: FastSint,
2439    block_size: FastSint,
2440    threads: SaSint,
2441) {
2442    if threads <= 1 || block_size < 16_384 {
2443        radix_sort_lms_suffixes_32s_6k(t, sa, induction_bucket, block_start, block_size);
2444        return;
2445    }
2446
2447    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
2448    let threads_usize = usize::try_from(threads)
2449        .expect("threads must be positive")
2450        .min(block_size_usize.max(1));
2451    let omp_block_stride = (block_size_usize / threads_usize) & !15usize;
2452
2453    {
2454        let sa_ro: &[SaSint] = sa;
2455        let t_ro: &[SaSint] = t;
2456        let cache_ptr = SyncMutPtr::new(cache);
2457        run_rayon_with_threads(threads_usize, || {
2458            (0..threads_usize)
2459                .into_par_iter()
2460                .for_each(|omp_thread_num| {
2461                    let omp_block_start = omp_thread_num * omp_block_stride;
2462                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2463                        omp_block_stride
2464                    } else {
2465                        block_size_usize - omp_block_start
2466                    };
2467                    if omp_block_size > 0 {
2468                        // SAFETY: disjoint cache range per thread.
2469                        let cache = unsafe { cache_ptr.as_slice() };
2470                        radix_sort_lms_suffixes_32s_block_gather(
2471                            t_ro,
2472                            sa_ro,
2473                            &mut cache[omp_block_start..],
2474                            block_start + omp_block_start as FastSint,
2475                            omp_block_size as FastSint,
2476                        );
2477                    }
2478                });
2479        });
2480    }
2481
2482    radix_sort_lms_suffixes_32s_6k_block_sort(induction_bucket, cache, block_start, block_size);
2483
2484    {
2485        let sa_ptr = SyncMutPtr::new(sa);
2486        let cache_ro: &[ThreadCache] = cache;
2487        run_rayon_with_threads(threads_usize, || {
2488            (0..threads_usize)
2489                .into_par_iter()
2490                .for_each(|omp_thread_num| {
2491                    let omp_block_start = omp_thread_num * omp_block_stride;
2492                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2493                        omp_block_stride
2494                    } else {
2495                        block_size_usize - omp_block_start
2496                    };
2497                    if omp_block_size > 0 {
2498                        // SAFETY: per-thread sa writes go to distinct symbol-indexed positions.
2499                        let sa = unsafe { sa_ptr.as_slice() };
2500                        place_cached_suffixes(
2501                            sa,
2502                            &cache_ro[omp_block_start..],
2503                            0,
2504                            omp_block_size as FastSint,
2505                        );
2506                    }
2507                });
2508        });
2509    }
2510}
2511
2512/// Internal helper: radix sort lms suffixes 32s 2k block (OpenMP variant).
2513#[doc(hidden)]
2514pub fn radix_sort_lms_suffixes_32s_2k_block_omp(
2515    t: &[SaSint],
2516    sa: &mut [SaSint],
2517    induction_bucket: &mut [SaSint],
2518    cache: &mut [ThreadCache],
2519    block_start: FastSint,
2520    block_size: FastSint,
2521    threads: SaSint,
2522) {
2523    if threads <= 1 || block_size < 16_384 {
2524        radix_sort_lms_suffixes_32s_2k(t, sa, induction_bucket, block_start, block_size);
2525        return;
2526    }
2527
2528    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
2529    let threads_usize = usize::try_from(threads)
2530        .expect("threads must be positive")
2531        .min(block_size_usize.max(1));
2532    let omp_block_stride = (block_size_usize / threads_usize) & !15usize;
2533
2534    {
2535        let sa_ro: &[SaSint] = sa;
2536        let t_ro: &[SaSint] = t;
2537        let cache_ptr = SyncMutPtr::new(cache);
2538        run_rayon_with_threads(threads_usize, || {
2539            (0..threads_usize)
2540                .into_par_iter()
2541                .for_each(|omp_thread_num| {
2542                    let omp_block_start = omp_thread_num * omp_block_stride;
2543                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2544                        omp_block_stride
2545                    } else {
2546                        block_size_usize - omp_block_start
2547                    };
2548                    if omp_block_size > 0 {
2549                        // SAFETY: disjoint cache range per thread.
2550                        let cache = unsafe { cache_ptr.as_slice() };
2551                        radix_sort_lms_suffixes_32s_block_gather(
2552                            t_ro,
2553                            sa_ro,
2554                            &mut cache[omp_block_start..],
2555                            block_start + omp_block_start as FastSint,
2556                            omp_block_size as FastSint,
2557                        );
2558                    }
2559                });
2560        });
2561    }
2562
2563    radix_sort_lms_suffixes_32s_2k_block_sort(induction_bucket, cache, block_start, block_size);
2564
2565    {
2566        let sa_ptr = SyncMutPtr::new(sa);
2567        let cache_ro: &[ThreadCache] = cache;
2568        run_rayon_with_threads(threads_usize, || {
2569            (0..threads_usize)
2570                .into_par_iter()
2571                .for_each(|omp_thread_num| {
2572                    let omp_block_start = omp_thread_num * omp_block_stride;
2573                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2574                        omp_block_stride
2575                    } else {
2576                        block_size_usize - omp_block_start
2577                    };
2578                    if omp_block_size > 0 {
2579                        // SAFETY: per-thread sa writes go to distinct symbol-indexed positions.
2580                        let sa = unsafe { sa_ptr.as_slice() };
2581                        place_cached_suffixes(
2582                            sa,
2583                            &cache_ro[omp_block_start..],
2584                            0,
2585                            omp_block_size as FastSint,
2586                        );
2587                    }
2588                });
2589        });
2590    }
2591}
2592
2593/// Internal helper: radix sort lms suffixes 32s 6k (OpenMP variant).
2594#[doc(hidden)]
2595pub fn radix_sort_lms_suffixes_32s_6k_omp(
2596    t: &[SaSint],
2597    sa: &mut [SaSint],
2598    n: SaSint,
2599    m: SaSint,
2600    induction_bucket: &mut [SaSint],
2601    threads: SaSint,
2602    _thread_state: &mut [ThreadState],
2603) {
2604    if threads <= 1 || m < 65_536 {
2605        radix_sort_lms_suffixes_32s_6k(
2606            t,
2607            sa,
2608            induction_bucket,
2609            n as FastSint - m as FastSint + 1,
2610            m as FastSint - 1,
2611        );
2612        return;
2613    }
2614
2615    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2616    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
2617    let mut block_start = 0usize;
2618    let m_usize = usize::try_from(m).expect("m must be non-negative");
2619    let n_usize = usize::try_from(n).expect("n must be non-negative");
2620    let last = m_usize - 1;
2621
2622    while block_start < last {
2623        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(last);
2624        radix_sort_lms_suffixes_32s_6k_block_omp(
2625            t,
2626            sa,
2627            induction_bucket,
2628            &mut cache,
2629            (n_usize - block_end) as FastSint,
2630            (block_end - block_start) as FastSint,
2631            threads,
2632        );
2633        block_start = block_end;
2634    }
2635}
2636
2637/// Internal helper: radix sort lms suffixes 32s 2k (OpenMP variant).
2638#[doc(hidden)]
2639pub fn radix_sort_lms_suffixes_32s_2k_omp(
2640    t: &[SaSint],
2641    sa: &mut [SaSint],
2642    n: SaSint,
2643    m: SaSint,
2644    induction_bucket: &mut [SaSint],
2645    threads: SaSint,
2646    _thread_state: &mut [ThreadState],
2647) {
2648    if threads <= 1 || m < 65_536 {
2649        radix_sort_lms_suffixes_32s_2k(
2650            t,
2651            sa,
2652            induction_bucket,
2653            n as FastSint - m as FastSint + 1,
2654            m as FastSint - 1,
2655        );
2656        return;
2657    }
2658
2659    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2660    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
2661    let mut block_start = 0usize;
2662    let m_usize = usize::try_from(m).expect("m must be non-negative");
2663    let n_usize = usize::try_from(n).expect("n must be non-negative");
2664    let last = m_usize - 1;
2665
2666    while block_start < last {
2667        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(last);
2668        radix_sort_lms_suffixes_32s_2k_block_omp(
2669            t,
2670            sa,
2671            induction_bucket,
2672            &mut cache,
2673            (n_usize - block_end) as FastSint,
2674            (block_end - block_start) as FastSint,
2675            threads,
2676        );
2677        block_start = block_end;
2678    }
2679}
2680
2681/// Internal helper: radix sort lms suffixes 32s 1k.
2682#[doc(hidden)]
2683pub fn radix_sort_lms_suffixes_32s_1k(
2684    t: &[SaSint],
2685    sa: &mut [SaSint],
2686    n: SaSint,
2687    buckets: &mut [SaSint],
2688) -> SaSint {
2689    let n_usize = usize::try_from(n).expect("n must be non-negative");
2690    let mut i = n as FastSint - 2;
2691    let mut m = 0;
2692    let mut f0 = 1usize;
2693    let mut f1: usize;
2694    let mut c0 = t[n_usize - 1] as FastSint;
2695    let mut c1: FastSint;
2696    let mut c2 = 0 as FastSint;
2697
2698    while i >= 67 {
2699        c1 = t[i as usize] as FastSint;
2700        f1 = usize::from(c1 > (c0 - f0 as FastSint));
2701        if (f1 & !f0) != 0 {
2702            c2 = c0;
2703            buckets[c2 as usize] -= 1;
2704            sa[buckets[c2 as usize] as usize] = (i + 1) as SaSint;
2705            m += 1;
2706        }
2707
2708        c0 = t[(i - 1) as usize] as FastSint;
2709        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2710        if (f0 & !f1) != 0 {
2711            c2 = c1;
2712            buckets[c2 as usize] -= 1;
2713            sa[buckets[c2 as usize] as usize] = i as SaSint;
2714            m += 1;
2715        }
2716
2717        c1 = t[(i - 2) as usize] as FastSint;
2718        f1 = usize::from(c1 > (c0 - f0 as FastSint));
2719        if (f1 & !f0) != 0 {
2720            c2 = c0;
2721            buckets[c2 as usize] -= 1;
2722            sa[buckets[c2 as usize] as usize] = (i - 1) as SaSint;
2723            m += 1;
2724        }
2725
2726        c0 = t[(i - 3) as usize] as FastSint;
2727        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2728        if (f0 & !f1) != 0 {
2729            c2 = c1;
2730            buckets[c2 as usize] -= 1;
2731            sa[buckets[c2 as usize] as usize] = (i - 2) as SaSint;
2732            m += 1;
2733        }
2734
2735        i -= 4;
2736    }
2737
2738    while i >= 0 {
2739        c1 = c0;
2740        c0 = t[i as usize] as FastSint;
2741        f1 = f0;
2742        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2743        if (f0 & !f1) != 0 {
2744            c2 = c1;
2745            buckets[c2 as usize] -= 1;
2746            sa[buckets[c2 as usize] as usize] = (i + 1) as SaSint;
2747            m += 1;
2748        }
2749        i -= 1;
2750    }
2751
2752    if m > 1 {
2753        sa[buckets[c2 as usize] as usize] = 0;
2754    }
2755
2756    m
2757}
2758
2759/// Internal helper: radix sort set markers 32s 6k.
2760#[doc(hidden)]
2761pub fn radix_sort_set_markers_32s_6k(
2762    sa: &mut [SaSint],
2763    induction_bucket: &[SaSint],
2764    omp_block_start: FastSint,
2765    omp_block_size: FastSint,
2766) {
2767    let mut i = omp_block_start;
2768    let mut j = omp_block_start + omp_block_size - 67;
2769
2770    while i < j {
2771        sa[induction_bucket[i as usize] as usize] |= SAINT_MIN;
2772        sa[induction_bucket[(i + 1) as usize] as usize] |= SAINT_MIN;
2773        sa[induction_bucket[(i + 2) as usize] as usize] |= SAINT_MIN;
2774        sa[induction_bucket[(i + 3) as usize] as usize] |= SAINT_MIN;
2775        i += 4;
2776    }
2777
2778    j += 67;
2779    while i < j {
2780        sa[induction_bucket[i as usize] as usize] |= SAINT_MIN;
2781        i += 1;
2782    }
2783}
2784
2785/// Internal helper: radix sort set markers 32s 4k.
2786#[doc(hidden)]
2787pub fn radix_sort_set_markers_32s_4k(
2788    sa: &mut [SaSint],
2789    induction_bucket: &[SaSint],
2790    omp_block_start: FastSint,
2791    omp_block_size: FastSint,
2792) {
2793    let mut i = omp_block_start;
2794    let mut j = omp_block_start + omp_block_size - 67;
2795
2796    while i < j {
2797        sa[induction_bucket[buckets_index2(i as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2798        sa[induction_bucket[buckets_index2((i + 1) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2799        sa[induction_bucket[buckets_index2((i + 2) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2800        sa[induction_bucket[buckets_index2((i + 3) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2801        i += 4;
2802    }
2803
2804    j += 67;
2805    while i < j {
2806        sa[induction_bucket[buckets_index2(i as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2807        i += 1;
2808    }
2809}
2810
2811/// Internal helper: radix sort set markers 32s 6k (OpenMP variant).
2812#[doc(hidden)]
2813pub fn radix_sort_set_markers_32s_6k_omp(
2814    sa: &mut [SaSint],
2815    k: SaSint,
2816    induction_bucket: &[SaSint],
2817    threads: SaSint,
2818) {
2819    if k <= 1 {
2820        return;
2821    }
2822
2823    if threads <= 1 || k < 65_536 {
2824        radix_sort_set_markers_32s_6k(sa, induction_bucket, 0, k as FastSint - 1);
2825        return;
2826    }
2827
2828    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2829    let last = usize::try_from(k - 1).expect("k must be positive");
2830    let stride = (last / threads_usize) & !15usize;
2831
2832    {
2833        let sa_ptr = SyncMutPtr::new(sa);
2834        run_rayon_with_threads(threads_usize, || {
2835            (0..threads_usize).into_par_iter().for_each(|thread| {
2836                let start = thread * stride;
2837                let end = if thread + 1 == threads_usize {
2838                    last
2839                } else {
2840                    start + stride
2841                };
2842                if end > start {
2843                    // SAFETY: per-thread disjoint sa[start..end] block.
2844                    let sa = unsafe { sa_ptr.as_slice() };
2845                    radix_sort_set_markers_32s_6k(
2846                        sa,
2847                        induction_bucket,
2848                        start as FastSint,
2849                        (end - start) as FastSint,
2850                    );
2851                }
2852            });
2853        });
2854    }
2855}
2856
2857/// Internal helper: radix sort set markers 32s 4k (OpenMP variant).
2858#[doc(hidden)]
2859pub fn radix_sort_set_markers_32s_4k_omp(
2860    sa: &mut [SaSint],
2861    k: SaSint,
2862    induction_bucket: &[SaSint],
2863    threads: SaSint,
2864) {
2865    if k <= 1 {
2866        return;
2867    }
2868
2869    if threads <= 1 || k < 65_536 {
2870        radix_sort_set_markers_32s_4k(sa, induction_bucket, 0, k as FastSint - 1);
2871        return;
2872    }
2873
2874    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2875    let last = usize::try_from(k - 1).expect("k must be positive");
2876    let stride = (last / threads_usize) & !15usize;
2877
2878    {
2879        let sa_ptr = SyncMutPtr::new(sa);
2880        run_rayon_with_threads(threads_usize, || {
2881            (0..threads_usize).into_par_iter().for_each(|thread| {
2882                let start = thread * stride;
2883                let end = if thread + 1 == threads_usize {
2884                    last
2885                } else {
2886                    start + stride
2887                };
2888                if end > start {
2889                    // SAFETY: per-thread disjoint sa[start..end] block.
2890                    let sa = unsafe { sa_ptr.as_slice() };
2891                    radix_sort_set_markers_32s_4k(
2892                        sa,
2893                        induction_bucket,
2894                        start as FastSint,
2895                        (end - start) as FastSint,
2896                    );
2897                }
2898            });
2899        });
2900    }
2901}
2902
2903/// Internal helper: initialize buckets for partial sorting 8u.
2904#[doc(hidden)]
2905pub fn initialize_buckets_for_partial_sorting_8u(
2906    t: &[u8],
2907    buckets: &mut [SaSint],
2908    first_lms_suffix: SaSint,
2909    left_suffixes_count: SaSint,
2910) {
2911    let temp_offset = 4 * ALPHABET_SIZE;
2912    buckets[buckets_index4(t[first_lms_suffix as usize] as usize, 1)] += 1;
2913
2914    let mut sum0 = left_suffixes_count + 1;
2915    let mut sum1 = 0;
2916    for j in 0..ALPHABET_SIZE {
2917        let i = buckets_index4(j, 0);
2918        let tj = buckets_index2(j, 0);
2919        buckets[temp_offset + tj] = sum0;
2920        sum0 += buckets[i] + buckets[i + 2];
2921        sum1 += buckets[i + 1];
2922        buckets[tj] = sum0;
2923        buckets[tj + 1] = sum1;
2924    }
2925}
2926
2927/// Internal helper: initialize buckets for partial sorting 32s 6k.
2928#[doc(hidden)]
2929pub fn initialize_buckets_for_partial_sorting_32s_6k(
2930    t: &[SaSint],
2931    k: SaSint,
2932    buckets: &mut [SaSint],
2933    first_lms_suffix: SaSint,
2934    left_suffixes_count: SaSint,
2935) {
2936    let k_usize = usize::try_from(k).expect("k must be non-negative");
2937    let temp_offset = 4 * k_usize;
2938    let first_symbol = t[first_lms_suffix as usize] as usize;
2939    let mut sum0 = left_suffixes_count + 1;
2940    let mut sum1 = 0;
2941    let mut sum2 = 0;
2942
2943    for j in 0..first_symbol {
2944        let i = buckets_index4(j, 0);
2945        let tj = buckets_index2(j, 0);
2946        let ss = buckets[i];
2947        let ls = buckets[i + 1];
2948        let sl = buckets[i + 2];
2949        let ll = buckets[i + 3];
2950
2951        buckets[i] = sum0;
2952        buckets[i + 1] = sum2;
2953        buckets[i + 2] = 0;
2954        buckets[i + 3] = 0;
2955
2956        sum0 += ss + sl;
2957        sum1 += ls;
2958        sum2 += ls + ll;
2959
2960        buckets[temp_offset + tj] = sum0;
2961        buckets[temp_offset + tj + 1] = sum1;
2962    }
2963
2964    sum1 += 1;
2965    for j in first_symbol..k_usize {
2966        let i = buckets_index4(j, 0);
2967        let tj = buckets_index2(j, 0);
2968        let ss = buckets[i];
2969        let ls = buckets[i + 1];
2970        let sl = buckets[i + 2];
2971        let ll = buckets[i + 3];
2972
2973        buckets[i] = sum0;
2974        buckets[i + 1] = sum2;
2975        buckets[i + 2] = 0;
2976        buckets[i + 3] = 0;
2977
2978        sum0 += ss + sl;
2979        sum1 += ls;
2980        sum2 += ls + ll;
2981
2982        buckets[temp_offset + tj] = sum0;
2983        buckets[temp_offset + tj + 1] = sum1;
2984    }
2985}
2986
2987/// Internal helper: partial sorting scan left to right 8u.
2988#[doc(hidden)]
2989pub fn partial_sorting_scan_left_to_right_8u(
2990    t: &[u8],
2991    sa: &mut [SaSint],
2992    buckets: &mut [SaSint],
2993    mut d: SaSint,
2994    omp_block_start: FastSint,
2995    omp_block_size: FastSint,
2996) -> SaSint {
2997    let induction_offset = 4 * ALPHABET_SIZE;
2998    let distinct_offset = 2 * ALPHABET_SIZE;
2999    let prefetch_distance = 64 as FastSint;
3000    let mut i = omp_block_start;
3001    let mut j = if omp_block_size > prefetch_distance + 1 {
3002        omp_block_start + omp_block_size - prefetch_distance - 1
3003    } else {
3004        omp_block_start
3005    };
3006
3007    let sa_ptr = sa.as_ptr();
3008    let t_ptr = t.as_ptr();
3009    let prefetch_distance_us = prefetch_distance as usize;
3010    while i < j {
3011        let i_us = i as usize;
3012        libsais_prefetchr(sa_ptr.wrapping_add(i_us + 2 * prefetch_distance_us));
3013        let pf0 = (sa[i_us + prefetch_distance_us] & SAINT_MAX) as usize;
3014        libsais_prefetchr(t_ptr.wrapping_add(pf0).wrapping_sub(1));
3015        libsais_prefetchr(t_ptr.wrapping_add(pf0).wrapping_sub(2));
3016        let pf1 = (sa[i_us + prefetch_distance_us + 1] & SAINT_MAX) as usize;
3017        libsais_prefetchr(t_ptr.wrapping_add(pf1).wrapping_sub(1));
3018        libsais_prefetchr(t_ptr.wrapping_add(pf1).wrapping_sub(2));
3019
3020        let mut p0 = sa[i as usize];
3021        d += SaSint::from(p0 < 0);
3022        p0 &= SAINT_MAX;
3023        let v0 = buckets_index2(
3024            t[(p0 - 1) as usize] as usize,
3025            usize::from(t[(p0 - 2) as usize] >= t[(p0 - 1) as usize]),
3026        );
3027        let pos0 = buckets[induction_offset + v0] as usize;
3028        sa[pos0] = (p0 - 1) | (((buckets[distinct_offset + v0] != d) as SaSint) << (SAINT_BIT - 1));
3029        buckets[induction_offset + v0] += 1;
3030        buckets[distinct_offset + v0] = d;
3031
3032        let mut p1 = sa[(i + 1) as usize];
3033        d += SaSint::from(p1 < 0);
3034        p1 &= SAINT_MAX;
3035        let v1 = buckets_index2(
3036            t[(p1 - 1) as usize] as usize,
3037            usize::from(t[(p1 - 2) as usize] >= t[(p1 - 1) as usize]),
3038        );
3039        let pos1 = buckets[induction_offset + v1] as usize;
3040        sa[pos1] = (p1 - 1) | (((buckets[distinct_offset + v1] != d) as SaSint) << (SAINT_BIT - 1));
3041        buckets[induction_offset + v1] += 1;
3042        buckets[distinct_offset + v1] = d;
3043
3044        i += 2;
3045    }
3046
3047    j = omp_block_start + omp_block_size;
3048    while i < j {
3049        let mut p = sa[i as usize];
3050        d += SaSint::from(p < 0);
3051        p &= SAINT_MAX;
3052        let v = buckets_index2(
3053            t[(p - 1) as usize] as usize,
3054            usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
3055        );
3056        let pos = buckets[induction_offset + v] as usize;
3057        sa[pos] = (p - 1) | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
3058        buckets[induction_offset + v] += 1;
3059        buckets[distinct_offset + v] = d;
3060        i += 1;
3061    }
3062
3063    d
3064}
3065
3066/// Internal helper: partial sorting scan left to right 8u (OpenMP variant).
3067#[doc(hidden)]
3068pub fn partial_sorting_scan_left_to_right_8u_omp(
3069    t: &[u8],
3070    sa: &mut [SaSint],
3071    n: SaSint,
3072    k: SaSint,
3073    buckets: &mut [SaSint],
3074    left_suffixes_count: SaSint,
3075    mut d: SaSint,
3076    threads: SaSint,
3077    thread_state: &mut [ThreadState],
3078) -> SaSint {
3079    let v = buckets_index2(
3080        t[(n - 1) as usize] as usize,
3081        usize::from(t[(n - 2) as usize] >= t[(n - 1) as usize]),
3082    );
3083    let induction_offset = 4 * ALPHABET_SIZE;
3084    let distinct_offset = 2 * ALPHABET_SIZE;
3085    let pos = buckets[induction_offset + v] as usize;
3086    sa[pos] = (n - 1) | SAINT_MIN;
3087    buckets[induction_offset + v] += 1;
3088    d += 1;
3089    buckets[distinct_offset + v] = d;
3090
3091    if threads == 1 || left_suffixes_count < 65_536 {
3092        return partial_sorting_scan_left_to_right_8u(
3093            t,
3094            sa,
3095            buckets,
3096            d,
3097            0,
3098            left_suffixes_count as FastSint,
3099        );
3100    }
3101
3102    let mut block_start = 0usize;
3103    let left_suffixes_count =
3104        usize::try_from(left_suffixes_count).expect("left_suffixes_count must be non-negative");
3105    let threads_usize = usize::try_from(threads)
3106        .expect("threads must be non-negative")
3107        .min(thread_state.len())
3108        .max(1);
3109    while block_start < left_suffixes_count {
3110        if sa[block_start] == 0 {
3111            block_start += 1;
3112        } else {
3113            let mut block_max_end =
3114                block_start + threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
3115            if block_max_end > left_suffixes_count {
3116                block_max_end = left_suffixes_count;
3117            }
3118            let mut block_end = block_start + 1;
3119            while block_end < block_max_end && sa[block_end] != 0 {
3120                block_end += 1;
3121            }
3122            let block_size = block_end - block_start;
3123
3124            if block_size < 32 {
3125                while block_start < block_end {
3126                    let p = sa[block_start];
3127                    d += SaSint::from(p < 0);
3128                    let p = p & SAINT_MAX;
3129                    let v = buckets_index2(
3130                        t[(p - 1) as usize] as usize,
3131                        usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
3132                    );
3133                    let pos = buckets[induction_offset + v] as usize;
3134                    sa[pos] = (p - 1)
3135                        | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
3136                    buckets[induction_offset + v] += 1;
3137                    buckets[distinct_offset + v] = d;
3138                    block_start += 1;
3139                }
3140            } else {
3141                d = partial_sorting_scan_left_to_right_8u_block_omp(
3142                    t,
3143                    sa,
3144                    k,
3145                    buckets,
3146                    d,
3147                    block_start as FastSint,
3148                    block_size as FastSint,
3149                    threads,
3150                    thread_state,
3151                );
3152                block_start = block_end;
3153            }
3154        }
3155    }
3156
3157    d
3158}
3159
3160/// Internal helper: partial sorting scan left to right 32s 6k.
3161#[doc(hidden)]
3162pub fn partial_sorting_scan_left_to_right_32s_6k(
3163    t: &[SaSint],
3164    sa: &mut [SaSint],
3165    buckets: &mut [SaSint],
3166    mut d: SaSint,
3167    omp_block_start: FastSint,
3168    omp_block_size: FastSint,
3169) -> SaSint {
3170    let prefetch_distance: FastSint = 64;
3171
3172    let mut i = omp_block_start;
3173    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3174    let sa_ptr = sa.as_ptr();
3175    let t_ptr = t.as_ptr();
3176    let buckets_ptr = buckets.as_ptr();
3177    let prefetch_distance_us = prefetch_distance as usize;
3178    while i < j {
3179        let i_us = i as usize;
3180        libsais_prefetchr(sa_ptr.wrapping_add(i_us + 3 * prefetch_distance_us));
3181        let pa = (sa[i_us + 2 * prefetch_distance_us] & SAINT_MAX) as usize;
3182        libsais_prefetchr(t_ptr.wrapping_add(pa).wrapping_sub(1));
3183        libsais_prefetchr(t_ptr.wrapping_add(pa).wrapping_sub(2));
3184        let pb = (sa[i_us + 2 * prefetch_distance_us + 1] & SAINT_MAX) as usize;
3185        libsais_prefetchr(t_ptr.wrapping_add(pb).wrapping_sub(1));
3186        libsais_prefetchr(t_ptr.wrapping_add(pb).wrapping_sub(2));
3187        let pc = (sa[i_us + prefetch_distance_us] & SAINT_MAX) as usize;
3188        let vc = buckets_index4(t[pc - usize::from(pc > 0)] as usize, 0);
3189        libsais_prefetchw(buckets_ptr.wrapping_add(vc));
3190        let pd = (sa[i_us + prefetch_distance_us + 1] & SAINT_MAX) as usize;
3191        let vd = buckets_index4(t[pd - usize::from(pd > 0)] as usize, 0);
3192        libsais_prefetchw(buckets_ptr.wrapping_add(vd));
3193
3194        let mut p0 = sa[i as usize];
3195        d += SaSint::from(p0 < 0);
3196        p0 &= SAINT_MAX;
3197        let p0u = p0 as usize;
3198        let v0 = buckets_index4(t[p0u - 1] as usize, usize::from(t[p0u - 2] >= t[p0u - 1]));
3199        let pos0 = buckets[v0] as usize;
3200        sa[pos0] = (p0 - 1) | (((buckets[2 + v0] != d) as SaSint) << (SAINT_BIT - 1));
3201        buckets[v0] += 1;
3202        buckets[2 + v0] = d;
3203
3204        let mut p1 = sa[(i + 1) as usize];
3205        d += SaSint::from(p1 < 0);
3206        p1 &= SAINT_MAX;
3207        let p1u = p1 as usize;
3208        let v1 = buckets_index4(t[p1u - 1] as usize, usize::from(t[p1u - 2] >= t[p1u - 1]));
3209        let pos1 = buckets[v1] as usize;
3210        sa[pos1] = (p1 - 1) | (((buckets[2 + v1] != d) as SaSint) << (SAINT_BIT - 1));
3211        buckets[v1] += 1;
3212        buckets[2 + v1] = d;
3213
3214        i += 2;
3215    }
3216
3217    j += 2 * prefetch_distance + 1;
3218    while i < j {
3219        let mut p = sa[i as usize];
3220        d += SaSint::from(p < 0);
3221        p &= SAINT_MAX;
3222        let pu = p as usize;
3223        let v = buckets_index4(t[pu - 1] as usize, usize::from(t[pu - 2] >= t[pu - 1]));
3224        let pos = buckets[v] as usize;
3225        sa[pos] = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
3226        buckets[v] += 1;
3227        buckets[2 + v] = d;
3228        i += 1;
3229    }
3230
3231    d
3232}
3233
3234/// Internal helper: partial sorting scan left to right 32s 4k.
3235#[doc(hidden)]
3236pub fn partial_sorting_scan_left_to_right_32s_4k(
3237    t: &[SaSint],
3238    sa: &mut [SaSint],
3239    k: SaSint,
3240    buckets: &mut [SaSint],
3241    mut d: SaSint,
3242    omp_block_start: FastSint,
3243    omp_block_size: FastSint,
3244) -> SaSint {
3245    let k_usize = usize::try_from(k).expect("k must be non-negative");
3246    let prefetch_distance: FastSint = 64;
3247    let induction_offset = 2 * k_usize;
3248    let mut i = omp_block_start;
3249    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3250
3251    while i < j {
3252        let i0 = i as usize;
3253        let mut p0 = sa[i0];
3254        sa[i0] = p0 & SAINT_MAX;
3255        if p0 > 0 {
3256            sa[i0] = 0;
3257            d += p0 >> (SUFFIX_GROUP_BIT - 1);
3258            p0 &= !SUFFIX_GROUP_MARKER;
3259            let p0u = p0 as usize;
3260            let c0 = t[p0u - 1];
3261            let f0 = usize::from(t[p0u - 2] < c0);
3262            let v0 = buckets_index2(c0 as usize, f0);
3263            let c0u = c0 as usize;
3264            let pos0 = buckets[induction_offset + c0u] as usize;
3265            sa[pos0] = (p0 - 1)
3266                | ((f0 as SaSint) << (SAINT_BIT - 1))
3267                | (((buckets[v0] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3268            buckets[induction_offset + c0u] += 1;
3269            buckets[v0] = d;
3270        }
3271
3272        let i1 = (i + 1) as usize;
3273        let mut p1 = sa[i1];
3274        sa[i1] = p1 & SAINT_MAX;
3275        if p1 > 0 {
3276            sa[i1] = 0;
3277            d += p1 >> (SUFFIX_GROUP_BIT - 1);
3278            p1 &= !SUFFIX_GROUP_MARKER;
3279            let p1u = p1 as usize;
3280            let c1 = t[p1u - 1];
3281            let f1 = usize::from(t[p1u - 2] < c1);
3282            let v1 = buckets_index2(c1 as usize, f1);
3283            let c1u = c1 as usize;
3284            let pos1 = buckets[induction_offset + c1u] as usize;
3285            sa[pos1] = (p1 - 1)
3286                | ((f1 as SaSint) << (SAINT_BIT - 1))
3287                | (((buckets[v1] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3288            buckets[induction_offset + c1u] += 1;
3289            buckets[v1] = d;
3290        }
3291
3292        i += 2;
3293    }
3294
3295    j += 2 * prefetch_distance + 1;
3296    while i < j {
3297        let iu = i as usize;
3298        let mut p = sa[iu];
3299        sa[iu] = p & SAINT_MAX;
3300        if p > 0 {
3301            sa[iu] = 0;
3302            d += p >> (SUFFIX_GROUP_BIT - 1);
3303            p &= !SUFFIX_GROUP_MARKER;
3304            let pu = p as usize;
3305            let c = t[pu - 1];
3306            let f = usize::from(t[pu - 2] < c);
3307            let v = buckets_index2(c as usize, f);
3308            let cu = c as usize;
3309            let pos = buckets[induction_offset + cu] as usize;
3310            sa[pos] = (p - 1)
3311                | ((f as SaSint) << (SAINT_BIT - 1))
3312                | (((buckets[v] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3313            buckets[induction_offset + cu] += 1;
3314            buckets[v] = d;
3315        }
3316        i += 1;
3317    }
3318
3319    d
3320}
3321
3322/// Internal helper: partial sorting scan left to right 32s 1k.
3323#[doc(hidden)]
3324pub fn partial_sorting_scan_left_to_right_32s_1k(
3325    t: &[SaSint],
3326    sa: &mut [SaSint],
3327    induction_bucket: &mut [SaSint],
3328    omp_block_start: FastSint,
3329    omp_block_size: FastSint,
3330) {
3331    let prefetch_distance = 64 as FastSint;
3332    let mut i = omp_block_start;
3333    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3334
3335    while i < j {
3336        let p0 = sa[i as usize];
3337        sa[i as usize] = p0 & SAINT_MAX;
3338        if p0 > 0 {
3339            sa[i as usize] = 0;
3340            let c0 = t[(p0 - 1) as usize] as usize;
3341            let pos0 = induction_bucket[c0] as usize;
3342            induction_bucket[c0] += 1;
3343            sa[pos0] = (p0 - 1)
3344                | ((usize::from(t[(p0 - 2) as usize] < t[(p0 - 1) as usize]) as SaSint)
3345                    << (SAINT_BIT - 1));
3346        }
3347
3348        let p1 = sa[(i + 1) as usize];
3349        sa[(i + 1) as usize] = p1 & SAINT_MAX;
3350        if p1 > 0 {
3351            sa[(i + 1) as usize] = 0;
3352            let c1 = t[(p1 - 1) as usize] as usize;
3353            let pos1 = induction_bucket[c1] as usize;
3354            induction_bucket[c1] += 1;
3355            sa[pos1] = (p1 - 1)
3356                | ((usize::from(t[(p1 - 2) as usize] < t[(p1 - 1) as usize]) as SaSint)
3357                    << (SAINT_BIT - 1));
3358        }
3359
3360        i += 2;
3361    }
3362
3363    j += 2 * prefetch_distance + 1;
3364    while i < j {
3365        let p = sa[i as usize];
3366        sa[i as usize] = p & SAINT_MAX;
3367        if p > 0 {
3368            sa[i as usize] = 0;
3369            let c = t[(p - 1) as usize] as usize;
3370            let pos = induction_bucket[c] as usize;
3371            induction_bucket[c] += 1;
3372            sa[pos] = (p - 1)
3373                | ((usize::from(t[(p - 2) as usize] < t[(p - 1) as usize]) as SaSint)
3374                    << (SAINT_BIT - 1));
3375        }
3376        i += 1;
3377    }
3378}
3379
3380/// Internal helper: partial sorting scan left to right 32s 6k (OpenMP variant).
3381#[doc(hidden)]
3382pub fn partial_sorting_scan_left_to_right_32s_6k_omp(
3383    t: &[SaSint],
3384    sa: &mut [SaSint],
3385    n: SaSint,
3386    buckets: &mut [SaSint],
3387    left_suffixes_count: SaSint,
3388    mut d: SaSint,
3389    threads: SaSint,
3390    thread_state: &mut [ThreadState],
3391) -> SaSint {
3392    let v = buckets_index4(
3393        t[(n - 1) as usize] as usize,
3394        usize::from(t[(n - 2) as usize] >= t[(n - 1) as usize]),
3395    );
3396    let pos = buckets[v] as usize;
3397    sa[pos] = (n - 1) | SAINT_MIN;
3398    buckets[v] += 1;
3399    d += 1;
3400    buckets[2 + v] = d;
3401    if threads == 1 || left_suffixes_count < 65_536 {
3402        return partial_sorting_scan_left_to_right_32s_6k(
3403            t,
3404            sa,
3405            buckets,
3406            d,
3407            0,
3408            left_suffixes_count as FastSint,
3409        );
3410    }
3411    if thread_state.is_empty() {
3412        return partial_sorting_scan_left_to_right_32s_6k(
3413            t,
3414            sa,
3415            buckets,
3416            d,
3417            0,
3418            left_suffixes_count as FastSint,
3419        );
3420    }
3421
3422    let left_suffixes_count =
3423        usize::try_from(left_suffixes_count).expect("left_suffixes_count must be non-negative");
3424    let threads_usize = usize::try_from(threads)
3425        .expect("threads must be non-negative")
3426        .max(1);
3427    let mut block_start = 0usize;
3428    let block_span = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3429    let mut cache = vec![ThreadCache::default(); block_span];
3430    while block_start < left_suffixes_count {
3431        let mut block_end = block_start + block_span;
3432        if block_end > left_suffixes_count {
3433            block_end = left_suffixes_count;
3434        }
3435
3436        d = partial_sorting_scan_left_to_right_32s_6k_block_omp(
3437            t,
3438            sa,
3439            buckets,
3440            d,
3441            &mut cache,
3442            block_start as FastSint,
3443            (block_end - block_start) as FastSint,
3444            threads,
3445        );
3446
3447        block_start = block_end;
3448    }
3449
3450    d
3451}
3452
3453/// Internal helper: partial sorting scan left to right 32s 4k (OpenMP variant).
3454#[doc(hidden)]
3455pub fn partial_sorting_scan_left_to_right_32s_4k_omp(
3456    t: &[SaSint],
3457    sa: &mut [SaSint],
3458    n: SaSint,
3459    k: SaSint,
3460    buckets: &mut [SaSint],
3461    mut d: SaSint,
3462    threads: SaSint,
3463    thread_state: &mut [ThreadState],
3464) -> SaSint {
3465    let k_usize = usize::try_from(k).expect("k must be non-negative");
3466    let induction_offset = 2 * k_usize;
3467    let distinct_offset = 0usize;
3468    let symbol = t[(n - 1) as usize] as usize;
3469    let is_s = usize::from(t[(n - 2) as usize] < t[(n - 1) as usize]);
3470    let pos = buckets[induction_offset + symbol] as usize;
3471    sa[pos] = (n - 1) | ((is_s as SaSint) << (SAINT_BIT - 1)) | SUFFIX_GROUP_MARKER;
3472    buckets[induction_offset + symbol] += 1;
3473    d += 1;
3474    buckets[distinct_offset + buckets_index2(symbol, is_s)] = d;
3475
3476    if threads == 1 || n < 65_536 {
3477        d = partial_sorting_scan_left_to_right_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
3478    } else {
3479        if thread_state.is_empty() {
3480            return partial_sorting_scan_left_to_right_32s_4k(
3481                t,
3482                sa,
3483                k,
3484                buckets,
3485                d,
3486                0,
3487                n as FastSint,
3488            );
3489        }
3490        let mut block_start = 0usize;
3491        let n_usize = usize::try_from(n).expect("n must be non-negative");
3492        let threads_usize = usize::try_from(threads)
3493            .expect("threads must be non-negative")
3494            .max(1);
3495        let chunk_capacity = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3496        let mut cache = vec![ThreadCache::default(); chunk_capacity];
3497
3498        while block_start < n_usize {
3499            let mut block_end = block_start + chunk_capacity;
3500            if block_end > n_usize {
3501                block_end = n_usize;
3502            }
3503
3504            d = partial_sorting_scan_left_to_right_32s_4k_block_omp(
3505                t,
3506                sa,
3507                k,
3508                buckets,
3509                d,
3510                &mut cache,
3511                block_start as FastSint,
3512                (block_end - block_start) as FastSint,
3513                threads,
3514            );
3515
3516            block_start = block_end;
3517        }
3518    }
3519
3520    d
3521}
3522
3523/// Internal helper: partial sorting scan left to right 32s 1k (OpenMP variant).
3524#[doc(hidden)]
3525pub fn partial_sorting_scan_left_to_right_32s_1k_omp(
3526    t: &[SaSint],
3527    sa: &mut [SaSint],
3528    n: SaSint,
3529    buckets: &mut [SaSint],
3530    threads: SaSint,
3531    thread_state: &mut [ThreadState],
3532) {
3533    let symbol = t[(n - 1) as usize] as usize;
3534    let pos = buckets[symbol] as usize;
3535    sa[pos] = (n - 1)
3536        | ((usize::from(t[(n - 2) as usize] < t[(n - 1) as usize]) as SaSint) << (SAINT_BIT - 1));
3537    buckets[symbol] += 1;
3538    if threads == 1 || n < 65_536 {
3539        partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, 0, n as FastSint);
3540    } else {
3541        if thread_state.is_empty() {
3542            partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, 0, n as FastSint);
3543            return;
3544        }
3545        let n_usize = usize::try_from(n).expect("n must be non-negative");
3546        let threads_usize = usize::try_from(threads)
3547            .expect("threads must be non-negative")
3548            .max(1);
3549        let mut block_start = 0usize;
3550        let block_span = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3551        let mut cache = vec![ThreadCache::default(); block_span];
3552
3553        while block_start < n_usize {
3554            let mut block_end = block_start + block_span;
3555            if block_end > n_usize {
3556                block_end = n_usize;
3557            }
3558
3559            partial_sorting_scan_left_to_right_32s_1k_block_omp(
3560                t,
3561                sa,
3562                buckets,
3563                &mut cache,
3564                block_start as FastSint,
3565                (block_end - block_start) as FastSint,
3566                threads,
3567            );
3568
3569            block_start = block_end;
3570        }
3571    }
3572}
3573
3574/// Internal helper: partial sorting scan left to right 8u block prepare.
3575#[doc(hidden)]
3576pub fn partial_sorting_scan_left_to_right_8u_block_prepare(
3577    t: &[u8],
3578    sa: &[SaSint],
3579    k: SaSint,
3580    buckets: &mut [SaSint],
3581    cache: &mut [ThreadCache],
3582    omp_block_start: FastSint,
3583    omp_block_size: FastSint,
3584) -> (FastSint, FastSint) {
3585    let k_usize = usize::try_from(k).expect("k must be non-negative");
3586    buckets[..2 * k_usize].fill(0);
3587    buckets[2 * k_usize..4 * k_usize].fill(0);
3588
3589    let mut i = omp_block_start;
3590    let mut j = omp_block_start + omp_block_size - 65;
3591    let mut count = 0usize;
3592    let mut d: SaSint = 1;
3593
3594    while i < j {
3595        let mut p0 = sa[i as usize];
3596        cache[count].index = p0;
3597        d += SaSint::from(p0 < 0);
3598        p0 &= SAINT_MAX;
3599        let v0 = buckets_index2(
3600            t[(p0 - 1) as usize] as usize,
3601            usize::from(t[(p0 - 2) as usize] >= t[(p0 - 1) as usize]),
3602        );
3603        cache[count].symbol = v0 as SaSint;
3604        count += 1;
3605        buckets[v0] += 1;
3606        buckets[2 * k_usize + v0] = d;
3607
3608        let mut p1 = sa[(i + 1) as usize];
3609        cache[count].index = p1;
3610        d += SaSint::from(p1 < 0);
3611        p1 &= SAINT_MAX;
3612        let v1 = buckets_index2(
3613            t[(p1 - 1) as usize] as usize,
3614            usize::from(t[(p1 - 2) as usize] >= t[(p1 - 1) as usize]),
3615        );
3616        cache[count].symbol = v1 as SaSint;
3617        count += 1;
3618        buckets[v1] += 1;
3619        buckets[2 * k_usize + v1] = d;
3620
3621        i += 2;
3622    }
3623
3624    j += 65;
3625    while i < j {
3626        let mut p = sa[i as usize];
3627        cache[count].index = p;
3628        d += SaSint::from(p < 0);
3629        p &= SAINT_MAX;
3630        let v = buckets_index2(
3631            t[(p - 1) as usize] as usize,
3632            usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
3633        );
3634        cache[count].symbol = v as SaSint;
3635        count += 1;
3636        buckets[v] += 1;
3637        buckets[2 * k_usize + v] = d;
3638        i += 1;
3639    }
3640
3641    (d as FastSint - 1, count as FastSint)
3642}
3643
3644/// Internal helper: partial sorting scan left to right 8u block place.
3645#[doc(hidden)]
3646pub fn partial_sorting_scan_left_to_right_8u_block_place(
3647    sa: &mut [SaSint],
3648    buckets: &mut [SaSint],
3649    k: SaSint,
3650    cache: &[ThreadCache],
3651    count: FastSint,
3652    mut d: SaSint,
3653) {
3654    let split = 2 * usize::try_from(k).expect("k must be non-negative");
3655    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
3656
3657    let mut i = 0usize;
3658    let mut j = usize::try_from(count)
3659        .expect("count must be non-negative")
3660        .saturating_sub(1);
3661    while i < j {
3662        let p0 = cache[i].index;
3663        d += SaSint::from(p0 < 0);
3664        let v0 = cache[i].symbol as usize;
3665        let pos0 = induction_bucket[v0] as usize;
3666        sa[pos0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
3667        induction_bucket[v0] += 1;
3668        distinct_names[v0] = d;
3669
3670        let p1 = cache[i + 1].index;
3671        d += SaSint::from(p1 < 0);
3672        let v1 = cache[i + 1].symbol as usize;
3673        let pos1 = induction_bucket[v1] as usize;
3674        sa[pos1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
3675        induction_bucket[v1] += 1;
3676        distinct_names[v1] = d;
3677
3678        i += 2;
3679    }
3680
3681    j += 1;
3682    while i < j {
3683        let p = cache[i].index;
3684        d += SaSint::from(p < 0);
3685        let v = cache[i].symbol as usize;
3686        let pos = induction_bucket[v] as usize;
3687        sa[pos] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
3688        induction_bucket[v] += 1;
3689        distinct_names[v] = d;
3690        i += 1;
3691    }
3692}
3693
3694/// Internal helper: partial sorting scan left to right 8u block (OpenMP variant).
3695#[doc(hidden)]
3696pub fn partial_sorting_scan_left_to_right_8u_block_omp(
3697    t: &[u8],
3698    sa: &mut [SaSint],
3699    k: SaSint,
3700    buckets: &mut [SaSint],
3701    d: SaSint,
3702    block_start: FastSint,
3703    block_size: FastSint,
3704    threads: SaSint,
3705    thread_state: &mut [ThreadState],
3706) -> SaSint {
3707    let mut d = d;
3708    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
3709    let k_usize = usize::try_from(k).expect("k must be non-negative");
3710    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
3711        usize::try_from(threads)
3712            .expect("threads must be non-negative")
3713            .min(thread_state.len())
3714            .max(1)
3715    } else {
3716        1
3717    };
3718    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
3719
3720    if omp_num_threads == 1 {
3721        return partial_sorting_scan_left_to_right_8u(t, sa, buckets, d, block_start, block_size);
3722    }
3723
3724    {
3725        let sa_ro: &[SaSint] = sa;
3726        run_rayon_with_threads(omp_num_threads, || {
3727            thread_state[..omp_num_threads]
3728                .par_iter_mut()
3729                .enumerate()
3730                .for_each(|(omp_thread_num, state)| {
3731                    let mut omp_block_start = omp_thread_num * omp_block_stride;
3732                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
3733                        omp_block_stride
3734                    } else {
3735                        block_size_usize - omp_block_start
3736                    };
3737                    omp_block_start +=
3738                        usize::try_from(block_start).expect("block_start must be non-negative");
3739
3740                    let (position, count) = partial_sorting_scan_left_to_right_8u_block_prepare(
3741                        t,
3742                        sa_ro,
3743                        k,
3744                        &mut state.buckets,
3745                        &mut state.cache,
3746                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
3747                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
3748                    );
3749                    state.position = position;
3750                    state.count = count;
3751                });
3752        });
3753    }
3754
3755    let induction_offset = 4 * ALPHABET_SIZE;
3756    let distinct_offset = 2 * ALPHABET_SIZE;
3757    let (prefix, induction_tail) = buckets.split_at_mut(induction_offset);
3758    let induction_bucket = &mut induction_tail[..2 * k_usize];
3759    let distinct_names = &mut prefix[distinct_offset..distinct_offset + 2 * k_usize];
3760
3761    for tnum in 0..omp_num_threads {
3762        let state = &mut thread_state[tnum];
3763        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
3764        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
3765
3766        for c in 0..2 * k_usize {
3767            let a = induction_bucket[c];
3768            let b = temp_induction_bucket[c];
3769            induction_bucket[c] = a + b;
3770            temp_induction_bucket[c] = a;
3771        }
3772
3773        d -= 1;
3774        for c in 0..2 * k_usize {
3775            let a = distinct_names[c];
3776            let b = temp_distinct_names[c];
3777            let next_d = b + d;
3778            distinct_names[c] = if b > 0 { next_d } else { a };
3779            temp_distinct_names[c] = a;
3780        }
3781        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
3782        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
3783    }
3784
3785    {
3786        let sa_ptr = SyncMutPtr::new(sa);
3787        run_rayon_with_threads(omp_num_threads, || {
3788            thread_state[..omp_num_threads]
3789                .par_iter_mut()
3790                .for_each(|state| {
3791                    // SAFETY: each thread writes only to sa positions selected by its own
3792                    // state.buckets induction values, which were partitioned in the serial
3793                    // merge step above so distinct threads produce distinct positions.
3794                    let sa = unsafe { sa_ptr.as_slice() };
3795                    partial_sorting_scan_left_to_right_8u_block_place(
3796                        sa,
3797                        &mut state.buckets,
3798                        k,
3799                        &state.cache,
3800                        state.count,
3801                        state.position as SaSint,
3802                    );
3803                });
3804        });
3805    }
3806
3807    d
3808}
3809
3810/// Internal helper: partial sorting shift markers 8u (OpenMP variant).
3811#[doc(hidden)]
3812pub fn partial_sorting_shift_markers_8u_omp(
3813    sa: &mut [SaSint],
3814    n: SaSint,
3815    buckets: &[SaSint],
3816    threads: SaSint,
3817) {
3818    let temp_bucket = &buckets[4 * ALPHABET_SIZE..];
3819    let thread_count = if threads > 1 && n >= 65536 {
3820        usize::try_from(threads).expect("threads must be positive")
3821    } else {
3822        1
3823    };
3824    let c_step = buckets_index2(1, 0) as isize;
3825    let c_min = buckets_index2(1, 0) as isize;
3826    let c_max = buckets_index2(ALPHABET_SIZE - 1, 0) as isize;
3827    {
3828        let sa_ptr = SyncMutPtr::new(sa);
3829        let buckets_ref: &[SaSint] = buckets;
3830        let temp_bucket_ref: &[SaSint] = temp_bucket;
3831        run_rayon_with_threads(thread_count, || {
3832            (0..thread_count).into_par_iter().for_each(|t| {
3833                let mut c = c_max - (t as isize * c_step);
3834                // SAFETY: different `c` values point to different bucket ranges; each
3835                // thread iterates over c values stride `c_step*thread_count` apart, so
3836                // sa writes are disjoint across threads.
3837                let sa = unsafe { sa_ptr.as_slice() };
3838                while c >= c_min {
3839                    let c_usize = c as usize;
3840                    let mut i = temp_bucket_ref[c_usize] as isize - 1;
3841                    let mut j = buckets_ref[c_usize - buckets_index2(1, 0)] as isize + 3;
3842                    let mut s = SAINT_MIN;
3843
3844                    while i >= j {
3845                        let p0 = sa[i as usize];
3846                        let q0 = (p0 & SAINT_MIN) ^ s;
3847                        s ^= q0;
3848                        sa[i as usize] = p0 ^ q0;
3849
3850                        let p1 = sa[(i - 1) as usize];
3851                        let q1 = (p1 & SAINT_MIN) ^ s;
3852                        s ^= q1;
3853                        sa[(i - 1) as usize] = p1 ^ q1;
3854
3855                        let p2 = sa[(i - 2) as usize];
3856                        let q2 = (p2 & SAINT_MIN) ^ s;
3857                        s ^= q2;
3858                        sa[(i - 2) as usize] = p2 ^ q2;
3859
3860                        let p3 = sa[(i - 3) as usize];
3861                        let q3 = (p3 & SAINT_MIN) ^ s;
3862                        s ^= q3;
3863                        sa[(i - 3) as usize] = p3 ^ q3;
3864
3865                        i -= 4;
3866                    }
3867
3868                    j -= 3;
3869                    while i >= j {
3870                        let p = sa[i as usize];
3871                        let q = (p & SAINT_MIN) ^ s;
3872                        s ^= q;
3873                        sa[i as usize] = p ^ q;
3874                        i -= 1;
3875                    }
3876
3877                    c -= c_step * thread_count as isize;
3878                }
3879            });
3880        });
3881    }
3882}
3883
3884/// Internal helper: partial sorting shift markers 32s 6k (OpenMP variant).
3885#[doc(hidden)]
3886pub fn partial_sorting_shift_markers_32s_6k_omp(
3887    sa: &mut [SaSint],
3888    k: SaSint,
3889    buckets: &[SaSint],
3890    threads: SaSint,
3891) {
3892    let k_usize = usize::try_from(k).expect("k must be non-negative");
3893    let temp_bucket = &buckets[4 * k_usize..];
3894    let thread_count = if threads > 1 && k >= 65536 {
3895        usize::try_from(threads).expect("threads must be positive")
3896    } else {
3897        1
3898    };
3899    {
3900        let sa_ptr = SyncMutPtr::new(sa);
3901        let buckets_ref: &[SaSint] = buckets;
3902        let temp_bucket_ref: &[SaSint] = temp_bucket;
3903        run_rayon_with_threads(thread_count, || {
3904            (0..thread_count).into_par_iter().for_each(|t| {
3905                let mut c = k_usize as isize - 1 - t as isize;
3906                // SAFETY: different `c` values map to different bucket-defined sa ranges.
3907                let sa = unsafe { sa_ptr.as_slice() };
3908                while c >= 1 {
3909                    let c_usize = c as usize;
3910                    let mut i = buckets_ref[buckets_index4(c_usize, 0)] as isize - 1;
3911                    let mut j = temp_bucket_ref[buckets_index2(c_usize - 1, 0)] as isize + 3;
3912                    let mut s = SAINT_MIN;
3913
3914                    while i >= j {
3915                        let p0 = sa[i as usize];
3916                        let q0 = (p0 & SAINT_MIN) ^ s;
3917                        s ^= q0;
3918                        sa[i as usize] = p0 ^ q0;
3919
3920                        let p1 = sa[(i - 1) as usize];
3921                        let q1 = (p1 & SAINT_MIN) ^ s;
3922                        s ^= q1;
3923                        sa[(i - 1) as usize] = p1 ^ q1;
3924
3925                        let p2 = sa[(i - 2) as usize];
3926                        let q2 = (p2 & SAINT_MIN) ^ s;
3927                        s ^= q2;
3928                        sa[(i - 2) as usize] = p2 ^ q2;
3929
3930                        let p3 = sa[(i - 3) as usize];
3931                        let q3 = (p3 & SAINT_MIN) ^ s;
3932                        s ^= q3;
3933                        sa[(i - 3) as usize] = p3 ^ q3;
3934
3935                        i -= 4;
3936                    }
3937
3938                    j -= 3;
3939                    while i >= j {
3940                        let p = sa[i as usize];
3941                        let q = (p & SAINT_MIN) ^ s;
3942                        s ^= q;
3943                        sa[i as usize] = p ^ q;
3944                        i -= 1;
3945                    }
3946
3947                    c -= thread_count as isize;
3948                }
3949            });
3950        });
3951    }
3952}
3953
3954/// Internal helper: partial sorting shift markers 32s 4k.
3955#[doc(hidden)]
3956pub fn partial_sorting_shift_markers_32s_4k(sa: &mut [SaSint], n: SaSint) {
3957    let mut i = n as isize - 1;
3958    let mut s = SUFFIX_GROUP_MARKER;
3959    while i >= 3 {
3960        let p0 = sa[i as usize];
3961        let q0 =
3962            ((p0 & SUFFIX_GROUP_MARKER) ^ s) & (((p0 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3963        s ^= q0;
3964        sa[i as usize] = p0 ^ q0;
3965
3966        let p1 = sa[(i - 1) as usize];
3967        let q1 =
3968            ((p1 & SUFFIX_GROUP_MARKER) ^ s) & (((p1 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3969        s ^= q1;
3970        sa[(i - 1) as usize] = p1 ^ q1;
3971
3972        let p2 = sa[(i - 2) as usize];
3973        let q2 =
3974            ((p2 & SUFFIX_GROUP_MARKER) ^ s) & (((p2 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3975        s ^= q2;
3976        sa[(i - 2) as usize] = p2 ^ q2;
3977
3978        let p3 = sa[(i - 3) as usize];
3979        let q3 =
3980            ((p3 & SUFFIX_GROUP_MARKER) ^ s) & (((p3 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3981        s ^= q3;
3982        sa[(i - 3) as usize] = p3 ^ q3;
3983
3984        i -= 4;
3985    }
3986
3987    while i >= 0 {
3988        let p = sa[i as usize];
3989        let q = ((p & SUFFIX_GROUP_MARKER) ^ s) & (((p > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3990        s ^= q;
3991        sa[i as usize] = p ^ q;
3992        i -= 1;
3993    }
3994}
3995
3996/// Internal helper: partial sorting shift buckets 32s 6k.
3997#[doc(hidden)]
3998pub fn partial_sorting_shift_buckets_32s_6k(k: SaSint, buckets: &mut [SaSint]) {
3999    let k_usize = usize::try_from(k).expect("k must be non-negative");
4000    let temp_offset = 4 * k_usize;
4001    for i in 0..k_usize {
4002        let src = buckets_index2(i, 0);
4003        let dst = 2 * src;
4004        buckets[dst] = buckets[temp_offset + src];
4005        buckets[dst + 1] = buckets[temp_offset + src + 1];
4006    }
4007}
4008
4009/// Internal helper: partial sorting scan right to left 8u.
4010#[doc(hidden)]
4011pub fn partial_sorting_scan_right_to_left_8u(
4012    t: &[u8],
4013    sa: &mut [SaSint],
4014    buckets: &mut [SaSint],
4015    mut d: SaSint,
4016    omp_block_start: FastSint,
4017    omp_block_size: FastSint,
4018) -> SaSint {
4019    if omp_block_size <= 0 {
4020        return d;
4021    }
4022
4023    let prefetch_distance = 64usize;
4024    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * ALPHABET_SIZE);
4025    let distinct_names = &mut distinct_names_all[..2 * ALPHABET_SIZE];
4026
4027    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4028    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4029    let mut i = start + size - 1;
4030    let mut j = start + prefetch_distance + 1;
4031
4032    let sa_ptr = sa.as_ptr();
4033    let t_ptr = t.as_ptr();
4034    while i >= j {
4035        libsais_prefetchr(sa_ptr.wrapping_add(i.wrapping_sub(2 * prefetch_distance)));
4036        let pf0 = (sa[i - prefetch_distance] & SAINT_MAX) as usize;
4037        libsais_prefetchr(t_ptr.wrapping_add(pf0).wrapping_sub(1));
4038        libsais_prefetchr(t_ptr.wrapping_add(pf0).wrapping_sub(2));
4039        let pf1 = (sa[i - prefetch_distance - 1] & SAINT_MAX) as usize;
4040        libsais_prefetchr(t_ptr.wrapping_add(pf1).wrapping_sub(1));
4041        libsais_prefetchr(t_ptr.wrapping_add(pf1).wrapping_sub(2));
4042
4043        let mut p0 = sa[i];
4044        d += SaSint::from(p0 < 0);
4045        p0 &= SAINT_MAX;
4046
4047        let p0_usize = p0 as usize;
4048        let v0 = buckets_index2(
4049            t[p0_usize - 1] as usize,
4050            usize::from(t[p0_usize - 2] > t[p0_usize - 1]),
4051        );
4052
4053        induction_bucket[v0] -= 1;
4054        let slot0 = induction_bucket[v0] as usize;
4055        sa[slot0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
4056        distinct_names[v0] = d;
4057
4058        let mut p1 = sa[i - 1];
4059        d += SaSint::from(p1 < 0);
4060        p1 &= SAINT_MAX;
4061
4062        let p1_usize = p1 as usize;
4063        let v1 = buckets_index2(
4064            t[p1_usize - 1] as usize,
4065            usize::from(t[p1_usize - 2] > t[p1_usize - 1]),
4066        );
4067
4068        induction_bucket[v1] -= 1;
4069        let slot1 = induction_bucket[v1] as usize;
4070        sa[slot1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
4071        distinct_names[v1] = d;
4072
4073        i -= 2;
4074    }
4075
4076    j = if start + prefetch_distance < start + size {
4077        start
4078    } else {
4079        start
4080    };
4081    while i >= j {
4082        let mut p = sa[i];
4083        d += SaSint::from(p < 0);
4084        p &= SAINT_MAX;
4085
4086        let p_usize = p as usize;
4087        let v = buckets_index2(
4088            t[p_usize - 1] as usize,
4089            usize::from(t[p_usize - 2] > t[p_usize - 1]),
4090        );
4091
4092        induction_bucket[v] -= 1;
4093        let slot = induction_bucket[v] as usize;
4094        sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4095        distinct_names[v] = d;
4096
4097        if i == 0 {
4098            break;
4099        }
4100        i -= 1;
4101    }
4102
4103    d
4104}
4105
4106/// Internal helper: partial gsa scan right to left 8u.
4107#[doc(hidden)]
4108pub fn partial_gsa_scan_right_to_left_8u(
4109    t: &[u8],
4110    sa: &mut [SaSint],
4111    buckets: &mut [SaSint],
4112    mut d: SaSint,
4113    omp_block_start: FastSint,
4114    omp_block_size: FastSint,
4115) -> SaSint {
4116    if omp_block_size <= 0 {
4117        return d;
4118    }
4119
4120    let prefetch_distance = 64usize;
4121    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * ALPHABET_SIZE);
4122    let distinct_names = &mut distinct_names_all[..2 * ALPHABET_SIZE];
4123
4124    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4125    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4126    let mut i = start + size - 1;
4127    let mut j = start + prefetch_distance + 1;
4128
4129    while i >= j {
4130        let mut p0 = sa[i];
4131        d += SaSint::from(p0 < 0);
4132        p0 &= SAINT_MAX;
4133
4134        let p0_usize = p0 as usize;
4135        let v0 = buckets_index2(
4136            t[p0_usize - 1] as usize,
4137            usize::from(t[p0_usize - 2] > t[p0_usize - 1]),
4138        );
4139
4140        if v0 != 1 {
4141            induction_bucket[v0] -= 1;
4142            let slot0 = induction_bucket[v0] as usize;
4143            sa[slot0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
4144            distinct_names[v0] = d;
4145        }
4146
4147        let mut p1 = sa[i - 1];
4148        d += SaSint::from(p1 < 0);
4149        p1 &= SAINT_MAX;
4150
4151        let p1_usize = p1 as usize;
4152        let v1 = buckets_index2(
4153            t[p1_usize - 1] as usize,
4154            usize::from(t[p1_usize - 2] > t[p1_usize - 1]),
4155        );
4156
4157        if v1 != 1 {
4158            induction_bucket[v1] -= 1;
4159            let slot1 = induction_bucket[v1] as usize;
4160            sa[slot1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
4161            distinct_names[v1] = d;
4162        }
4163
4164        i -= 2;
4165    }
4166
4167    j = start;
4168    while i >= j {
4169        let mut p = sa[i];
4170        d += SaSint::from(p < 0);
4171        p &= SAINT_MAX;
4172
4173        let p_usize = p as usize;
4174        let v = buckets_index2(
4175            t[p_usize - 1] as usize,
4176            usize::from(t[p_usize - 2] > t[p_usize - 1]),
4177        );
4178
4179        if v != 1 {
4180            induction_bucket[v] -= 1;
4181            let slot = induction_bucket[v] as usize;
4182            sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4183            distinct_names[v] = d;
4184        }
4185
4186        if i == 0 {
4187            break;
4188        }
4189        i -= 1;
4190    }
4191
4192    d
4193}
4194
4195/// Internal helper: partial sorting scan right to left 8u block prepare.
4196#[doc(hidden)]
4197pub fn partial_sorting_scan_right_to_left_8u_block_prepare(
4198    t: &[u8],
4199    sa: &[SaSint],
4200    k: SaSint,
4201    buckets: &mut [SaSint],
4202    cache: &mut [ThreadCache],
4203    omp_block_start: FastSint,
4204    omp_block_size: FastSint,
4205) -> (FastSint, FastSint) {
4206    let k_usize = usize::try_from(k).expect("k must be non-negative");
4207    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * k_usize);
4208    let distinct_names = &mut distinct_names_all[..2 * k_usize];
4209    induction_bucket.fill(0);
4210    distinct_names.fill(0);
4211
4212    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4213    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4214    let mut count = 0usize;
4215    let mut d = 1;
4216
4217    let mut i = start + size;
4218    while i > start {
4219        i -= 1;
4220
4221        let mut p = sa[i];
4222        cache[count].index = p;
4223        d += SaSint::from(p < 0);
4224        p &= SAINT_MAX;
4225
4226        let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4227        let v = buckets_index2(
4228            t[p_usize - 1] as usize,
4229            usize::from(t[p_usize - 2] > t[p_usize - 1]),
4230        );
4231
4232        cache[count].symbol = v as SaSint;
4233        induction_bucket[v] += 1;
4234        distinct_names[v] = d;
4235        count += 1;
4236    }
4237
4238    ((d - 1) as FastSint, count as FastSint)
4239}
4240
4241/// Internal helper: partial sorting scan right to left 8u block place.
4242#[doc(hidden)]
4243pub fn partial_sorting_scan_right_to_left_8u_block_place(
4244    sa: &mut [SaSint],
4245    buckets: &mut [SaSint],
4246    k: SaSint,
4247    cache: &[ThreadCache],
4248    count: FastSint,
4249    mut d: SaSint,
4250) {
4251    let split = 2 * usize::try_from(k).expect("k must be non-negative");
4252    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
4253
4254    let count = usize::try_from(count).expect("count must be non-negative");
4255    for entry in &cache[..count] {
4256        let p = entry.index;
4257        d += SaSint::from(p < 0);
4258        let v = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
4259        induction_bucket[v] -= 1;
4260        let slot = usize::try_from(induction_bucket[v]).expect("bucket slot must be non-negative");
4261        sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4262        distinct_names[v] = d;
4263    }
4264}
4265
4266/// Internal helper: partial gsa scan right to left 8u block place.
4267#[doc(hidden)]
4268pub fn partial_gsa_scan_right_to_left_8u_block_place(
4269    sa: &mut [SaSint],
4270    buckets: &mut [SaSint],
4271    k: SaSint,
4272    cache: &[ThreadCache],
4273    count: FastSint,
4274    mut d: SaSint,
4275) {
4276    let split = 2 * usize::try_from(k).expect("k must be non-negative");
4277    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
4278
4279    let count = usize::try_from(count).expect("count must be non-negative");
4280    for entry in &cache[..count] {
4281        let p = entry.index;
4282        d += SaSint::from(p < 0);
4283        let v = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
4284        if v != 1 {
4285            induction_bucket[v] -= 1;
4286            let slot =
4287                usize::try_from(induction_bucket[v]).expect("bucket slot must be non-negative");
4288            sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4289            distinct_names[v] = d;
4290        }
4291    }
4292}
4293
4294/// Internal helper: partial sorting scan right to left 8u block (OpenMP variant).
4295#[doc(hidden)]
4296pub fn partial_sorting_scan_right_to_left_8u_block_omp(
4297    t: &[u8],
4298    sa: &mut [SaSint],
4299    k: SaSint,
4300    buckets: &mut [SaSint],
4301    d: SaSint,
4302    block_start: FastSint,
4303    block_size: FastSint,
4304    threads: SaSint,
4305    thread_state: &mut [ThreadState],
4306) -> SaSint {
4307    let mut d = d;
4308    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
4309    let k_usize = usize::try_from(k).expect("k must be non-negative");
4310    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
4311        usize::try_from(threads)
4312            .expect("threads must be non-negative")
4313            .min(thread_state.len())
4314            .max(1)
4315    } else {
4316        1
4317    };
4318    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
4319
4320    if omp_num_threads == 1 {
4321        return partial_sorting_scan_right_to_left_8u(t, sa, buckets, d, block_start, block_size);
4322    }
4323
4324    {
4325        let sa_ro: &[SaSint] = sa;
4326        run_rayon_with_threads(omp_num_threads, || {
4327            thread_state[..omp_num_threads]
4328                .par_iter_mut()
4329                .enumerate()
4330                .for_each(|(omp_thread_num, state)| {
4331                    let mut omp_block_start = omp_thread_num * omp_block_stride;
4332                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
4333                        omp_block_stride
4334                    } else {
4335                        block_size_usize - omp_block_start
4336                    };
4337                    omp_block_start +=
4338                        usize::try_from(block_start).expect("block_start must be non-negative");
4339
4340                    let (position, count) = partial_sorting_scan_right_to_left_8u_block_prepare(
4341                        t,
4342                        sa_ro,
4343                        k,
4344                        &mut state.buckets,
4345                        &mut state.cache,
4346                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
4347                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
4348                    );
4349                    state.position = position;
4350                    state.count = count;
4351                });
4352        });
4353    }
4354
4355    let distinct_offset = 2 * ALPHABET_SIZE;
4356    let (induction_bucket, distinct_tail) = buckets.split_at_mut(distinct_offset);
4357    let distinct_names = &mut distinct_tail[..2 * k_usize];
4358
4359    for tnum in (0..omp_num_threads).rev() {
4360        let state = &mut thread_state[tnum];
4361        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
4362        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
4363
4364        for c in 0..2 * k_usize {
4365            let a = induction_bucket[c];
4366            let b = temp_induction_bucket[c];
4367            induction_bucket[c] = a - b;
4368            temp_induction_bucket[c] = a;
4369        }
4370
4371        d -= 1;
4372        for c in 0..2 * k_usize {
4373            let a = distinct_names[c];
4374            let b = temp_distinct_names[c];
4375            let next_d = b + d;
4376            distinct_names[c] = if b > 0 { next_d } else { a };
4377            temp_distinct_names[c] = a;
4378        }
4379        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
4380        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
4381    }
4382
4383    {
4384        let sa_ptr = SyncMutPtr::new(sa);
4385        run_rayon_with_threads(omp_num_threads, || {
4386            thread_state[..omp_num_threads]
4387                .par_iter_mut()
4388                .for_each(|state| {
4389                    // SAFETY: per-thread state.buckets induction values place each thread's
4390                    // writes at distinct positions in sa.
4391                    let sa = unsafe { sa_ptr.as_slice() };
4392                    partial_sorting_scan_right_to_left_8u_block_place(
4393                        sa,
4394                        &mut state.buckets,
4395                        k,
4396                        &state.cache,
4397                        state.count,
4398                        state.position as SaSint,
4399                    );
4400                });
4401        });
4402    }
4403
4404    d
4405}
4406
4407/// Internal helper: partial gsa scan right to left 8u block (OpenMP variant).
4408#[doc(hidden)]
4409pub fn partial_gsa_scan_right_to_left_8u_block_omp(
4410    t: &[u8],
4411    sa: &mut [SaSint],
4412    k: SaSint,
4413    buckets: &mut [SaSint],
4414    d: SaSint,
4415    block_start: FastSint,
4416    block_size: FastSint,
4417    threads: SaSint,
4418    thread_state: &mut [ThreadState],
4419) -> SaSint {
4420    let mut d = d;
4421    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
4422    let k_usize = usize::try_from(k).expect("k must be non-negative");
4423    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
4424        usize::try_from(threads)
4425            .expect("threads must be non-negative")
4426            .min(thread_state.len())
4427            .max(1)
4428    } else {
4429        1
4430    };
4431    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
4432
4433    if omp_num_threads == 1 {
4434        return partial_gsa_scan_right_to_left_8u(t, sa, buckets, d, block_start, block_size);
4435    }
4436
4437    {
4438        let sa_ro: &[SaSint] = sa;
4439        run_rayon_with_threads(omp_num_threads, || {
4440            thread_state[..omp_num_threads]
4441                .par_iter_mut()
4442                .enumerate()
4443                .for_each(|(omp_thread_num, state)| {
4444                    let mut omp_block_start = omp_thread_num * omp_block_stride;
4445                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
4446                        omp_block_stride
4447                    } else {
4448                        block_size_usize - omp_block_start
4449                    };
4450                    omp_block_start +=
4451                        usize::try_from(block_start).expect("block_start must be non-negative");
4452
4453                    let (position, count) = partial_sorting_scan_right_to_left_8u_block_prepare(
4454                        t,
4455                        sa_ro,
4456                        k,
4457                        &mut state.buckets,
4458                        &mut state.cache,
4459                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
4460                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
4461                    );
4462                    state.position = position;
4463                    state.count = count;
4464                });
4465        });
4466    }
4467
4468    let distinct_offset = 2 * ALPHABET_SIZE;
4469    let (induction_bucket, distinct_tail) = buckets.split_at_mut(distinct_offset);
4470    let distinct_names = &mut distinct_tail[..2 * k_usize];
4471
4472    for tnum in (0..omp_num_threads).rev() {
4473        let state = &mut thread_state[tnum];
4474        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
4475        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
4476
4477        for c in 0..2 * k_usize {
4478            let a = induction_bucket[c];
4479            let b = temp_induction_bucket[c];
4480            induction_bucket[c] = a - b;
4481            temp_induction_bucket[c] = a;
4482        }
4483
4484        d -= 1;
4485        for c in 0..2 * k_usize {
4486            let a = distinct_names[c];
4487            let b = temp_distinct_names[c];
4488            let next_d = b + d;
4489            distinct_names[c] = if b > 0 { next_d } else { a };
4490            temp_distinct_names[c] = a;
4491        }
4492        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
4493        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
4494    }
4495
4496    {
4497        let sa_ptr = SyncMutPtr::new(sa);
4498        run_rayon_with_threads(omp_num_threads, || {
4499            thread_state[..omp_num_threads]
4500                .par_iter_mut()
4501                .for_each(|state| {
4502                    // SAFETY: per-thread bucket-induction values point each thread's writes
4503                    // to distinct sa positions.
4504                    let sa = unsafe { sa_ptr.as_slice() };
4505                    partial_gsa_scan_right_to_left_8u_block_place(
4506                        sa,
4507                        &mut state.buckets,
4508                        k,
4509                        &state.cache,
4510                        state.count,
4511                        state.position as SaSint,
4512                    );
4513                });
4514        });
4515    }
4516
4517    d
4518}
4519
4520/// Internal helper: partial sorting scan right to left 8u (OpenMP variant).
4521#[doc(hidden)]
4522pub fn partial_sorting_scan_right_to_left_8u_omp(
4523    t: &[u8],
4524    sa: &mut [SaSint],
4525    n: SaSint,
4526    k: SaSint,
4527    buckets: &mut [SaSint],
4528    first_lms_suffix: SaSint,
4529    left_suffixes_count: SaSint,
4530    mut d: SaSint,
4531    threads: SaSint,
4532    thread_state: &mut [ThreadState],
4533) {
4534    let scan_start = left_suffixes_count as FastSint + 1;
4535    let scan_end = n as FastSint - first_lms_suffix as FastSint;
4536
4537    if threads == 1 || (scan_end - scan_start) < 65_536 {
4538        let _ = partial_sorting_scan_right_to_left_8u(
4539            t,
4540            sa,
4541            buckets,
4542            d,
4543            scan_start,
4544            scan_end - scan_start,
4545        );
4546        return;
4547    }
4548
4549    let distinct_offset = 2 * ALPHABET_SIZE;
4550
4551    let mut block_start = usize::try_from(scan_end - 1).expect("scan end must be positive");
4552    let scan_start_usize = usize::try_from(scan_start).expect("scan_start must be non-negative");
4553    let threads_usize = usize::try_from(threads)
4554        .expect("threads must be non-negative")
4555        .min(thread_state.len())
4556        .max(1);
4557
4558    while block_start >= scan_start_usize {
4559        if sa[block_start] == 0 {
4560            if block_start == 0 {
4561                break;
4562            }
4563            block_start -= 1;
4564        } else {
4565            let mut block_max_end = block_start.saturating_sub(
4566                threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize),
4567            );
4568            if block_max_end + 1 < scan_start_usize {
4569                block_max_end = scan_start_usize.saturating_sub(1);
4570            }
4571            let mut block_end = block_start - 1;
4572            while block_end > block_max_end && sa[block_end] != 0 {
4573                block_end -= 1;
4574            }
4575            let block_size = block_start - block_end;
4576
4577            if block_size < 32 {
4578                while block_start > block_end {
4579                    let p = sa[block_start];
4580                    d += SaSint::from(p < 0);
4581                    let p = p & SAINT_MAX;
4582                    let v = buckets_index2(
4583                        t[(p - 1) as usize] as usize,
4584                        usize::from(t[(p - 2) as usize] > t[(p - 1) as usize]),
4585                    );
4586                    buckets[v] -= 1;
4587                    let slot =
4588                        usize::try_from(buckets[v]).expect("bucket slot must be non-negative");
4589                    sa[slot] = (p - 1)
4590                        | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
4591                    buckets[distinct_offset + v] = d;
4592
4593                    if block_start == 0 {
4594                        break;
4595                    }
4596                    block_start -= 1;
4597                }
4598            } else {
4599                d = partial_sorting_scan_right_to_left_8u_block_omp(
4600                    t,
4601                    sa,
4602                    k,
4603                    buckets,
4604                    d,
4605                    FastSint::try_from(block_end + 1).expect("block start must fit FastSint"),
4606                    FastSint::try_from(block_size).expect("block size must fit FastSint"),
4607                    threads,
4608                    thread_state,
4609                );
4610                block_start = block_end;
4611            }
4612        }
4613    }
4614}
4615
4616/// Internal helper: partial gsa scan right to left 8u (OpenMP variant).
4617#[doc(hidden)]
4618pub fn partial_gsa_scan_right_to_left_8u_omp(
4619    t: &[u8],
4620    sa: &mut [SaSint],
4621    n: SaSint,
4622    k: SaSint,
4623    buckets: &mut [SaSint],
4624    first_lms_suffix: SaSint,
4625    left_suffixes_count: SaSint,
4626    mut d: SaSint,
4627    threads: SaSint,
4628    thread_state: &mut [ThreadState],
4629) {
4630    let scan_start = left_suffixes_count as FastSint + 1;
4631    let scan_end = n as FastSint - first_lms_suffix as FastSint;
4632
4633    if threads == 1 || (scan_end - scan_start) < 65_536 {
4634        let _ =
4635            partial_gsa_scan_right_to_left_8u(t, sa, buckets, d, scan_start, scan_end - scan_start);
4636        return;
4637    }
4638
4639    let distinct_offset = 2 * ALPHABET_SIZE;
4640    let mut block_start = usize::try_from(scan_end - 1).expect("scan end must be positive");
4641    let scan_start_usize = usize::try_from(scan_start).expect("scan_start must be non-negative");
4642    let threads_usize = usize::try_from(threads)
4643        .expect("threads must be non-negative")
4644        .min(thread_state.len())
4645        .max(1);
4646
4647    while block_start >= scan_start_usize {
4648        if sa[block_start] == 0 {
4649            if block_start == 0 {
4650                break;
4651            }
4652            block_start -= 1;
4653        } else {
4654            let mut block_max_end = block_start.saturating_sub(
4655                threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize),
4656            );
4657            if block_max_end + 1 < scan_start_usize {
4658                block_max_end = scan_start_usize.saturating_sub(1);
4659            }
4660            let mut block_end = block_start - 1;
4661            while block_end > block_max_end && sa[block_end] != 0 {
4662                block_end -= 1;
4663            }
4664            let block_size = block_start - block_end;
4665
4666            if block_size < 32 {
4667                while block_start > block_end {
4668                    let p = sa[block_start];
4669                    d += SaSint::from(p < 0);
4670                    let p = p & SAINT_MAX;
4671                    let v = buckets_index2(
4672                        t[(p - 1) as usize] as usize,
4673                        usize::from(t[(p - 2) as usize] > t[(p - 1) as usize]),
4674                    );
4675                    if v != 1 {
4676                        buckets[v] -= 1;
4677                        let slot =
4678                            usize::try_from(buckets[v]).expect("bucket slot must be non-negative");
4679                        sa[slot] = (p - 1)
4680                            | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
4681                        buckets[distinct_offset + v] = d;
4682                    }
4683
4684                    if block_start == 0 {
4685                        break;
4686                    }
4687                    block_start -= 1;
4688                }
4689            } else {
4690                d = partial_gsa_scan_right_to_left_8u_block_omp(
4691                    t,
4692                    sa,
4693                    k,
4694                    buckets,
4695                    d,
4696                    FastSint::try_from(block_end + 1).expect("block start must fit FastSint"),
4697                    FastSint::try_from(block_size).expect("block size must fit FastSint"),
4698                    threads,
4699                    thread_state,
4700                );
4701                block_start = block_end;
4702            }
4703        }
4704    }
4705}
4706
4707/// Internal helper: partial sorting scan right to left 32s 6k.
4708#[doc(hidden)]
4709pub fn partial_sorting_scan_right_to_left_32s_6k(
4710    t: &[SaSint],
4711    sa: &mut [SaSint],
4712    buckets: &mut [SaSint],
4713    mut d: SaSint,
4714    omp_block_start: FastSint,
4715    omp_block_size: FastSint,
4716) -> SaSint {
4717    if omp_block_size <= 0 {
4718        return d;
4719    }
4720
4721    let prefetch_distance: FastSint = 64;
4722    let mut i = omp_block_start + omp_block_size - 1;
4723    let mut j = omp_block_start + 2 * prefetch_distance + 1;
4724
4725    let sa_ptr = sa.as_ptr();
4726    let t_ptr = t.as_ptr();
4727    let buckets_ptr = buckets.as_ptr();
4728    let prefetch_distance_us = prefetch_distance as usize;
4729    while i >= j {
4730        let i_us = i as usize;
4731        libsais_prefetchr(sa_ptr.wrapping_add(i_us.wrapping_sub(3 * prefetch_distance_us)));
4732        let pa = (sa[i_us - 2 * prefetch_distance_us] & SAINT_MAX) as usize;
4733        libsais_prefetchr(t_ptr.wrapping_add(pa).wrapping_sub(1));
4734        libsais_prefetchr(t_ptr.wrapping_add(pa).wrapping_sub(2));
4735        let pb = (sa[i_us - 2 * prefetch_distance_us - 1] & SAINT_MAX) as usize;
4736        libsais_prefetchr(t_ptr.wrapping_add(pb).wrapping_sub(1));
4737        libsais_prefetchr(t_ptr.wrapping_add(pb).wrapping_sub(2));
4738        let pc = (sa[i_us - prefetch_distance_us] & SAINT_MAX) as usize;
4739        let vc = buckets_index4(t[pc - usize::from(pc > 0)] as usize, 0);
4740        libsais_prefetchw(buckets_ptr.wrapping_add(vc));
4741        let pd = (sa[i_us - prefetch_distance_us - 1] & SAINT_MAX) as usize;
4742        let vd = buckets_index4(t[pd - usize::from(pd > 0)] as usize, 0);
4743        libsais_prefetchw(buckets_ptr.wrapping_add(vd));
4744
4745        let mut p0 = sa[i as usize];
4746        d += SaSint::from(p0 < 0);
4747        p0 &= SAINT_MAX;
4748        let p0u = p0 as usize;
4749        let v0 = buckets_index4(t[p0u - 1] as usize, usize::from(t[p0u - 2] > t[p0u - 1]));
4750        buckets[v0] -= 1;
4751        let slot0 = buckets[v0] as usize;
4752        sa[slot0] = (p0 - 1) | (((buckets[2 + v0] != d) as SaSint) << (SAINT_BIT - 1));
4753        buckets[2 + v0] = d;
4754
4755        let mut p1 = sa[(i - 1) as usize];
4756        d += SaSint::from(p1 < 0);
4757        p1 &= SAINT_MAX;
4758        let p1u = p1 as usize;
4759        let v1 = buckets_index4(t[p1u - 1] as usize, usize::from(t[p1u - 2] > t[p1u - 1]));
4760        buckets[v1] -= 1;
4761        let slot1 = buckets[v1] as usize;
4762        sa[slot1] = (p1 - 1) | (((buckets[2 + v1] != d) as SaSint) << (SAINT_BIT - 1));
4763        buckets[2 + v1] = d;
4764
4765        i -= 2;
4766    }
4767
4768    j -= 2 * prefetch_distance + 1;
4769    while i >= j {
4770        let mut p = sa[i as usize];
4771        d += SaSint::from(p < 0);
4772        p &= SAINT_MAX;
4773        let pu = p as usize;
4774        let v = buckets_index4(t[pu - 1] as usize, usize::from(t[pu - 2] > t[pu - 1]));
4775
4776        buckets[v] -= 1;
4777        let slot = buckets[v] as usize;
4778        sa[slot] = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
4779        buckets[2 + v] = d;
4780        i -= 1;
4781    }
4782
4783    d
4784}
4785
4786/// Internal helper: partial sorting scan right to left 32s 4k.
4787#[doc(hidden)]
4788pub fn partial_sorting_scan_right_to_left_32s_4k(
4789    t: &[SaSint],
4790    sa: &mut [SaSint],
4791    k: SaSint,
4792    buckets: &mut [SaSint],
4793    mut d: SaSint,
4794    omp_block_start: FastSint,
4795    omp_block_size: FastSint,
4796) -> SaSint {
4797    if omp_block_size <= 0 {
4798        return d;
4799    }
4800
4801    let k_usize = usize::try_from(k).expect("k must be non-negative");
4802    let prefetch_distance: FastSint = 64;
4803    let induction_offset = 3 * k_usize;
4804
4805    let mut i = omp_block_start + omp_block_size - 1;
4806    let mut j = omp_block_start + 2 * prefetch_distance + 1;
4807
4808    while i >= j {
4809        let i0 = i as usize;
4810        let mut p0 = sa[i0];
4811        if p0 > 0 {
4812            sa[i0] = 0;
4813            d += p0 >> (SUFFIX_GROUP_BIT - 1);
4814            p0 &= !SUFFIX_GROUP_MARKER;
4815
4816            let p0u = p0 as usize;
4817            let c0 = t[p0u - 1];
4818            let f0 = usize::from(t[p0u - 2] > c0);
4819            let v0 = buckets_index2(c0 as usize, f0);
4820            let c0u = c0 as usize;
4821            buckets[induction_offset + c0u] -= 1;
4822            let slot0 = buckets[induction_offset + c0u] as usize;
4823            sa[slot0] = (p0 - 1)
4824                | ((f0 as SaSint) << (SAINT_BIT - 1))
4825                | (((buckets[v0] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4826            buckets[v0] = d;
4827        }
4828
4829        let i1 = (i - 1) as usize;
4830        let mut p1 = sa[i1];
4831        if p1 > 0 {
4832            sa[i1] = 0;
4833            d += p1 >> (SUFFIX_GROUP_BIT - 1);
4834            p1 &= !SUFFIX_GROUP_MARKER;
4835
4836            let p1u = p1 as usize;
4837            let c1 = t[p1u - 1];
4838            let f1 = usize::from(t[p1u - 2] > c1);
4839            let v1 = buckets_index2(c1 as usize, f1);
4840            let c1u = c1 as usize;
4841            buckets[induction_offset + c1u] -= 1;
4842            let slot1 = buckets[induction_offset + c1u] as usize;
4843            sa[slot1] = (p1 - 1)
4844                | ((f1 as SaSint) << (SAINT_BIT - 1))
4845                | (((buckets[v1] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4846            buckets[v1] = d;
4847        }
4848
4849        i -= 2;
4850    }
4851
4852    j -= 2 * prefetch_distance + 1;
4853    while i >= j {
4854        let iu = i as usize;
4855        let mut p = sa[iu];
4856        if p > 0 {
4857            sa[iu] = 0;
4858            d += p >> (SUFFIX_GROUP_BIT - 1);
4859            p &= !SUFFIX_GROUP_MARKER;
4860
4861            let pu = p as usize;
4862            let c = t[pu - 1];
4863            let f = usize::from(t[pu - 2] > c);
4864            let v = buckets_index2(c as usize, f);
4865            let cu = c as usize;
4866            buckets[induction_offset + cu] -= 1;
4867            let slot = buckets[induction_offset + cu] as usize;
4868            sa[slot] = (p - 1)
4869                | ((f as SaSint) << (SAINT_BIT - 1))
4870                | (((buckets[v] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4871            buckets[v] = d;
4872        }
4873        i -= 1;
4874    }
4875
4876    d
4877}
4878
4879/// Internal helper: partial sorting scan right to left 32s 1k.
4880#[doc(hidden)]
4881pub fn partial_sorting_scan_right_to_left_32s_1k(
4882    t: &[SaSint],
4883    sa: &mut [SaSint],
4884    induction_bucket: &mut [SaSint],
4885    omp_block_start: FastSint,
4886    omp_block_size: FastSint,
4887) {
4888    if omp_block_size <= 0 {
4889        return;
4890    }
4891
4892    let prefetch_distance = 64usize;
4893    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4894    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4895    let mut i = (start + size - 1) as isize;
4896    let mut j = (start + 2 * prefetch_distance + 1) as isize;
4897
4898    while i >= j {
4899        let p0 = sa[i as usize];
4900        if p0 > 0 {
4901            sa[i as usize] = 0;
4902            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
4903            let bucket_index0 =
4904                usize::try_from(t[p0_usize - 1]).expect("bucket symbol must be non-negative");
4905            induction_bucket[bucket_index0] -= 1;
4906            let slot0 = usize::try_from(induction_bucket[bucket_index0])
4907                .expect("bucket slot must be non-negative");
4908            sa[slot0] = (p0 - 1)
4909                | ((usize::from(t[p0_usize - 2] > t[p0_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4910        }
4911        let p1 = sa[(i - 1) as usize];
4912        if p1 > 0 {
4913            sa[(i - 1) as usize] = 0;
4914            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
4915            let bucket_index1 =
4916                usize::try_from(t[p1_usize - 1]).expect("bucket symbol must be non-negative");
4917            induction_bucket[bucket_index1] -= 1;
4918            let slot1 = usize::try_from(induction_bucket[bucket_index1])
4919                .expect("bucket slot must be non-negative");
4920            sa[slot1] = (p1 - 1)
4921                | ((usize::from(t[p1_usize - 2] > t[p1_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4922        }
4923
4924        i -= 2;
4925    }
4926
4927    j -= (2 * prefetch_distance + 1) as isize;
4928    while i >= j {
4929        let p = sa[i as usize];
4930        if p > 0 {
4931            sa[i as usize] = 0;
4932            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4933            let bucket_index =
4934                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative");
4935            induction_bucket[bucket_index] -= 1;
4936            let slot = usize::try_from(induction_bucket[bucket_index])
4937                .expect("bucket slot must be non-negative");
4938            sa[slot] = (p - 1)
4939                | ((usize::from(t[p_usize - 2] > t[p_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4940        }
4941        if i == 0 {
4942            break;
4943        }
4944        i -= 1;
4945    }
4946}
4947
4948/// Internal helper: partial sorting scan right to left 32s 6k block gather.
4949#[doc(hidden)]
4950pub fn partial_sorting_scan_right_to_left_32s_6k_block_gather(
4951    t: &[SaSint],
4952    sa: &[SaSint],
4953    cache: &mut [ThreadCache],
4954    omp_block_start: FastSint,
4955    omp_block_size: FastSint,
4956) {
4957    if omp_block_size <= 0 {
4958        return;
4959    }
4960
4961    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4962    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4963    for offset in 0..size {
4964        let i = start + offset;
4965        let mut p = sa[i];
4966        let mut symbol = 0usize;
4967        p &= SAINT_MAX;
4968        if p != 0 {
4969            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4970            symbol = buckets_index4(
4971                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
4972                usize::from(t[p_usize - 2] > t[p_usize - 1]),
4973            );
4974        }
4975        cache[offset].index = sa[i];
4976        cache[offset].symbol = symbol as SaSint;
4977    }
4978}
4979
4980/// Internal helper: partial sorting scan right to left 32s 4k block gather.
4981#[doc(hidden)]
4982pub fn partial_sorting_scan_right_to_left_32s_4k_block_gather(
4983    t: &[SaSint],
4984    sa: &mut [SaSint],
4985    cache: &mut [ThreadCache],
4986    omp_block_start: FastSint,
4987    omp_block_size: FastSint,
4988) {
4989    if omp_block_size <= 0 {
4990        return;
4991    }
4992
4993    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4994    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4995    for offset in 0..size {
4996        let i = start + offset;
4997        let mut symbol = SAINT_MIN;
4998        let mut p = sa[i];
4999        if p > 0 {
5000            sa[i] = 0;
5001            cache[offset].index = p;
5002            p &= !SUFFIX_GROUP_MARKER;
5003            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
5004            symbol = buckets_index2(
5005                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
5006                usize::from(t[p_usize - 2] > t[p_usize - 1]),
5007            ) as SaSint;
5008        }
5009        cache[offset].symbol = symbol;
5010    }
5011}
5012
5013/// Internal helper: partial sorting scan right to left 32s 1k block gather.
5014#[doc(hidden)]
5015pub fn partial_sorting_scan_right_to_left_32s_1k_block_gather(
5016    t: &[SaSint],
5017    sa: &mut [SaSint],
5018    cache: &mut [ThreadCache],
5019    omp_block_start: FastSint,
5020    omp_block_size: FastSint,
5021) {
5022    if omp_block_size <= 0 {
5023        return;
5024    }
5025    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5026    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5027    for offset in 0..size {
5028        let i = start + offset;
5029        let mut symbol = SAINT_MIN;
5030        let p = sa[i];
5031        if p > 0 {
5032            sa[i] = 0;
5033            cache[offset].index = (p - 1)
5034                | ((usize::from(t[p as usize - 2] > t[p as usize - 1]) as SaSint)
5035                    << (SAINT_BIT - 1));
5036            symbol = t[p as usize - 1];
5037        }
5038        cache[offset].symbol = symbol;
5039    }
5040}
5041
5042/// Internal helper: partial sorting scan right to left 32s 6k block sort.
5043#[doc(hidden)]
5044pub fn partial_sorting_scan_right_to_left_32s_6k_block_sort(
5045    t: &[SaSint],
5046    buckets: &mut [SaSint],
5047    mut d: SaSint,
5048    cache: &mut [ThreadCache],
5049    omp_block_start: FastSint,
5050    omp_block_size: FastSint,
5051) -> SaSint {
5052    if omp_block_size <= 0 {
5053        return d;
5054    }
5055
5056    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5057    let mut i = size;
5058    while i > 0 {
5059        i -= 1;
5060
5061        let v = usize::try_from(cache[i].symbol).expect("cache symbol must be non-negative");
5062        let p = cache[i].index;
5063        d += SaSint::from(p < 0);
5064        buckets[v] -= 1;
5065        let target = buckets[v];
5066        cache[i].symbol = target;
5067        cache[i].index = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
5068        buckets[2 + v] = d;
5069
5070        let block_end = omp_block_start as SaSint + omp_block_size as SaSint;
5071        if target >= omp_block_start as SaSint && target < block_end {
5072            let s = usize::try_from(target - omp_block_start as SaSint)
5073                .expect("cache slot must be non-negative");
5074            let q = cache[i].index & SAINT_MAX;
5075            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5076            cache[s].index = cache[i].index;
5077            cache[s].symbol = buckets_index4(
5078                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5079                usize::from(t[q_usize - 2] > t[q_usize - 1]),
5080            ) as SaSint;
5081        }
5082    }
5083
5084    d
5085}
5086
5087/// Internal helper: partial sorting scan right to left 32s 4k block sort.
5088#[doc(hidden)]
5089pub fn partial_sorting_scan_right_to_left_32s_4k_block_sort(
5090    t: &[SaSint],
5091    k: SaSint,
5092    buckets: &mut [SaSint],
5093    mut d: SaSint,
5094    cache: &mut [ThreadCache],
5095    omp_block_start: FastSint,
5096    omp_block_size: FastSint,
5097) -> SaSint {
5098    if omp_block_size <= 0 {
5099        return d;
5100    }
5101
5102    let k_usize = usize::try_from(k).expect("k must be non-negative");
5103    let (distinct_names, tail) = buckets.split_at_mut(2 * k_usize);
5104    let induction_bucket = &mut tail[k_usize..2 * k_usize];
5105
5106    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5107    let mut i = size;
5108    while i > 0 {
5109        i -= 1;
5110
5111        let v = cache[i].symbol;
5112        if v >= 0 {
5113            let p = cache[i].index;
5114            d += p >> (SUFFIX_GROUP_BIT - 1);
5115            let bucket_index = usize::try_from(v >> 1).expect("bucket symbol must be non-negative");
5116            induction_bucket[bucket_index] -= 1;
5117            let target = induction_bucket[bucket_index];
5118            cache[i].symbol = target;
5119            cache[i].index = (p - 1)
5120                | ((v & 1) << (SAINT_BIT - 1))
5121                | (((distinct_names
5122                    [usize::try_from(v).expect("bucket symbol must be non-negative")]
5123                    != d) as SaSint)
5124                    << (SUFFIX_GROUP_BIT - 1));
5125            distinct_names[usize::try_from(v).expect("bucket symbol must be non-negative")] = d;
5126
5127            let block_end = omp_block_start as SaSint + omp_block_size as SaSint;
5128            if target >= omp_block_start as SaSint && target < block_end {
5129                let ni = usize::try_from(target - omp_block_start as SaSint)
5130                    .expect("cache slot must be non-negative");
5131                let mut np = cache[i].index;
5132                if np > 0 {
5133                    cache[i].index = 0;
5134                    cache[ni].index = np;
5135                    np &= !SUFFIX_GROUP_MARKER;
5136                    let np_usize = usize::try_from(np).expect("suffix index must be non-negative");
5137                    cache[ni].symbol = buckets_index2(
5138                        usize::try_from(t[np_usize - 1])
5139                            .expect("bucket symbol must be non-negative"),
5140                        usize::from(t[np_usize - 2] > t[np_usize - 1]),
5141                    ) as SaSint;
5142                }
5143            }
5144        }
5145    }
5146
5147    d
5148}
5149
5150/// Internal helper: partial sorting scan right to left 32s 1k block sort.
5151#[doc(hidden)]
5152pub fn partial_sorting_scan_right_to_left_32s_1k_block_sort(
5153    t: &[SaSint],
5154    induction_bucket: &mut [SaSint],
5155    cache: &mut [ThreadCache],
5156    omp_block_start: FastSint,
5157    omp_block_size: FastSint,
5158) {
5159    if omp_block_size <= 0 {
5160        return;
5161    }
5162    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5163    let mut offset = size;
5164
5165    while offset > 0 {
5166        offset -= 1;
5167        let v = cache[offset].symbol;
5168        if v >= 0 {
5169            let bucket_index = v as usize;
5170            induction_bucket[bucket_index] -= 1;
5171            let target = induction_bucket[bucket_index];
5172            cache[offset].symbol = target;
5173            let block_end = omp_block_start as SaSint + omp_block_size as SaSint;
5174            if target >= omp_block_start as SaSint && target < block_end {
5175                let ni = usize::try_from(target - omp_block_start as SaSint)
5176                    .expect("cache slot must be non-negative");
5177                let np = cache[offset].index;
5178                if np > 0 {
5179                    cache[offset].index = 0;
5180                    cache[ni].index = (np - 1)
5181                        | ((usize::from(t[np as usize - 2] > t[np as usize - 1]) as SaSint)
5182                            << (SAINT_BIT - 1));
5183                    cache[ni].symbol = t[np as usize - 1];
5184                }
5185            }
5186        }
5187    }
5188}
5189
5190/// Internal helper: partial sorting scan right to left 32s 6k block (OpenMP variant).
5191#[doc(hidden)]
5192pub fn partial_sorting_scan_right_to_left_32s_6k_block_omp(
5193    t: &[SaSint],
5194    sa: &mut [SaSint],
5195    buckets: &mut [SaSint],
5196    mut d: SaSint,
5197    cache: &mut [ThreadCache],
5198    block_start: FastSint,
5199    block_size: FastSint,
5200    threads: SaSint,
5201) -> SaSint {
5202    if block_size <= 0 {
5203        return d;
5204    }
5205    if threads == 1 || block_size < 16_384 {
5206        return partial_sorting_scan_right_to_left_32s_6k(
5207            t,
5208            sa,
5209            buckets,
5210            d,
5211            block_start,
5212            block_size,
5213        );
5214    }
5215
5216    let threads_usize = usize::try_from(threads)
5217        .expect("threads must be non-negative")
5218        .max(1);
5219    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5220    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5221    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5222
5223    {
5224        let sa_ro: &[SaSint] = sa;
5225        let t_ro: &[SaSint] = t;
5226        let cache_ptr = SyncMutPtr::new(cache);
5227        run_rayon_with_threads(omp_num_threads, || {
5228            (0..omp_num_threads)
5229                .into_par_iter()
5230                .for_each(|omp_thread_num| {
5231                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5232                        omp_block_stride
5233                    } else {
5234                        block_size_usize - omp_thread_num * omp_block_stride
5235                    };
5236                    let omp_block_start = usize::try_from(block_start)
5237                        .expect("block_start must be non-negative")
5238                        + omp_thread_num * omp_block_stride;
5239                    if omp_block_size > 0 {
5240                        // SAFETY: each thread writes to a disjoint cache slice.
5241                        let cache = unsafe { cache_ptr.as_slice() };
5242                        partial_sorting_scan_right_to_left_32s_6k_block_gather(
5243                            t_ro,
5244                            sa_ro,
5245                            &mut cache[omp_thread_num * omp_block_stride
5246                                ..omp_thread_num * omp_block_stride + omp_block_size],
5247                            omp_block_start as FastSint,
5248                            omp_block_size as FastSint,
5249                        );
5250                    }
5251                });
5252        });
5253    }
5254
5255    d = partial_sorting_scan_right_to_left_32s_6k_block_sort(
5256        t,
5257        buckets,
5258        d,
5259        &mut cache[..block_size_usize],
5260        block_start,
5261        block_size,
5262    );
5263
5264    {
5265        let sa_ptr = SyncMutPtr::new(sa);
5266        let cache_ro: &[ThreadCache] = cache;
5267        run_rayon_with_threads(omp_num_threads, || {
5268            (0..omp_num_threads)
5269                .into_par_iter()
5270                .for_each(|omp_thread_num| {
5271                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5272                        omp_block_stride
5273                    } else {
5274                        block_size_usize - omp_thread_num * omp_block_stride
5275                    };
5276                    let cache_start = omp_thread_num * omp_block_stride;
5277                    if omp_block_size > 0 {
5278                        // SAFETY: place_cached_suffixes writes to sa[cache_entry.symbol] for
5279                        // each entry in cache[cache_start..cache_start+size]; symbols were
5280                        // computed during block_sort so threads produce disjoint sa indices.
5281                        let sa = unsafe { sa_ptr.as_slice() };
5282                        place_cached_suffixes(
5283                            sa,
5284                            &cache_ro[cache_start..],
5285                            0,
5286                            omp_block_size as FastSint,
5287                        );
5288                    }
5289                });
5290        });
5291    }
5292
5293    d
5294}
5295
5296/// Internal helper: partial sorting scan right to left 32s 4k block (OpenMP variant).
5297#[doc(hidden)]
5298pub fn partial_sorting_scan_right_to_left_32s_4k_block_omp(
5299    t: &[SaSint],
5300    sa: &mut [SaSint],
5301    k: SaSint,
5302    buckets: &mut [SaSint],
5303    mut d: SaSint,
5304    cache: &mut [ThreadCache],
5305    block_start: FastSint,
5306    block_size: FastSint,
5307    threads: SaSint,
5308) -> SaSint {
5309    if block_size <= 0 {
5310        return d;
5311    }
5312    if threads == 1 || block_size < 16_384 {
5313        return partial_sorting_scan_right_to_left_32s_4k(
5314            t,
5315            sa,
5316            k,
5317            buckets,
5318            d,
5319            block_start,
5320            block_size,
5321        );
5322    }
5323
5324    let threads_usize = usize::try_from(threads)
5325        .expect("threads must be non-negative")
5326        .max(1);
5327    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5328    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5329    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5330
5331    {
5332        let sa_ptr = SyncMutPtr::new(sa);
5333        let t_ro: &[SaSint] = t;
5334        let cache_ptr = SyncMutPtr::new(cache);
5335        run_rayon_with_threads(omp_num_threads, || {
5336            (0..omp_num_threads)
5337                .into_par_iter()
5338                .for_each(|omp_thread_num| {
5339                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5340                        omp_block_stride
5341                    } else {
5342                        block_size_usize - omp_thread_num * omp_block_stride
5343                    };
5344                    let omp_block_start = usize::try_from(block_start)
5345                        .expect("block_start must be non-negative")
5346                        + omp_thread_num * omp_block_stride;
5347                    if omp_block_size > 0 {
5348                        // SAFETY: per-thread disjoint cache slice and disjoint
5349                        // sa[omp_block_start..omp_block_start+omp_block_size] writes.
5350                        let cache = unsafe { cache_ptr.as_slice() };
5351                        let sa = unsafe { sa_ptr.as_slice() };
5352                        partial_sorting_scan_right_to_left_32s_4k_block_gather(
5353                            t_ro,
5354                            sa,
5355                            &mut cache[omp_thread_num * omp_block_stride
5356                                ..omp_thread_num * omp_block_stride + omp_block_size],
5357                            omp_block_start as FastSint,
5358                            omp_block_size as FastSint,
5359                        );
5360                    }
5361                });
5362        });
5363    }
5364
5365    d = partial_sorting_scan_right_to_left_32s_4k_block_sort(
5366        t,
5367        k,
5368        buckets,
5369        d,
5370        &mut cache[..block_size_usize],
5371        block_start,
5372        block_size,
5373    );
5374
5375    {
5376        let sa_ptr = SyncMutPtr::new(sa);
5377        let cache_ptr = SyncMutPtr::new(cache);
5378        run_rayon_with_threads(omp_num_threads, || {
5379            (0..omp_num_threads)
5380                .into_par_iter()
5381                .for_each(|omp_thread_num| {
5382                    let omp_block_start = omp_thread_num * omp_block_stride;
5383                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5384                        omp_block_stride
5385                    } else {
5386                        block_size_usize - omp_block_start
5387                    };
5388                    if omp_block_size > 0 {
5389                        // SAFETY: each thread compacts and places only within its own cache
5390                        // sub-range; sa writes go to symbol-indexed positions which are disjoint
5391                        // across threads by construction in block_sort.
5392                        let sa = unsafe { sa_ptr.as_slice() };
5393                        let cache = unsafe { cache_ptr.as_slice() };
5394                        compact_and_place_cached_suffixes(
5395                            sa,
5396                            &mut cache[omp_block_start..],
5397                            0,
5398                            omp_block_size as FastSint,
5399                        );
5400                    }
5401                });
5402        });
5403    }
5404
5405    d
5406}
5407
5408/// Internal helper: partial sorting scan right to left 32s 1k block (OpenMP variant).
5409#[doc(hidden)]
5410pub fn partial_sorting_scan_right_to_left_32s_1k_block_omp(
5411    t: &[SaSint],
5412    sa: &mut [SaSint],
5413    buckets: &mut [SaSint],
5414    cache: &mut [ThreadCache],
5415    block_start: FastSint,
5416    block_size: FastSint,
5417    threads: SaSint,
5418) {
5419    if block_size <= 0 {
5420        return;
5421    }
5422    if threads == 1 || block_size < 16_384 {
5423        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, block_start, block_size);
5424        return;
5425    }
5426
5427    let threads_usize = usize::try_from(threads)
5428        .expect("threads must be non-negative")
5429        .max(1);
5430    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5431    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5432    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5433    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5434
5435    {
5436        let sa_ptr = SyncMutPtr::new(sa);
5437        let t_ro: &[SaSint] = t;
5438        let cache_ptr = SyncMutPtr::new(cache);
5439        run_rayon_with_threads(omp_num_threads, || {
5440            (0..omp_num_threads)
5441                .into_par_iter()
5442                .for_each(|omp_thread_num| {
5443                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5444                        omp_block_stride
5445                    } else {
5446                        block_size_usize - omp_thread_num * omp_block_stride
5447                    };
5448                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5449                    if omp_block_size > 0 {
5450                        // SAFETY: per-thread disjoint cache slice and disjoint sa block.
5451                        let cache = unsafe { cache_ptr.as_slice() };
5452                        let sa = unsafe { sa_ptr.as_slice() };
5453                        partial_sorting_scan_right_to_left_32s_1k_block_gather(
5454                            t_ro,
5455                            sa,
5456                            &mut cache[omp_thread_num * omp_block_stride
5457                                ..omp_thread_num * omp_block_stride + omp_block_size],
5458                            omp_block_start as FastSint,
5459                            omp_block_size as FastSint,
5460                        );
5461                    }
5462                });
5463        });
5464    }
5465
5466    let cache = &mut cache[..block_size_usize];
5467    partial_sorting_scan_right_to_left_32s_1k_block_sort(
5468        t,
5469        buckets,
5470        cache,
5471        block_start,
5472        block_size,
5473    );
5474    {
5475        let sa_ptr = SyncMutPtr::new(sa);
5476        let cache_ptr = SyncMutPtr::new(cache);
5477        run_rayon_with_threads(omp_num_threads, || {
5478            (0..omp_num_threads)
5479                .into_par_iter()
5480                .for_each(|omp_thread_num| {
5481                    let omp_block_start = omp_thread_num * omp_block_stride;
5482                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5483                        omp_block_stride
5484                    } else {
5485                        block_size_usize - omp_block_start
5486                    };
5487                    if omp_block_size > 0 {
5488                        // SAFETY: per-thread disjoint cache range; sa writes go to distinct
5489                        // symbol-indexed positions established by block_sort.
5490                        let sa = unsafe { sa_ptr.as_slice() };
5491                        let cache = unsafe { cache_ptr.as_slice() };
5492                        compact_and_place_cached_suffixes(
5493                            sa,
5494                            &mut cache[omp_block_start..],
5495                            0,
5496                            omp_block_size as FastSint,
5497                        );
5498                    }
5499                });
5500        });
5501    }
5502}
5503
5504/// Internal helper: partial sorting scan left to right 32s 6k block gather.
5505#[doc(hidden)]
5506pub fn partial_sorting_scan_left_to_right_32s_6k_block_gather(
5507    t: &[SaSint],
5508    sa: &mut [SaSint],
5509    cache: &mut [ThreadCache],
5510    omp_block_start: FastSint,
5511    omp_block_size: FastSint,
5512) {
5513    if omp_block_size <= 0 {
5514        return;
5515    }
5516
5517    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5518    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5519    for offset in 0..size {
5520        let i = start + offset;
5521        let p = sa[i];
5522        cache[offset].index = p;
5523        let q = p & SAINT_MAX;
5524        cache[offset].symbol = if q != 0 {
5525            buckets_index4(
5526                usize::try_from(t[q as usize - 1]).expect("bucket symbol must be non-negative"),
5527                usize::from(t[q as usize - 2] >= t[q as usize - 1]),
5528            ) as SaSint
5529        } else {
5530            0
5531        };
5532    }
5533}
5534
5535/// Internal helper: partial sorting scan left to right 32s 4k block gather.
5536#[doc(hidden)]
5537pub fn partial_sorting_scan_left_to_right_32s_4k_block_gather(
5538    t: &[SaSint],
5539    sa: &mut [SaSint],
5540    cache: &mut [ThreadCache],
5541    omp_block_start: FastSint,
5542    omp_block_size: FastSint,
5543) {
5544    if omp_block_size <= 0 {
5545        return;
5546    }
5547
5548    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5549    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5550    for offset in 0..size {
5551        let i = start + offset;
5552        let mut symbol = SAINT_MIN;
5553        let mut p = sa[i];
5554        if p > 0 {
5555            cache[offset].index = p;
5556            p &= !SUFFIX_GROUP_MARKER;
5557            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
5558            symbol = buckets_index2(
5559                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
5560                usize::from(t[p_usize - 2] < t[p_usize - 1]),
5561            ) as SaSint;
5562            p = 0;
5563        }
5564        cache[offset].symbol = symbol;
5565        sa[i] = p & SAINT_MAX;
5566    }
5567}
5568
5569/// Internal helper: partial sorting scan left to right 32s 1k block gather.
5570#[doc(hidden)]
5571pub fn partial_sorting_scan_left_to_right_32s_1k_block_gather(
5572    t: &[SaSint],
5573    sa: &mut [SaSint],
5574    cache: &mut [ThreadCache],
5575    omp_block_start: FastSint,
5576    omp_block_size: FastSint,
5577) {
5578    if omp_block_size <= 0 {
5579        return;
5580    }
5581    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5582    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5583    for offset in 0..size {
5584        let i = start + offset;
5585        let mut symbol = SAINT_MIN;
5586        let mut p = sa[i];
5587        if p > 0 {
5588            cache[offset].index = (p - 1)
5589                | ((usize::from(t[p as usize - 2] < t[p as usize - 1]) as SaSint)
5590                    << (SAINT_BIT - 1));
5591            symbol = t[p as usize - 1];
5592            p = 0;
5593        }
5594        cache[offset].symbol = symbol;
5595        sa[i] = p & SAINT_MAX;
5596    }
5597}
5598
5599/// Internal helper: partial sorting scan left to right 32s 6k block sort.
5600#[doc(hidden)]
5601pub fn partial_sorting_scan_left_to_right_32s_6k_block_sort(
5602    t: &[SaSint],
5603    buckets: &mut [SaSint],
5604    mut d: SaSint,
5605    cache: &mut [ThreadCache],
5606    omp_block_start: FastSint,
5607    omp_block_size: FastSint,
5608) -> SaSint {
5609    if omp_block_size <= 0 {
5610        return d;
5611    }
5612
5613    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5614    let block_end =
5615        start + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5616
5617    let mut i = start;
5618    let mut j = block_end.saturating_sub(65);
5619    while i < j {
5620        let cache_i0 = i - start;
5621        let cache_i1 = cache_i0 + 1;
5622
5623        let v0 =
5624            usize::try_from(cache[cache_i0].symbol).expect("cache symbol must be non-negative");
5625        let p0 = cache[cache_i0].index;
5626        d += SaSint::from(p0 < 0);
5627        cache[cache_i0].symbol = buckets[v0];
5628        buckets[v0] += 1;
5629        cache[cache_i0].index =
5630            (p0 - 1) | ((SaSint::from(buckets[2 + v0] != d)) << (SAINT_BIT - 1));
5631        buckets[2 + v0] = d;
5632        if cache[cache_i0].symbol >= omp_block_start as SaSint
5633            && cache[cache_i0].symbol < block_end as SaSint
5634        {
5635            let s = usize::try_from(cache[cache_i0].symbol - omp_block_start as SaSint)
5636                .expect("cache slot must be non-negative");
5637            let q = cache[cache_i0].index & SAINT_MAX;
5638            cache[s].index = cache[cache_i0].index;
5639            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5640            cache[s].symbol = buckets_index4(
5641                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5642                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5643            ) as SaSint;
5644        }
5645
5646        let v1 =
5647            usize::try_from(cache[cache_i1].symbol).expect("cache symbol must be non-negative");
5648        let p1 = cache[cache_i1].index;
5649        d += SaSint::from(p1 < 0);
5650        cache[cache_i1].symbol = buckets[v1];
5651        buckets[v1] += 1;
5652        cache[cache_i1].index =
5653            (p1 - 1) | ((SaSint::from(buckets[2 + v1] != d)) << (SAINT_BIT - 1));
5654        buckets[2 + v1] = d;
5655        if cache[cache_i1].symbol >= omp_block_start as SaSint
5656            && cache[cache_i1].symbol < block_end as SaSint
5657        {
5658            let s = usize::try_from(cache[cache_i1].symbol - omp_block_start as SaSint)
5659                .expect("cache slot must be non-negative");
5660            let q = cache[cache_i1].index & SAINT_MAX;
5661            cache[s].index = cache[cache_i1].index;
5662            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5663            cache[s].symbol = buckets_index4(
5664                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5665                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5666            ) as SaSint;
5667        }
5668
5669        i += 2;
5670    }
5671
5672    j += 65;
5673    while i < j {
5674        let cache_i = i - start;
5675        let v = usize::try_from(cache[cache_i].symbol).expect("cache symbol must be non-negative");
5676        let p = cache[cache_i].index;
5677        d += SaSint::from(p < 0);
5678        cache[cache_i].symbol = buckets[v];
5679        buckets[v] += 1;
5680        cache[cache_i].index = (p - 1) | ((SaSint::from(buckets[2 + v] != d)) << (SAINT_BIT - 1));
5681        buckets[2 + v] = d;
5682        if cache[cache_i].symbol >= omp_block_start as SaSint
5683            && cache[cache_i].symbol < block_end as SaSint
5684        {
5685            let s = usize::try_from(cache[cache_i].symbol - omp_block_start as SaSint)
5686                .expect("cache slot must be non-negative");
5687            let q = cache[cache_i].index & SAINT_MAX;
5688            cache[s].index = cache[cache_i].index;
5689            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5690            cache[s].symbol = buckets_index4(
5691                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5692                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5693            ) as SaSint;
5694        }
5695        i += 1;
5696    }
5697
5698    d
5699}
5700
5701/// Internal helper: partial sorting scan left to right 32s 4k block sort.
5702#[doc(hidden)]
5703pub fn partial_sorting_scan_left_to_right_32s_4k_block_sort(
5704    t: &[SaSint],
5705    k: SaSint,
5706    buckets: &mut [SaSint],
5707    mut d: SaSint,
5708    cache: &mut [ThreadCache],
5709    omp_block_start: FastSint,
5710    omp_block_size: FastSint,
5711) -> SaSint {
5712    if omp_block_size <= 0 {
5713        return d;
5714    }
5715
5716    let k_usize = usize::try_from(k).expect("k must be non-negative");
5717    let (distinct_names, tail) = buckets.split_at_mut(2 * k_usize);
5718    let induction_bucket = &mut tail[..k_usize];
5719
5720    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5721    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5722    let block_end = start + size;
5723
5724    for offset in 0..size {
5725        let v = cache[offset].symbol;
5726        if v >= 0 {
5727            let p = cache[offset].index;
5728            d += p >> (SUFFIX_GROUP_BIT - 1);
5729
5730            let bucket_index = usize::try_from(v >> 1).expect("bucket index must be non-negative");
5731            let v_usize = usize::try_from(v).expect("cache symbol must be non-negative");
5732            let target = induction_bucket[bucket_index];
5733            induction_bucket[bucket_index] += 1;
5734
5735            cache[offset].symbol = target;
5736            cache[offset].index = (p - 1)
5737                | ((v & 1) << (SAINT_BIT - 1))
5738                | (((distinct_names[v_usize] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
5739            distinct_names[v_usize] = d;
5740
5741            if target >= omp_block_start as SaSint && target < block_end as SaSint {
5742                let ni = usize::try_from(target - omp_block_start as SaSint)
5743                    .expect("cache slot must be non-negative");
5744                let mut np = cache[offset].index;
5745                if np > 0 {
5746                    cache[ni].index = np;
5747                    np &= !SUFFIX_GROUP_MARKER;
5748                    let np_usize = usize::try_from(np).expect("suffix index must be non-negative");
5749                    cache[ni].symbol = buckets_index2(
5750                        usize::try_from(t[np_usize - 1])
5751                            .expect("bucket symbol must be non-negative"),
5752                        usize::from(t[np_usize - 2] < t[np_usize - 1]),
5753                    ) as SaSint;
5754                    np = 0;
5755                }
5756                cache[offset].index = np & SAINT_MAX;
5757            }
5758        }
5759    }
5760
5761    d
5762}
5763
5764/// Internal helper: partial sorting scan left to right 32s 1k block sort.
5765#[doc(hidden)]
5766pub fn partial_sorting_scan_left_to_right_32s_1k_block_sort(
5767    t: &[SaSint],
5768    induction_bucket: &mut [SaSint],
5769    cache: &mut [ThreadCache],
5770    omp_block_start: FastSint,
5771    omp_block_size: FastSint,
5772) {
5773    if omp_block_size <= 0 {
5774        return;
5775    }
5776    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5777    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5778    let block_end = start + size;
5779
5780    for offset in 0..size {
5781        let v = cache[offset].symbol;
5782        if v >= 0 {
5783            let v_usize = v as usize;
5784            let target = induction_bucket[v_usize];
5785            cache[offset].symbol = target;
5786            induction_bucket[v_usize] += 1;
5787            if target >= omp_block_start as SaSint && target < block_end as SaSint {
5788                let ni = usize::try_from(target - omp_block_start as SaSint)
5789                    .expect("cache slot must be non-negative");
5790                let mut np = cache[offset].index;
5791                if np > 0 {
5792                    cache[ni].index = (np - 1)
5793                        | ((usize::from(t[np as usize - 2] < t[np as usize - 1]) as SaSint)
5794                            << (SAINT_BIT - 1));
5795                    cache[ni].symbol = t[np as usize - 1];
5796                    np = 0;
5797                }
5798                cache[offset].index = np & SAINT_MAX;
5799            }
5800        }
5801    }
5802}
5803
5804/// Internal helper: partial sorting scan left to right 32s 6k block (OpenMP variant).
5805#[doc(hidden)]
5806pub fn partial_sorting_scan_left_to_right_32s_6k_block_omp(
5807    t: &[SaSint],
5808    sa: &mut [SaSint],
5809    buckets: &mut [SaSint],
5810    d: SaSint,
5811    cache: &mut [ThreadCache],
5812    block_start: FastSint,
5813    block_size: FastSint,
5814    threads: SaSint,
5815) -> SaSint {
5816    if block_size <= 0 {
5817        return d;
5818    }
5819    if threads == 1 || block_size < 16_384 {
5820        return partial_sorting_scan_left_to_right_32s_6k(
5821            t,
5822            sa,
5823            buckets,
5824            d,
5825            block_start,
5826            block_size,
5827        );
5828    }
5829
5830    let threads_usize = usize::try_from(threads)
5831        .expect("threads must be non-negative")
5832        .max(1);
5833    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5834    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5835    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5836    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5837
5838    {
5839        let sa_ptr = SyncMutPtr::new(sa);
5840        let t_ro: &[SaSint] = t;
5841        let cache_ptr = SyncMutPtr::new(cache);
5842        run_rayon_with_threads(omp_num_threads, || {
5843            (0..omp_num_threads)
5844                .into_par_iter()
5845                .for_each(|omp_thread_num| {
5846                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5847                        omp_block_stride
5848                    } else {
5849                        block_size_usize - omp_thread_num * omp_block_stride
5850                    };
5851                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5852                    if omp_block_size > 0 {
5853                        // SAFETY: disjoint cache slice and disjoint sa block per thread.
5854                        let cache = unsafe { cache_ptr.as_slice() };
5855                        let sa = unsafe { sa_ptr.as_slice() };
5856                        partial_sorting_scan_left_to_right_32s_6k_block_gather(
5857                            t_ro,
5858                            sa,
5859                            &mut cache[omp_thread_num * omp_block_stride
5860                                ..omp_thread_num * omp_block_stride + omp_block_size],
5861                            omp_block_start as FastSint,
5862                            omp_block_size as FastSint,
5863                        );
5864                    }
5865                });
5866        });
5867    }
5868
5869    let d = partial_sorting_scan_left_to_right_32s_6k_block_sort(
5870        t,
5871        buckets,
5872        d,
5873        &mut cache[..block_size_usize],
5874        block_start,
5875        block_size,
5876    );
5877
5878    {
5879        let sa_ptr = SyncMutPtr::new(sa);
5880        let cache_ro: &[ThreadCache] = cache;
5881        run_rayon_with_threads(omp_num_threads, || {
5882            (0..omp_num_threads)
5883                .into_par_iter()
5884                .for_each(|omp_thread_num| {
5885                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5886                        omp_block_stride
5887                    } else {
5888                        block_size_usize - omp_thread_num * omp_block_stride
5889                    };
5890                    if omp_block_size > 0 {
5891                        // SAFETY: per-thread sa writes go to distinct symbol-indexed positions.
5892                        let sa = unsafe { sa_ptr.as_slice() };
5893                        place_cached_suffixes(
5894                            sa,
5895                            &cache_ro[omp_thread_num * omp_block_stride..],
5896                            0,
5897                            omp_block_size as FastSint,
5898                        );
5899                    }
5900                });
5901        });
5902    }
5903    d
5904}
5905
5906/// Internal helper: partial sorting scan left to right 32s 4k block (OpenMP variant).
5907#[doc(hidden)]
5908pub fn partial_sorting_scan_left_to_right_32s_4k_block_omp(
5909    t: &[SaSint],
5910    sa: &mut [SaSint],
5911    k: SaSint,
5912    buckets: &mut [SaSint],
5913    d: SaSint,
5914    cache: &mut [ThreadCache],
5915    block_start: FastSint,
5916    block_size: FastSint,
5917    threads: SaSint,
5918) -> SaSint {
5919    if block_size <= 0 {
5920        return d;
5921    }
5922    if threads == 1 || block_size < 16_384 {
5923        return partial_sorting_scan_left_to_right_32s_4k(
5924            t,
5925            sa,
5926            k,
5927            buckets,
5928            d,
5929            block_start,
5930            block_size,
5931        );
5932    }
5933
5934    let threads_usize = usize::try_from(threads)
5935        .expect("threads must be non-negative")
5936        .max(1);
5937    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5938    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5939    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5940    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5941
5942    {
5943        let sa_ptr = SyncMutPtr::new(sa);
5944        let t_ro: &[SaSint] = t;
5945        let cache_ptr = SyncMutPtr::new(cache);
5946        run_rayon_with_threads(omp_num_threads, || {
5947            (0..omp_num_threads)
5948                .into_par_iter()
5949                .for_each(|omp_thread_num| {
5950                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5951                        omp_block_stride
5952                    } else {
5953                        block_size_usize - omp_thread_num * omp_block_stride
5954                    };
5955                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5956                    if omp_block_size > 0 {
5957                        // SAFETY: disjoint cache slice and disjoint sa block per thread.
5958                        let cache = unsafe { cache_ptr.as_slice() };
5959                        let sa = unsafe { sa_ptr.as_slice() };
5960                        partial_sorting_scan_left_to_right_32s_4k_block_gather(
5961                            t_ro,
5962                            sa,
5963                            &mut cache[omp_thread_num * omp_block_stride
5964                                ..omp_thread_num * omp_block_stride + omp_block_size],
5965                            omp_block_start as FastSint,
5966                            omp_block_size as FastSint,
5967                        );
5968                    }
5969                });
5970        });
5971    }
5972
5973    let cache = &mut cache[..block_size_usize];
5974    let d = partial_sorting_scan_left_to_right_32s_4k_block_sort(
5975        t,
5976        k,
5977        buckets,
5978        d,
5979        cache,
5980        block_start,
5981        block_size,
5982    );
5983
5984    {
5985        let sa_ptr = SyncMutPtr::new(sa);
5986        let cache_ptr = SyncMutPtr::new(cache);
5987        run_rayon_with_threads(omp_num_threads, || {
5988            (0..omp_num_threads)
5989                .into_par_iter()
5990                .for_each(|omp_thread_num| {
5991                    let omp_block_start = omp_thread_num * omp_block_stride;
5992                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5993                        omp_block_stride
5994                    } else {
5995                        block_size_usize - omp_block_start
5996                    };
5997                    if omp_block_size > 0 {
5998                        // SAFETY: disjoint cache range and sa writes to symbol-indexed positions.
5999                        let sa = unsafe { sa_ptr.as_slice() };
6000                        let cache = unsafe { cache_ptr.as_slice() };
6001                        compact_and_place_cached_suffixes(
6002                            sa,
6003                            &mut cache[omp_block_start..],
6004                            0,
6005                            omp_block_size as FastSint,
6006                        );
6007                    }
6008                });
6009        });
6010    }
6011
6012    d
6013}
6014
6015/// Internal helper: partial sorting scan left to right 32s 1k block (OpenMP variant).
6016#[doc(hidden)]
6017pub fn partial_sorting_scan_left_to_right_32s_1k_block_omp(
6018    t: &[SaSint],
6019    sa: &mut [SaSint],
6020    buckets: &mut [SaSint],
6021    cache: &mut [ThreadCache],
6022    block_start: FastSint,
6023    block_size: FastSint,
6024    threads: SaSint,
6025) {
6026    if block_size <= 0 {
6027        return;
6028    }
6029    if threads == 1 || block_size < 16_384 {
6030        partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, block_start, block_size);
6031        return;
6032    }
6033
6034    let threads_usize = usize::try_from(threads)
6035        .expect("threads must be non-negative")
6036        .max(1);
6037    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
6038    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
6039    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
6040    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
6041
6042    {
6043        let sa_ptr = SyncMutPtr::new(sa);
6044        let t_ro: &[SaSint] = t;
6045        let cache_ptr = SyncMutPtr::new(cache);
6046        run_rayon_with_threads(omp_num_threads, || {
6047            (0..omp_num_threads)
6048                .into_par_iter()
6049                .for_each(|omp_thread_num| {
6050                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6051                        omp_block_stride
6052                    } else {
6053                        block_size_usize - omp_thread_num * omp_block_stride
6054                    };
6055                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
6056                    if omp_block_size > 0 {
6057                        // SAFETY: disjoint cache slice and disjoint sa block per thread.
6058                        let cache = unsafe { cache_ptr.as_slice() };
6059                        let sa = unsafe { sa_ptr.as_slice() };
6060                        partial_sorting_scan_left_to_right_32s_1k_block_gather(
6061                            t_ro,
6062                            sa,
6063                            &mut cache[omp_thread_num * omp_block_stride
6064                                ..omp_thread_num * omp_block_stride + omp_block_size],
6065                            omp_block_start as FastSint,
6066                            omp_block_size as FastSint,
6067                        );
6068                    }
6069                });
6070        });
6071    }
6072
6073    let cache = &mut cache[..block_size_usize];
6074    partial_sorting_scan_left_to_right_32s_1k_block_sort(
6075        t,
6076        buckets,
6077        cache,
6078        block_start,
6079        block_size,
6080    );
6081    {
6082        let sa_ptr = SyncMutPtr::new(sa);
6083        let cache_ptr = SyncMutPtr::new(cache);
6084        run_rayon_with_threads(omp_num_threads, || {
6085            (0..omp_num_threads)
6086                .into_par_iter()
6087                .for_each(|omp_thread_num| {
6088                    let omp_block_start = omp_thread_num * omp_block_stride;
6089                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6090                        omp_block_stride
6091                    } else {
6092                        block_size_usize - omp_block_start
6093                    };
6094                    if omp_block_size > 0 {
6095                        // SAFETY: disjoint cache range and sa writes to symbol-indexed positions.
6096                        let sa = unsafe { sa_ptr.as_slice() };
6097                        let cache = unsafe { cache_ptr.as_slice() };
6098                        compact_and_place_cached_suffixes(
6099                            sa,
6100                            &mut cache[omp_block_start..],
6101                            0,
6102                            omp_block_size as FastSint,
6103                        );
6104                    }
6105                });
6106        });
6107    }
6108}
6109
6110/// Internal helper: partial sorting scan right to left 32s 6k (OpenMP variant).
6111#[doc(hidden)]
6112pub fn partial_sorting_scan_right_to_left_32s_6k_omp(
6113    t: &[SaSint],
6114    sa: &mut [SaSint],
6115    n: SaSint,
6116    buckets: &mut [SaSint],
6117    first_lms_suffix: SaSint,
6118    left_suffixes_count: SaSint,
6119    mut d: SaSint,
6120    threads: SaSint,
6121    thread_state: &mut [ThreadState],
6122) -> SaSint {
6123    let scan_start = left_suffixes_count as FastSint + 1;
6124    let scan_end = n as FastSint - first_lms_suffix as FastSint;
6125    if threads == 1 || (scan_end - scan_start) < 65_536 {
6126        return partial_sorting_scan_right_to_left_32s_6k(
6127            t,
6128            sa,
6129            buckets,
6130            d,
6131            scan_start,
6132            scan_end - scan_start,
6133        );
6134    }
6135    if thread_state.is_empty() {
6136        return partial_sorting_scan_right_to_left_32s_6k(
6137            t,
6138            sa,
6139            buckets,
6140            d,
6141            scan_start,
6142            scan_end - scan_start,
6143        );
6144    }
6145
6146    let threads_usize = usize::try_from(threads)
6147        .expect("threads must be non-negative")
6148        .max(1);
6149    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
6150    let mut block_start = scan_end - 1;
6151    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
6152        .expect("block span must fit FastSint");
6153    while block_start >= scan_start {
6154        let mut block_end = block_start - block_span;
6155        if block_end < scan_start {
6156            block_end = scan_start - 1;
6157        }
6158
6159        d = partial_sorting_scan_right_to_left_32s_6k_block_omp(
6160            t,
6161            sa,
6162            buckets,
6163            d,
6164            &mut cache,
6165            block_end + 1,
6166            block_start - block_end,
6167            threads,
6168        );
6169
6170        if block_end < scan_start {
6171            break;
6172        }
6173        block_start = block_end;
6174    }
6175
6176    d
6177}
6178
6179/// Internal helper: partial sorting scan right to left 32s 4k (OpenMP variant).
6180#[doc(hidden)]
6181pub fn partial_sorting_scan_right_to_left_32s_4k_omp(
6182    t: &[SaSint],
6183    sa: &mut [SaSint],
6184    n: SaSint,
6185    k: SaSint,
6186    buckets: &mut [SaSint],
6187    mut d: SaSint,
6188    threads: SaSint,
6189    thread_state: &mut [ThreadState],
6190) -> SaSint {
6191    if threads == 1 || n < 65_536 {
6192        return partial_sorting_scan_right_to_left_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
6193    }
6194    if thread_state.is_empty() {
6195        return partial_sorting_scan_right_to_left_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
6196    }
6197    let threads_usize = usize::try_from(threads)
6198        .expect("threads must be non-negative")
6199        .max(1);
6200    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
6201    let mut block_start = FastSint::try_from(n).expect("n must fit FastSint") - 1;
6202    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
6203        .expect("block span must fit FastSint");
6204    while block_start >= 0 {
6205        let mut block_end = block_start - block_span;
6206        if block_end < 0 {
6207            block_end = -1;
6208        }
6209
6210        d = partial_sorting_scan_right_to_left_32s_4k_block_omp(
6211            t,
6212            sa,
6213            k,
6214            buckets,
6215            d,
6216            &mut cache,
6217            block_end + 1,
6218            block_start - block_end,
6219            threads,
6220        );
6221
6222        if block_end < 0 {
6223            break;
6224        }
6225        block_start = block_end;
6226    }
6227
6228    d
6229}
6230
6231/// Internal helper: partial sorting scan right to left 32s 1k (OpenMP variant).
6232#[doc(hidden)]
6233pub fn partial_sorting_scan_right_to_left_32s_1k_omp(
6234    t: &[SaSint],
6235    sa: &mut [SaSint],
6236    n: SaSint,
6237    buckets: &mut [SaSint],
6238    threads: SaSint,
6239    thread_state: &mut [ThreadState],
6240) {
6241    if threads == 1 || n < 65_536 {
6242        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, 0, n as FastSint);
6243        return;
6244    }
6245    if thread_state.is_empty() {
6246        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, 0, n as FastSint);
6247        return;
6248    }
6249
6250    let threads_usize = usize::try_from(threads)
6251        .expect("threads must be non-negative")
6252        .max(1);
6253    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
6254    let mut block_start = FastSint::try_from(n).expect("n must fit FastSint") - 1;
6255    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
6256        .expect("block span must fit FastSint");
6257    while block_start >= 0 {
6258        let mut block_end = block_start - block_span;
6259        if block_end < 0 {
6260            block_end = -1;
6261        }
6262
6263        partial_sorting_scan_right_to_left_32s_1k_block_omp(
6264            t,
6265            sa,
6266            buckets,
6267            &mut cache,
6268            block_end + 1,
6269            block_start - block_end,
6270            threads,
6271        );
6272
6273        if block_end < 0 {
6274            break;
6275        }
6276        block_start = block_end;
6277    }
6278}
6279
6280/// Internal helper: partial sorting gather lms suffixes 32s 4k.
6281#[doc(hidden)]
6282pub fn partial_sorting_gather_lms_suffixes_32s_4k(
6283    sa: &mut [SaSint],
6284    omp_block_start: FastSint,
6285    omp_block_size: FastSint,
6286) -> FastSint {
6287    if omp_block_size <= 0 {
6288        return omp_block_start;
6289    }
6290
6291    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6292    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6293    let mut l = start;
6294
6295    for i in start..start + size {
6296        let s = sa[i] as SaUint;
6297        sa[l] = ((s.wrapping_sub(SUFFIX_GROUP_MARKER as SaUint)) & !(SUFFIX_GROUP_MARKER as SaUint))
6298            as SaSint;
6299        l += usize::from((s as SaSint) < 0);
6300    }
6301
6302    l as FastSint
6303}
6304
6305/// Internal helper: partial sorting gather lms suffixes 32s 1k.
6306#[doc(hidden)]
6307pub fn partial_sorting_gather_lms_suffixes_32s_1k(
6308    sa: &mut [SaSint],
6309    omp_block_start: FastSint,
6310    omp_block_size: FastSint,
6311) -> FastSint {
6312    if omp_block_size <= 0 {
6313        return omp_block_start;
6314    }
6315
6316    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6317    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6318    let mut l = start;
6319
6320    for i in start..start + size {
6321        let s = sa[i];
6322        sa[l] = s & SAINT_MAX;
6323        l += usize::from(s < 0);
6324    }
6325
6326    l as FastSint
6327}
6328
6329/// Internal helper: partial sorting gather lms suffixes 32s 4k (OpenMP variant).
6330#[doc(hidden)]
6331pub fn partial_sorting_gather_lms_suffixes_32s_4k_omp(
6332    sa: &mut [SaSint],
6333    n: SaSint,
6334    threads: SaSint,
6335    thread_state: &mut [ThreadState],
6336) {
6337    let n_usize = usize::try_from(n).expect("n must be non-negative");
6338    let omp_num_threads = if threads > 1 && n >= 65_536 {
6339        usize::try_from(threads)
6340            .expect("threads must be non-negative")
6341            .min(thread_state.len())
6342            .max(1)
6343    } else {
6344        1
6345    };
6346
6347    if omp_num_threads == 1 {
6348        let _ = partial_sorting_gather_lms_suffixes_32s_4k(sa, 0, n as FastSint);
6349        return;
6350    }
6351
6352    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
6353    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
6354        let block_start = thread_num * omp_block_stride;
6355        let block_size = if thread_num + 1 < omp_num_threads {
6356            omp_block_stride
6357        } else {
6358            n_usize - block_start
6359        };
6360        state.position = block_start as FastSint;
6361        state.count = partial_sorting_gather_lms_suffixes_32s_4k(
6362            sa,
6363            block_start as FastSint,
6364            block_size as FastSint,
6365        ) - block_start as FastSint;
6366    }
6367
6368    let mut position = 0usize;
6369    for (thread_num, state) in thread_state.iter().take(omp_num_threads).enumerate() {
6370        let count = usize::try_from(state.count).expect("count must be non-negative");
6371        let src = usize::try_from(state.position).expect("position must be non-negative");
6372        if thread_num > 0 && count > 0 {
6373            sa.copy_within(src..src + count, position);
6374        }
6375        position += count;
6376    }
6377}
6378
6379/// Internal helper: partial sorting gather lms suffixes 32s 1k (OpenMP variant).
6380#[doc(hidden)]
6381pub fn partial_sorting_gather_lms_suffixes_32s_1k_omp(
6382    sa: &mut [SaSint],
6383    n: SaSint,
6384    threads: SaSint,
6385    thread_state: &mut [ThreadState],
6386) {
6387    let n_usize = usize::try_from(n).expect("n must be non-negative");
6388    let omp_num_threads = if threads > 1 && n >= 65_536 {
6389        usize::try_from(threads)
6390            .expect("threads must be non-negative")
6391            .min(thread_state.len())
6392            .max(1)
6393    } else {
6394        1
6395    };
6396
6397    if omp_num_threads == 1 {
6398        let _ = partial_sorting_gather_lms_suffixes_32s_1k(sa, 0, n as FastSint);
6399        return;
6400    }
6401
6402    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
6403    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
6404        let block_start = thread_num * omp_block_stride;
6405        let block_size = if thread_num + 1 < omp_num_threads {
6406            omp_block_stride
6407        } else {
6408            n_usize - block_start
6409        };
6410        state.position = block_start as FastSint;
6411        state.count = partial_sorting_gather_lms_suffixes_32s_1k(
6412            sa,
6413            block_start as FastSint,
6414            block_size as FastSint,
6415        ) - block_start as FastSint;
6416    }
6417
6418    let mut position = 0usize;
6419    for (thread_num, state) in thread_state.iter().take(omp_num_threads).enumerate() {
6420        let count = usize::try_from(state.count).expect("count must be non-negative");
6421        let src = usize::try_from(state.position).expect("position must be non-negative");
6422        if thread_num > 0 && count > 0 {
6423            sa.copy_within(src..src + count, position);
6424        }
6425        position += count;
6426    }
6427}
6428
6429/// Internal helper: induce partial order 8u (OpenMP variant).
6430#[doc(hidden)]
6431pub fn induce_partial_order_8u_omp(
6432    t: &[u8],
6433    sa: &mut [SaSint],
6434    n: SaSint,
6435    k: SaSint,
6436    flags: SaSint,
6437    buckets: &mut [SaSint],
6438    first_lms_suffix: SaSint,
6439    left_suffixes_count: SaSint,
6440    threads: SaSint,
6441    thread_state: &mut [ThreadState],
6442) {
6443    buckets[2 * ALPHABET_SIZE..4 * ALPHABET_SIZE].fill(0);
6444
6445    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
6446        let left = 4 * ALPHABET_SIZE + buckets_index2(0, 1);
6447        let right = 4 * ALPHABET_SIZE + buckets_index2(1, 1);
6448        buckets[left] = buckets[right] - 1;
6449        flip_suffix_markers_omp(sa, buckets[left], threads);
6450    }
6451
6452    let d = partial_sorting_scan_left_to_right_8u_omp(
6453        t,
6454        sa,
6455        n,
6456        k,
6457        buckets,
6458        left_suffixes_count,
6459        0,
6460        threads,
6461        thread_state,
6462    );
6463    partial_sorting_shift_markers_8u_omp(sa, n, buckets, threads);
6464
6465    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
6466        partial_gsa_scan_right_to_left_8u_omp(
6467            t,
6468            sa,
6469            n,
6470            k,
6471            buckets,
6472            first_lms_suffix,
6473            left_suffixes_count,
6474            d,
6475            threads,
6476            thread_state,
6477        );
6478
6479        if t[usize::try_from(first_lms_suffix).expect("first_lms_suffix must be non-negative")] == 0
6480        {
6481            let count = usize::try_from(buckets[buckets_index2(1, 1)] - 1)
6482                .expect("count must be non-negative");
6483            sa.copy_within(0..count, 1);
6484            sa[0] = first_lms_suffix | SAINT_MIN;
6485        }
6486
6487        buckets[buckets_index2(0, 1)] = 0;
6488    } else {
6489        partial_sorting_scan_right_to_left_8u_omp(
6490            t,
6491            sa,
6492            n,
6493            k,
6494            buckets,
6495            first_lms_suffix,
6496            left_suffixes_count,
6497            d,
6498            threads,
6499            thread_state,
6500        );
6501    }
6502}
6503
6504/// Internal helper: induce partial order 32s 6k (OpenMP variant).
6505#[doc(hidden)]
6506pub fn induce_partial_order_32s_6k_omp(
6507    t: &[SaSint],
6508    sa: &mut [SaSint],
6509    n: SaSint,
6510    k: SaSint,
6511    buckets: &mut [SaSint],
6512    first_lms_suffix: SaSint,
6513    left_suffixes_count: SaSint,
6514    threads: SaSint,
6515    thread_state: &mut [ThreadState],
6516) {
6517    let d = partial_sorting_scan_left_to_right_32s_6k_omp(
6518        t,
6519        sa,
6520        n,
6521        buckets,
6522        left_suffixes_count,
6523        0,
6524        threads,
6525        thread_state,
6526    );
6527    partial_sorting_shift_markers_32s_6k_omp(sa, k, buckets, threads);
6528    partial_sorting_shift_buckets_32s_6k(k, buckets);
6529    let _ = partial_sorting_scan_right_to_left_32s_6k_omp(
6530        t,
6531        sa,
6532        n,
6533        buckets,
6534        first_lms_suffix,
6535        left_suffixes_count,
6536        d,
6537        threads,
6538        thread_state,
6539    );
6540}
6541
6542/// Internal helper: induce partial order 32s 4k (OpenMP variant).
6543#[doc(hidden)]
6544pub fn induce_partial_order_32s_4k_omp(
6545    t: &[SaSint],
6546    sa: &mut [SaSint],
6547    n: SaSint,
6548    k: SaSint,
6549    buckets: &mut [SaSint],
6550    threads: SaSint,
6551    thread_state: &mut [ThreadState],
6552) {
6553    let zero_len = 2 * usize::try_from(k).expect("k must be non-negative");
6554    buckets[..zero_len].fill(0);
6555
6556    let d = partial_sorting_scan_left_to_right_32s_4k_omp(
6557        t,
6558        sa,
6559        n,
6560        k,
6561        buckets,
6562        0,
6563        threads,
6564        thread_state,
6565    );
6566    partial_sorting_shift_markers_32s_4k(sa, n);
6567    let _ = partial_sorting_scan_right_to_left_32s_4k_omp(
6568        t,
6569        sa,
6570        n,
6571        k,
6572        buckets,
6573        d,
6574        threads,
6575        thread_state,
6576    );
6577    partial_sorting_gather_lms_suffixes_32s_4k_omp(sa, n, threads, thread_state);
6578}
6579
6580/// Internal helper: induce partial order 32s 2k (OpenMP variant).
6581#[doc(hidden)]
6582pub fn induce_partial_order_32s_2k_omp(
6583    t: &[SaSint],
6584    sa: &mut [SaSint],
6585    n: SaSint,
6586    k: SaSint,
6587    buckets: &mut [SaSint],
6588    threads: SaSint,
6589    thread_state: &mut [ThreadState],
6590) {
6591    let k_usize = usize::try_from(k).expect("k must be non-negative");
6592    let (left, right) = buckets.split_at_mut(k_usize);
6593    partial_sorting_scan_left_to_right_32s_1k_omp(t, sa, n, right, threads, thread_state);
6594    partial_sorting_scan_right_to_left_32s_1k_omp(t, sa, n, left, threads, thread_state);
6595    partial_sorting_gather_lms_suffixes_32s_1k_omp(sa, n, threads, thread_state);
6596}
6597
6598/// Internal helper: induce partial order 32s 1k (OpenMP variant).
6599#[doc(hidden)]
6600pub fn induce_partial_order_32s_1k_omp(
6601    t: &[SaSint],
6602    sa: &mut [SaSint],
6603    n: SaSint,
6604    k: SaSint,
6605    buckets: &mut [SaSint],
6606    threads: SaSint,
6607    thread_state: &mut [ThreadState],
6608) {
6609    count_suffixes_32s(t, n, k, buckets);
6610    initialize_buckets_start_32s_1k(k, buckets);
6611    partial_sorting_scan_left_to_right_32s_1k_omp(t, sa, n, buckets, threads, thread_state);
6612
6613    count_suffixes_32s(t, n, k, buckets);
6614    initialize_buckets_end_32s_1k(k, buckets);
6615    partial_sorting_scan_right_to_left_32s_1k_omp(t, sa, n, buckets, threads, thread_state);
6616
6617    partial_sorting_gather_lms_suffixes_32s_1k_omp(sa, n, threads, thread_state);
6618}
6619
6620/// Internal helper: renumber lms suffixes 8u.
6621#[doc(hidden)]
6622pub fn renumber_lms_suffixes_8u(
6623    sa: &mut [SaSint],
6624    m: SaSint,
6625    mut name: SaSint,
6626    omp_block_start: FastSint,
6627    omp_block_size: FastSint,
6628) -> SaSint {
6629    if omp_block_size <= 0 {
6630        return name;
6631    }
6632
6633    let m_usize = usize::try_from(m).expect("m must be non-negative");
6634    let (sa_head, sam) = sa.split_at_mut(m_usize);
6635    let mut i = omp_block_start;
6636    let mut j = omp_block_start + omp_block_size - 64 - 3;
6637
6638    while i < j {
6639        let i0 = i as usize;
6640        let p0 = sa_head[i0];
6641        let d0 = ((p0 & SAINT_MAX) >> 1) as usize;
6642        sam[d0] = name | SAINT_MIN;
6643        name += SaSint::from(p0 < 0);
6644
6645        let p1 = sa_head[i0 + 1];
6646        let d1 = ((p1 & SAINT_MAX) >> 1) as usize;
6647        sam[d1] = name | SAINT_MIN;
6648        name += SaSint::from(p1 < 0);
6649
6650        let p2 = sa_head[i0 + 2];
6651        let d2 = ((p2 & SAINT_MAX) >> 1) as usize;
6652        sam[d2] = name | SAINT_MIN;
6653        name += SaSint::from(p2 < 0);
6654
6655        let p3 = sa_head[i0 + 3];
6656        let d3 = ((p3 & SAINT_MAX) >> 1) as usize;
6657        sam[d3] = name | SAINT_MIN;
6658        name += SaSint::from(p3 < 0);
6659
6660        i += 4;
6661    }
6662
6663    j += 64 + 3;
6664    while i < j {
6665        let p = sa_head[i as usize];
6666        let d = ((p & SAINT_MAX) >> 1) as usize;
6667        sam[d] = name | SAINT_MIN;
6668        name += SaSint::from(p < 0);
6669        i += 1;
6670    }
6671
6672    name
6673}
6674
6675/// Internal helper: gather marked lms suffixes.
6676#[doc(hidden)]
6677pub fn gather_marked_lms_suffixes(
6678    sa: &mut [SaSint],
6679    m: SaSint,
6680    l: FastSint,
6681    omp_block_start: FastSint,
6682    omp_block_size: FastSint,
6683) -> FastSint {
6684    if omp_block_size <= 0 {
6685        return l;
6686    }
6687
6688    let mut l = l - 1;
6689    let mut i = m as FastSint + omp_block_start + omp_block_size - 1;
6690    let mut j = m as FastSint + omp_block_start + 3;
6691
6692    while i >= j {
6693        let i0 = i as usize;
6694        let s0 = sa[i0];
6695        sa[l as usize] = s0 & SAINT_MAX;
6696        l -= FastSint::from(s0 < 0);
6697
6698        let s1 = sa[i0 - 1];
6699        sa[l as usize] = s1 & SAINT_MAX;
6700        l -= FastSint::from(s1 < 0);
6701
6702        let s2 = sa[i0 - 2];
6703        sa[l as usize] = s2 & SAINT_MAX;
6704        l -= FastSint::from(s2 < 0);
6705
6706        let s3 = sa[i0 - 3];
6707        sa[l as usize] = s3 & SAINT_MAX;
6708        l -= FastSint::from(s3 < 0);
6709
6710        i -= 4;
6711    }
6712
6713    j -= 3;
6714    while i >= j {
6715        let s = sa[i as usize];
6716        sa[l as usize] = s & SAINT_MAX;
6717        l -= FastSint::from(s < 0);
6718        i -= 1;
6719    }
6720
6721    l + 1
6722}
6723
6724/// Internal helper: renumber lms suffixes 8u (OpenMP variant).
6725#[doc(hidden)]
6726pub fn renumber_lms_suffixes_8u_omp(
6727    sa: &mut [SaSint],
6728    m: SaSint,
6729    threads: SaSint,
6730    thread_state: &mut [ThreadState],
6731) -> SaSint {
6732    let omp_num_threads = if threads > 1 && m >= 65_536 {
6733        usize::try_from(threads)
6734            .expect("threads must be non-negative")
6735            .min(thread_state.len())
6736            .max(1)
6737    } else {
6738        1
6739    };
6740    let omp_block_stride = (m as FastSint / omp_num_threads as FastSint) & !15;
6741
6742    let name = if omp_num_threads == 1 {
6743        renumber_lms_suffixes_8u(sa, m, 0, 0, m as FastSint)
6744    } else {
6745        {
6746            let sa_ro: &[SaSint] = sa;
6747            run_rayon_with_threads(omp_num_threads, || {
6748                thread_state[..omp_num_threads]
6749                    .par_iter_mut()
6750                    .enumerate()
6751                    .for_each(|(omp_thread_num, state)| {
6752                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6753                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6754                            omp_block_stride
6755                        } else {
6756                            m as FastSint - omp_block_start
6757                        };
6758                        state.count =
6759                            count_negative_marked_suffixes(sa_ro, omp_block_start, omp_block_size)
6760                                as FastSint;
6761                    });
6762            });
6763        }
6764
6765        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
6766            .iter()
6767            .map(|s| s.count)
6768            .collect();
6769        let name = counts.iter().sum::<FastSint>() as SaSint;
6770
6771        {
6772            let sa_ptr = SyncMutPtr::new(sa);
6773            let counts_ref: &[FastSint] = &counts;
6774            run_rayon_with_threads(omp_num_threads, || {
6775                (0..omp_num_threads)
6776                    .into_par_iter()
6777                    .for_each(|omp_thread_num| {
6778                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6779                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6780                            omp_block_stride
6781                        } else {
6782                            m as FastSint - omp_block_start
6783                        };
6784
6785                        let mut count: FastSint = 0;
6786                        for t in 0..omp_thread_num {
6787                            count += counts_ref[t];
6788                        }
6789
6790                        // SAFETY: each thread writes only to sa[omp_block_start..+omp_block_size].
6791                        let sa = unsafe { sa_ptr.as_slice() };
6792                        let _ = renumber_lms_suffixes_8u(
6793                            sa,
6794                            m,
6795                            count as SaSint,
6796                            omp_block_start,
6797                            omp_block_size,
6798                        );
6799                    });
6800            });
6801        }
6802        name
6803    };
6804
6805    name
6806}
6807
6808/// Internal helper: gather marked lms suffixes (OpenMP variant).
6809#[doc(hidden)]
6810pub fn gather_marked_lms_suffixes_omp(
6811    sa: &mut [SaSint],
6812    n: SaSint,
6813    m: SaSint,
6814    fs: SaSint,
6815    threads: SaSint,
6816    thread_state: &mut [ThreadState],
6817) {
6818    let n_fast = n as FastSint;
6819    let m_fast = m as FastSint;
6820    let omp_num_threads = if threads > 1 && n >= 131_072 {
6821        usize::try_from(threads)
6822            .expect("threads must be non-negative")
6823            .min(thread_state.len())
6824            .max(1)
6825    } else {
6826        1
6827    };
6828    let omp_block_stride = ((n_fast >> 1) / omp_num_threads as FastSint) & !15;
6829
6830    if omp_num_threads == 1 {
6831        let _ = gather_marked_lms_suffixes(sa, m, n_fast + fs as FastSint, 0, n_fast >> 1);
6832    } else {
6833        {
6834            let sa_ptr = SyncMutPtr::new(sa);
6835            run_rayon_with_threads(omp_num_threads, || {
6836                thread_state[..omp_num_threads]
6837                    .par_iter_mut()
6838                    .enumerate()
6839                    .for_each(|(omp_thread_num, state)| {
6840                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6841                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6842                            omp_block_stride
6843                        } else {
6844                            (n_fast >> 1) - omp_block_start
6845                        };
6846
6847                        // SAFETY: per-thread disjoint sa block.
6848                        let sa = unsafe { sa_ptr.as_slice() };
6849                        if omp_thread_num < omp_num_threads - 1 {
6850                            state.position = gather_marked_lms_suffixes(
6851                                sa,
6852                                m,
6853                                m_fast + omp_block_start + omp_block_size,
6854                                omp_block_start,
6855                                omp_block_size,
6856                            );
6857                            state.count =
6858                                m_fast + omp_block_start + omp_block_size - state.position;
6859                        } else {
6860                            state.position = gather_marked_lms_suffixes(
6861                                sa,
6862                                m,
6863                                n_fast + fs as FastSint,
6864                                omp_block_start,
6865                                omp_block_size,
6866                            );
6867                            state.count = n_fast + fs as FastSint - state.position;
6868                        }
6869                    });
6870            });
6871        }
6872
6873        let mut position = n_fast + fs as FastSint;
6874        for t in (0..omp_num_threads).rev() {
6875            position -= thread_state[t].count;
6876            if t + 1 != omp_num_threads && thread_state[t].count > 0 {
6877                let src = usize::try_from(thread_state[t].position)
6878                    .expect("position must be non-negative");
6879                let len =
6880                    usize::try_from(thread_state[t].count).expect("count must be non-negative");
6881                let dst = usize::try_from(position).expect("position must be non-negative");
6882                sa.copy_within(src..src + len, dst);
6883            }
6884        }
6885    }
6886}
6887
6888/// Internal helper: renumber and gather lms suffixes (OpenMP variant).
6889#[doc(hidden)]
6890pub fn renumber_and_gather_lms_suffixes_omp(
6891    sa: &mut [SaSint],
6892    n: SaSint,
6893    m: SaSint,
6894    fs: SaSint,
6895    threads: SaSint,
6896    thread_state: &mut [ThreadState],
6897) -> SaSint {
6898    let m_usize = usize::try_from(m).expect("m must be non-negative");
6899    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
6900    sa[m_usize..m_usize + half_n].fill(0);
6901
6902    let name = renumber_lms_suffixes_8u_omp(sa, m, threads, thread_state);
6903    if name < m {
6904        gather_marked_lms_suffixes_omp(sa, n, m, fs, threads, thread_state);
6905    } else {
6906        let mut i = 0;
6907        while i < m_usize {
6908            sa[i] &= SAINT_MAX;
6909            i += 1;
6910        }
6911    }
6912
6913    name
6914}
6915
6916/// Internal helper: renumber distinct lms suffixes 32s 4k.
6917#[doc(hidden)]
6918pub fn renumber_distinct_lms_suffixes_32s_4k(
6919    sa: &mut [SaSint],
6920    m: SaSint,
6921    mut name: SaSint,
6922    omp_block_start: FastSint,
6923    omp_block_size: FastSint,
6924) -> SaSint {
6925    if omp_block_size <= 0 {
6926        return name;
6927    }
6928
6929    let prefetch_distance = 64usize;
6930    let m_usize = usize::try_from(m).expect("m must be non-negative");
6931    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6932    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6933    let (sa_head, sam) = sa.split_at_mut(m_usize);
6934    let mut i = start;
6935    let mut j = start
6936        .saturating_add(size)
6937        .saturating_sub(prefetch_distance + 3);
6938    let mut p0;
6939    let mut p1;
6940    let mut p2;
6941    let mut p3 = 0;
6942
6943    while i < j {
6944        p0 = sa_head[i];
6945        sa_head[i] = p0 & SAINT_MAX;
6946        sam[(sa_head[i] >> 1) as usize] = name | (p0 & p3 & SAINT_MIN);
6947        name += SaSint::from(p0 < 0);
6948
6949        p1 = sa_head[i + 1];
6950        sa_head[i + 1] = p1 & SAINT_MAX;
6951        sam[(sa_head[i + 1] >> 1) as usize] = name | (p1 & p0 & SAINT_MIN);
6952        name += SaSint::from(p1 < 0);
6953
6954        p2 = sa_head[i + 2];
6955        sa_head[i + 2] = p2 & SAINT_MAX;
6956        sam[(sa_head[i + 2] >> 1) as usize] = name | (p2 & p1 & SAINT_MIN);
6957        name += SaSint::from(p2 < 0);
6958
6959        p3 = sa_head[i + 3];
6960        sa_head[i + 3] = p3 & SAINT_MAX;
6961        sam[(sa_head[i + 3] >> 1) as usize] = name | (p3 & p2 & SAINT_MIN);
6962        name += SaSint::from(p3 < 0);
6963
6964        i += 4;
6965    }
6966
6967    j = start + size;
6968    while i < j {
6969        p2 = p3;
6970        p3 = sa_head[i];
6971        sa_head[i] = p3 & SAINT_MAX;
6972        sam[(sa_head[i] >> 1) as usize] = name | (p3 & p2 & SAINT_MIN);
6973        name += SaSint::from(p3 < 0);
6974        i += 1;
6975    }
6976
6977    name
6978}
6979
6980/// Internal helper: mark distinct lms suffixes 32s.
6981#[doc(hidden)]
6982pub fn mark_distinct_lms_suffixes_32s(
6983    sa: &mut [SaSint],
6984    m: SaSint,
6985    omp_block_start: FastSint,
6986    omp_block_size: FastSint,
6987) {
6988    if omp_block_size <= 0 {
6989        return;
6990    }
6991
6992    let m_usize = usize::try_from(m).expect("m must be non-negative");
6993    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6994    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6995    let mut i = m_usize + start;
6996    let mut j = m_usize + start + size.saturating_sub(3);
6997    let mut p3 = 0;
6998
6999    while i < j {
7000        let mut p0 = sa[i];
7001        sa[i] = p0 & (p3 | SAINT_MAX);
7002        p0 = if p0 == 0 { p3 } else { p0 };
7003
7004        let mut p1 = sa[i + 1];
7005        sa[i + 1] = p1 & (p0 | SAINT_MAX);
7006        p1 = if p1 == 0 { p0 } else { p1 };
7007
7008        let mut p2 = sa[i + 2];
7009        sa[i + 2] = p2 & (p1 | SAINT_MAX);
7010        p2 = if p2 == 0 { p1 } else { p2 };
7011
7012        p3 = sa[i + 3];
7013        sa[i + 3] = p3 & (p2 | SAINT_MAX);
7014        p3 = if p3 == 0 { p2 } else { p3 };
7015
7016        i += 4;
7017    }
7018
7019    j = m_usize + start + size;
7020    while i < j {
7021        let p2 = p3;
7022        p3 = sa[i];
7023        sa[i] = p3 & (p2 | SAINT_MAX);
7024        p3 = if p3 == 0 { p2 } else { p3 };
7025        i += 1;
7026    }
7027}
7028
7029/// Internal helper: clamp lms suffixes length 32s.
7030#[doc(hidden)]
7031pub fn clamp_lms_suffixes_length_32s(
7032    sa: &mut [SaSint],
7033    m: SaSint,
7034    omp_block_start: FastSint,
7035    omp_block_size: FastSint,
7036) {
7037    if omp_block_size <= 0 {
7038        return;
7039    }
7040
7041    let m_usize = usize::try_from(m).expect("m must be non-negative");
7042    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7043    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7044    let mut i = m_usize + start;
7045    let mut j = m_usize + start + size.saturating_sub(3);
7046
7047    while i < j {
7048        let s0 = sa[i];
7049        sa[i] = if s0 < 0 { s0 } else { 0 } & SAINT_MAX;
7050
7051        let s1 = sa[i + 1];
7052        sa[i + 1] = if s1 < 0 { s1 } else { 0 } & SAINT_MAX;
7053
7054        let s2 = sa[i + 2];
7055        sa[i + 2] = if s2 < 0 { s2 } else { 0 } & SAINT_MAX;
7056
7057        let s3 = sa[i + 3];
7058        sa[i + 3] = if s3 < 0 { s3 } else { 0 } & SAINT_MAX;
7059
7060        i += 4;
7061    }
7062
7063    j = m_usize + start + size;
7064    while i < j {
7065        let s = sa[i];
7066        sa[i] = if s < 0 { s } else { 0 } & SAINT_MAX;
7067        i += 1;
7068    }
7069}
7070
7071/// Internal helper: renumber distinct lms suffixes 32s 4k (OpenMP variant).
7072#[doc(hidden)]
7073pub fn renumber_distinct_lms_suffixes_32s_4k_omp(
7074    sa: &mut [SaSint],
7075    m: SaSint,
7076    threads: SaSint,
7077    thread_state: &mut [ThreadState],
7078) -> SaSint {
7079    let m_usize = usize::try_from(m).expect("m must be non-negative");
7080    let omp_num_threads = if threads > 1 && m >= 65_536 {
7081        usize::try_from(threads)
7082            .expect("threads must be non-negative")
7083            .min(thread_state.len())
7084            .max(1)
7085    } else {
7086        1
7087    };
7088    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
7089
7090    let name = if omp_num_threads == 1 {
7091        let omp_block_start = 0usize;
7092        let omp_block_size = m_usize - omp_block_start;
7093        renumber_distinct_lms_suffixes_32s_4k(
7094            sa,
7095            m,
7096            1,
7097            omp_block_start as FastSint,
7098            omp_block_size as FastSint,
7099        )
7100    } else {
7101        {
7102            let sa_ro: &[SaSint] = sa;
7103            run_rayon_with_threads(omp_num_threads, || {
7104                thread_state[..omp_num_threads]
7105                    .par_iter_mut()
7106                    .enumerate()
7107                    .for_each(|(omp_thread_num, state)| {
7108                        let omp_block_start = omp_thread_num * omp_block_stride;
7109                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7110                            omp_block_stride
7111                        } else {
7112                            m_usize - omp_block_start
7113                        };
7114                        state.count = count_negative_marked_suffixes(
7115                            sa_ro,
7116                            omp_block_start as FastSint,
7117                            omp_block_size as FastSint,
7118                        ) as FastSint;
7119                    });
7120            });
7121        }
7122
7123        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
7124            .iter()
7125            .map(|s| s.count)
7126            .collect();
7127        let name = (1 + counts.iter().sum::<FastSint>()) as SaSint;
7128
7129        {
7130            let sa_ptr = SyncMutPtr::new(sa);
7131            let counts_ref: &[FastSint] = &counts;
7132            run_rayon_with_threads(omp_num_threads, || {
7133                (0..omp_num_threads)
7134                    .into_par_iter()
7135                    .for_each(|omp_thread_num| {
7136                        let omp_block_start = omp_thread_num * omp_block_stride;
7137                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7138                            omp_block_stride
7139                        } else {
7140                            m_usize - omp_block_start
7141                        };
7142
7143                        let mut count: FastSint = 1;
7144                        for t in 0..omp_thread_num {
7145                            count += counts_ref[t];
7146                        }
7147
7148                        // SAFETY: per-thread disjoint sa block.
7149                        let sa = unsafe { sa_ptr.as_slice() };
7150                        let _ = renumber_distinct_lms_suffixes_32s_4k(
7151                            sa,
7152                            m,
7153                            count as SaSint,
7154                            omp_block_start as FastSint,
7155                            omp_block_size as FastSint,
7156                        );
7157                    });
7158            });
7159        }
7160        name
7161    };
7162
7163    name - 1
7164}
7165
7166/// Internal helper: mark distinct lms suffixes 32s (OpenMP variant).
7167#[doc(hidden)]
7168pub fn mark_distinct_lms_suffixes_32s_omp(
7169    sa: &mut [SaSint],
7170    n: SaSint,
7171    m: SaSint,
7172    threads: SaSint,
7173) {
7174    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
7175    let omp_num_threads = if threads > 1 && n >= 131_072 {
7176        usize::try_from(threads)
7177            .expect("threads must be non-negative")
7178            .max(1)
7179    } else {
7180        1
7181    };
7182    let omp_block_stride = (half_n / omp_num_threads) & !15usize;
7183
7184    {
7185        let sa_ptr = SyncMutPtr::new(sa);
7186        run_rayon_with_threads(omp_num_threads, || {
7187            (0..omp_num_threads)
7188                .into_par_iter()
7189                .for_each(|omp_thread_num| {
7190                    let omp_block_start = omp_thread_num * omp_block_stride;
7191                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7192                        omp_block_stride
7193                    } else {
7194                        half_n - omp_block_start
7195                    };
7196                    // SAFETY: per-thread disjoint sa block.
7197                    let sa = unsafe { sa_ptr.as_slice() };
7198                    mark_distinct_lms_suffixes_32s(
7199                        sa,
7200                        m,
7201                        omp_block_start as FastSint,
7202                        omp_block_size as FastSint,
7203                    );
7204                });
7205        });
7206    }
7207}
7208
7209/// Internal helper: clamp lms suffixes length 32s (OpenMP variant).
7210#[doc(hidden)]
7211pub fn clamp_lms_suffixes_length_32s_omp(sa: &mut [SaSint], n: SaSint, m: SaSint, threads: SaSint) {
7212    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
7213    let omp_num_threads = if threads > 1 && n >= 131_072 {
7214        usize::try_from(threads)
7215            .expect("threads must be non-negative")
7216            .max(1)
7217    } else {
7218        1
7219    };
7220    let omp_block_stride = (half_n / omp_num_threads) & !15usize;
7221
7222    {
7223        let sa_ptr = SyncMutPtr::new(sa);
7224        run_rayon_with_threads(omp_num_threads, || {
7225            (0..omp_num_threads)
7226                .into_par_iter()
7227                .for_each(|omp_thread_num| {
7228                    let omp_block_start = omp_thread_num * omp_block_stride;
7229                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7230                        omp_block_stride
7231                    } else {
7232                        half_n - omp_block_start
7233                    };
7234                    // SAFETY: per-thread disjoint sa block.
7235                    let sa = unsafe { sa_ptr.as_slice() };
7236                    clamp_lms_suffixes_length_32s(
7237                        sa,
7238                        m,
7239                        omp_block_start as FastSint,
7240                        omp_block_size as FastSint,
7241                    );
7242                });
7243        });
7244    }
7245}
7246
7247/// Internal helper: renumber and mark distinct lms suffixes 32s 4k (OpenMP variant).
7248#[doc(hidden)]
7249pub fn renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
7250    sa: &mut [SaSint],
7251    n: SaSint,
7252    m: SaSint,
7253    threads: SaSint,
7254    thread_state: &mut [ThreadState],
7255) -> SaSint {
7256    let m_usize = usize::try_from(m).expect("m must be non-negative");
7257    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
7258    sa[m_usize..m_usize + half_n].fill(0);
7259
7260    let name = renumber_distinct_lms_suffixes_32s_4k_omp(sa, m, threads, thread_state);
7261    if name < m {
7262        mark_distinct_lms_suffixes_32s_omp(sa, n, m, threads);
7263    }
7264
7265    name
7266}
7267
7268/// Internal helper: renumber and mark distinct lms suffixes 32s 1k (OpenMP variant).
7269#[doc(hidden)]
7270pub fn renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(
7271    t: &[SaSint],
7272    sa: &mut [SaSint],
7273    n: SaSint,
7274    m: SaSint,
7275    threads: SaSint,
7276) -> SaSint {
7277    let m_usize = usize::try_from(m).expect("m must be non-negative");
7278    let n_usize = usize::try_from(n).expect("n must be non-negative");
7279
7280    let _ = gather_lms_suffixes_32s(t, sa, n);
7281
7282    let zero_len = n_usize
7283        .checked_sub(m_usize)
7284        .and_then(|v| v.checked_sub(m_usize))
7285        .expect("n must be at least 2*m");
7286    sa[m_usize..m_usize + zero_len].fill(0);
7287
7288    {
7289        let prefetch_distance: FastSint = 64;
7290        let mut i = n as FastSint - m as FastSint;
7291        let mut j = n as FastSint - 1 - prefetch_distance - 3;
7292
7293        while i < j {
7294            let iu = i as usize;
7295            let s0 = (sa[iu] as SaUint >> 1) as usize;
7296            let s1 = (sa[iu + 1] as SaUint >> 1) as usize;
7297            let s2 = (sa[iu + 2] as SaUint >> 1) as usize;
7298            let s3 = (sa[iu + 3] as SaUint >> 1) as usize;
7299
7300            sa[m_usize + s0] = sa[iu + 1] - sa[iu] + 1 + SAINT_MIN;
7301            sa[m_usize + s1] = sa[iu + 2] - sa[iu + 1] + 1 + SAINT_MIN;
7302            sa[m_usize + s2] = sa[iu + 3] - sa[iu + 2] + 1 + SAINT_MIN;
7303            sa[m_usize + s3] = sa[iu + 4] - sa[iu + 3] + 1 + SAINT_MIN;
7304            i += 4;
7305        }
7306
7307        j += prefetch_distance + 3;
7308        while i < j {
7309            let iu = i as usize;
7310            let s = (sa[iu] as SaUint >> 1) as usize;
7311            sa[m_usize + s] = sa[iu + 1] - sa[iu] + 1 + SAINT_MIN;
7312            i += 1;
7313        }
7314
7315        let tail = (sa[n_usize - 1] as SaUint >> 1) as usize;
7316        sa[m_usize + tail] = 1 + SAINT_MIN;
7317    }
7318
7319    clamp_lms_suffixes_length_32s_omp(sa, n, m, threads);
7320
7321    let mut name = 1;
7322    if m_usize > 0 {
7323        let (sa_head, sam) = sa.split_at_mut(m_usize);
7324        let mut i = 1usize;
7325        let prefetch_distance = 64usize;
7326        let mut j = m_usize.saturating_sub(prefetch_distance + 1);
7327        let mut p = usize::try_from(sa_head[0]).expect("suffix index must be non-negative");
7328        let mut plen = sam[p >> 1];
7329        let mut pdiff = SAINT_MIN;
7330
7331        while i < j {
7332            let q = usize::try_from(sa_head[i]).expect("suffix index must be non-negative");
7333            let qlen = sam[q >> 1];
7334            let mut qdiff = SAINT_MIN;
7335            if plen == qlen {
7336                let mut l = 0usize;
7337                while l < qlen as usize {
7338                    if t[p + l] != t[q + l] {
7339                        break;
7340                    }
7341                    l += 1;
7342                }
7343                qdiff = ((l as SaSint) - qlen) & SAINT_MIN;
7344            }
7345            sam[p >> 1] = name | (pdiff & qdiff);
7346            name += SaSint::from(qdiff < 0);
7347
7348            p = usize::try_from(sa_head[i + 1]).expect("suffix index must be non-negative");
7349            plen = sam[p >> 1];
7350            pdiff = SAINT_MIN;
7351            if qlen == plen {
7352                let mut l = 0usize;
7353                while l < plen as usize {
7354                    if t[q + l] != t[p + l] {
7355                        break;
7356                    }
7357                    l += 1;
7358                }
7359                pdiff = ((l as SaSint) - plen) & SAINT_MIN;
7360            }
7361            sam[q >> 1] = name | (qdiff & pdiff);
7362            name += SaSint::from(pdiff < 0);
7363            i += 2;
7364        }
7365
7366        j = m_usize;
7367        while i < j {
7368            let q = usize::try_from(sa_head[i]).expect("suffix index must be non-negative");
7369            let qlen = sam[q >> 1];
7370            let mut qdiff = SAINT_MIN;
7371            if plen == qlen {
7372                let mut l = 0usize;
7373                while l < plen as usize {
7374                    if t[p + l] != t[q + l] {
7375                        break;
7376                    }
7377                    l += 1;
7378                }
7379                qdiff = ((l as SaSint) - plen) & SAINT_MIN;
7380            }
7381            sam[p >> 1] = name | (pdiff & qdiff);
7382            name += SaSint::from(qdiff < 0);
7383
7384            p = q;
7385            plen = qlen;
7386            pdiff = qdiff;
7387            i += 1;
7388        }
7389
7390        sam[p >> 1] = name | pdiff;
7391        name += 1;
7392    }
7393
7394    if name <= m {
7395        mark_distinct_lms_suffixes_32s_omp(sa, n, m, threads);
7396    }
7397
7398    name - 1
7399}
7400
7401/// Internal helper: reconstruct lms suffixes.
7402#[doc(hidden)]
7403pub fn reconstruct_lms_suffixes(
7404    sa: &mut [SaSint],
7405    n: SaSint,
7406    m: SaSint,
7407    omp_block_start: FastSint,
7408    omp_block_size: FastSint,
7409) {
7410    if omp_block_size <= 0 {
7411        return;
7412    }
7413
7414    let prefetch_distance: FastSint = 64;
7415    let base = (n - m) as usize;
7416    let mut i = omp_block_start;
7417    let mut j = omp_block_start + omp_block_size - prefetch_distance - 3;
7418
7419    while i < j {
7420        let iu = i as usize;
7421        let s0 = sa[iu] as usize;
7422        let s1 = sa[iu + 1] as usize;
7423        let s2 = sa[iu + 2] as usize;
7424        let s3 = sa[iu + 3] as usize;
7425        sa[iu] = sa[base + s0];
7426        sa[iu + 1] = sa[base + s1];
7427        sa[iu + 2] = sa[base + s2];
7428        sa[iu + 3] = sa[base + s3];
7429        i += 4;
7430    }
7431
7432    j += prefetch_distance + 3;
7433    while i < j {
7434        let iu = i as usize;
7435        let s = sa[iu] as usize;
7436        sa[iu] = sa[base + s];
7437        i += 1;
7438    }
7439}
7440
7441/// Internal helper: reconstruct lms suffixes (OpenMP variant).
7442#[doc(hidden)]
7443pub fn reconstruct_lms_suffixes_omp(sa: &mut [SaSint], n: SaSint, m: SaSint, threads: SaSint) {
7444    let m_usize = usize::try_from(m).expect("m must be non-negative");
7445    let omp_num_threads = if threads > 1 && m >= 65_536 {
7446        usize::try_from(threads)
7447            .expect("threads must be non-negative")
7448            .max(1)
7449    } else {
7450        1
7451    };
7452    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
7453
7454    {
7455        let sa_ptr = SyncMutPtr::new(sa);
7456        run_rayon_with_threads(omp_num_threads, || {
7457            (0..omp_num_threads)
7458                .into_par_iter()
7459                .for_each(|omp_thread_num| {
7460                    let omp_block_start = omp_thread_num * omp_block_stride;
7461                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7462                        omp_block_stride
7463                    } else {
7464                        m_usize - omp_block_start
7465                    };
7466                    // SAFETY: per-thread disjoint sa block.
7467                    let sa = unsafe { sa_ptr.as_slice() };
7468                    reconstruct_lms_suffixes(
7469                        sa,
7470                        n,
7471                        m,
7472                        omp_block_start as FastSint,
7473                        omp_block_size as FastSint,
7474                    );
7475                });
7476        });
7477    }
7478}
7479
7480/// Internal helper: place lms suffixes interval 8u.
7481#[doc(hidden)]
7482pub fn place_lms_suffixes_interval_8u(
7483    sa: &mut [SaSint],
7484    n: SaSint,
7485    mut m: SaSint,
7486    flags: SaSint,
7487    buckets: &mut [SaSint],
7488) {
7489    let bucket_end_base = 7 * ALPHABET_SIZE;
7490    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
7491        buckets[bucket_end_base] -= 1;
7492    }
7493
7494    let mut j = usize::try_from(n).expect("n must be non-negative");
7495    for c in (0..ALPHABET_SIZE - 1).rev() {
7496        let l = usize::try_from(
7497            buckets[buckets_index2(c, 1) + buckets_index2(1, 0)] - buckets[buckets_index2(c, 1)],
7498        )
7499        .expect("interval length must be non-negative");
7500        if l > 0 {
7501            let i = usize::try_from(buckets[bucket_end_base + c])
7502                .expect("bucket end must be non-negative");
7503            if j > i {
7504                sa[i..j].fill(0);
7505            }
7506
7507            let new_j = i - l;
7508            let src_end = usize::try_from(m).expect("m must be non-negative");
7509            let src_start = src_end - l;
7510            sa.copy_within(src_start..src_end, new_j);
7511            m -= l as SaSint;
7512            j = new_j;
7513        }
7514    }
7515
7516    sa[..j].fill(0);
7517
7518    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
7519        buckets[bucket_end_base] += 1;
7520    }
7521}
7522
7523/// Internal helper: place lms suffixes interval 32s 4k.
7524#[doc(hidden)]
7525pub fn place_lms_suffixes_interval_32s_4k(
7526    sa: &mut [SaSint],
7527    n: SaSint,
7528    k: SaSint,
7529    mut m: SaSint,
7530    buckets: &[SaSint],
7531) {
7532    let k_usize = usize::try_from(k).expect("k must be non-negative");
7533    let bucket_end = &buckets[3 * k_usize..4 * k_usize];
7534
7535    let mut j = usize::try_from(n).expect("n must be non-negative");
7536    for c in (0..k_usize - 1).rev() {
7537        let l = usize::try_from(
7538            buckets[buckets_index2(c, 1) + buckets_index2(1, 0)] - buckets[buckets_index2(c, 1)],
7539        )
7540        .expect("interval length must be non-negative");
7541        if l > 0 {
7542            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7543            if j > i {
7544                sa[i..j].fill(0);
7545            }
7546
7547            let new_j = i - l;
7548            let src_end = usize::try_from(m).expect("m must be non-negative");
7549            let src_start = src_end - l;
7550            sa.copy_within(src_start..src_end, new_j);
7551            m -= l as SaSint;
7552            j = new_j;
7553        }
7554    }
7555
7556    sa[..j].fill(0);
7557}
7558
7559/// Internal helper: place lms suffixes interval 32s 2k.
7560#[doc(hidden)]
7561pub fn place_lms_suffixes_interval_32s_2k(
7562    sa: &mut [SaSint],
7563    n: SaSint,
7564    k: SaSint,
7565    mut m: SaSint,
7566    buckets: &[SaSint],
7567) {
7568    let k_usize = usize::try_from(k).expect("k must be non-negative");
7569    let mut j = usize::try_from(n).expect("n must be non-negative");
7570
7571    if k_usize > 1 {
7572        let mut c = buckets_index2(k_usize - 2, 0) as isize;
7573        while c >= buckets_index2(0, 0) as isize {
7574            let c_usize = c as usize;
7575            let l = usize::try_from(
7576                buckets[c_usize + buckets_index2(1, 1)] - buckets[c_usize + buckets_index2(0, 1)],
7577            )
7578            .expect("interval length must be non-negative");
7579            if l > 0 {
7580                let i =
7581                    usize::try_from(buckets[c_usize]).expect("bucket start must be non-negative");
7582                if j > i {
7583                    sa[i..j].fill(0);
7584                }
7585
7586                let new_j = i - l;
7587                let src_end = usize::try_from(m).expect("m must be non-negative");
7588                let src_start = src_end - l;
7589                sa.copy_within(src_start..src_end, new_j);
7590                m -= l as SaSint;
7591                j = new_j;
7592            }
7593            c -= buckets_index2(1, 0) as isize;
7594        }
7595    }
7596
7597    sa[..j].fill(0);
7598}
7599
7600/// Internal helper: place lms suffixes interval 32s 1k.
7601#[doc(hidden)]
7602pub fn place_lms_suffixes_interval_32s_1k(
7603    t: &[SaSint],
7604    sa: &mut [SaSint],
7605    k: SaSint,
7606    m: SaSint,
7607    buckets: &[SaSint],
7608) {
7609    let mut c = k - 1;
7610    let c_usize = usize::try_from(c).expect("k must be positive");
7611    let mut l = usize::try_from(buckets[c_usize]).expect("bucket end must be non-negative");
7612
7613    let m_usize = usize::try_from(m).expect("m must be non-negative");
7614    for i in (0..m_usize).rev() {
7615        let p = usize::try_from(sa[i]).expect("suffix index must be non-negative");
7616        let tp = t[p];
7617        if tp != c {
7618            c = tp;
7619            let bucket = usize::try_from(c).expect("bucket index must be non-negative");
7620            let bucket_pos =
7621                usize::try_from(buckets[bucket]).expect("bucket end must be non-negative");
7622            if l > bucket_pos {
7623                sa[bucket_pos..l].fill(0);
7624            }
7625            l = bucket_pos;
7626        }
7627        l -= 1;
7628        sa[l] = p as SaSint;
7629    }
7630
7631    sa[..l].fill(0);
7632}
7633
7634/// Internal helper: place lms suffixes histogram 32s 6k.
7635#[doc(hidden)]
7636pub fn place_lms_suffixes_histogram_32s_6k(
7637    sa: &mut [SaSint],
7638    n: SaSint,
7639    k: SaSint,
7640    mut m: SaSint,
7641    buckets: &[SaSint],
7642) {
7643    let k_usize = usize::try_from(k).expect("k must be non-negative");
7644    let bucket_end = &buckets[5 * k_usize..6 * k_usize];
7645
7646    let mut j = usize::try_from(n).expect("n must be non-negative");
7647    for c in (0..k_usize - 1).rev() {
7648        let l = usize::try_from(buckets[buckets_index4(c, 1)])
7649            .expect("histogram length must be non-negative");
7650        if l > 0 {
7651            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7652            if j > i {
7653                sa[i..j].fill(0);
7654            }
7655
7656            let new_j = i - l;
7657            let src_end = usize::try_from(m).expect("m must be non-negative");
7658            let src_start = src_end - l;
7659            sa.copy_within(src_start..src_end, new_j);
7660            m -= l as SaSint;
7661            j = new_j;
7662        }
7663    }
7664
7665    sa[..j].fill(0);
7666}
7667
7668/// Internal helper: place lms suffixes histogram 32s 4k.
7669#[doc(hidden)]
7670pub fn place_lms_suffixes_histogram_32s_4k(
7671    sa: &mut [SaSint],
7672    n: SaSint,
7673    k: SaSint,
7674    mut m: SaSint,
7675    buckets: &[SaSint],
7676) {
7677    let k_usize = usize::try_from(k).expect("k must be non-negative");
7678    let bucket_end = &buckets[3 * k_usize..4 * k_usize];
7679
7680    let mut j = usize::try_from(n).expect("n must be non-negative");
7681    for c in (0..k_usize - 1).rev() {
7682        let l = usize::try_from(buckets[buckets_index2(c, 1)])
7683            .expect("histogram length must be non-negative");
7684        if l > 0 {
7685            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7686            if j > i {
7687                sa[i..j].fill(0);
7688            }
7689
7690            let new_j = i - l;
7691            let src_end = usize::try_from(m).expect("m must be non-negative");
7692            let src_start = src_end - l;
7693            sa.copy_within(src_start..src_end, new_j);
7694            m -= l as SaSint;
7695            j = new_j;
7696        }
7697    }
7698
7699    sa[..j].fill(0);
7700}
7701
7702/// Internal helper: place lms suffixes histogram 32s 2k.
7703#[doc(hidden)]
7704pub fn place_lms_suffixes_histogram_32s_2k(
7705    sa: &mut [SaSint],
7706    n: SaSint,
7707    k: SaSint,
7708    mut m: SaSint,
7709    buckets: &[SaSint],
7710) {
7711    let k_usize = usize::try_from(k).expect("k must be non-negative");
7712    let mut j = usize::try_from(n).expect("n must be non-negative");
7713
7714    if k_usize > 1 {
7715        let mut c = buckets_index2(k_usize - 2, 0) as isize;
7716        while c >= buckets_index2(0, 0) as isize {
7717            let c_usize = c as usize;
7718            let l = usize::try_from(buckets[c_usize + buckets_index2(0, 1)])
7719                .expect("histogram length must be non-negative");
7720            if l > 0 {
7721                let i =
7722                    usize::try_from(buckets[c_usize]).expect("bucket start must be non-negative");
7723                if j > i {
7724                    sa[i..j].fill(0);
7725                }
7726
7727                let new_j = i - l;
7728                let src_end = usize::try_from(m).expect("m must be non-negative");
7729                let src_start = src_end - l;
7730                sa.copy_within(src_start..src_end, new_j);
7731                m -= l as SaSint;
7732                j = new_j;
7733            }
7734            c -= buckets_index2(1, 0) as isize;
7735        }
7736    }
7737
7738    sa[..j].fill(0);
7739}
7740
7741/// Internal helper: final bwt scan left to right 8u.
7742#[doc(hidden)]
7743pub fn final_bwt_scan_left_to_right_8u(
7744    t: &[u8],
7745    sa: &mut [SaSint],
7746    induction_bucket: &mut [SaSint],
7747    omp_block_start: FastSint,
7748    omp_block_size: FastSint,
7749) {
7750    if omp_block_size <= 0 {
7751        return;
7752    }
7753
7754    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7755    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7756    for i in start..start + size {
7757        let mut p = sa[i];
7758        sa[i] = p & SAINT_MAX;
7759        if p > 0 {
7760            p -= 1;
7761            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7762            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
7763            let bucket = t[p_usize] as usize;
7764            let slot = usize::try_from(induction_bucket[bucket])
7765                .expect("bucket slot must be non-negative");
7766            sa[slot] = p
7767                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7768                    << (SAINT_BIT - 1));
7769            induction_bucket[bucket] += 1;
7770        }
7771    }
7772}
7773
7774/// Internal helper: final bwt aux scan left to right 8u.
7775#[doc(hidden)]
7776pub fn final_bwt_aux_scan_left_to_right_8u(
7777    t: &[u8],
7778    sa: &mut [SaSint],
7779    rm: SaSint,
7780    i_out: &mut [SaSint],
7781    induction_bucket: &mut [SaSint],
7782    omp_block_start: FastSint,
7783    omp_block_size: FastSint,
7784) {
7785    if omp_block_size <= 0 {
7786        return;
7787    }
7788
7789    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7790    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7791    for i in start..start + size {
7792        let mut p = sa[i];
7793        sa[i] = p & SAINT_MAX;
7794        if p > 0 {
7795            p -= 1;
7796            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7797            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
7798            let bucket = t[p_usize] as usize;
7799            let slot = usize::try_from(induction_bucket[bucket])
7800                .expect("bucket slot must be non-negative");
7801            sa[slot] = p
7802                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7803                    << (SAINT_BIT - 1));
7804            induction_bucket[bucket] += 1;
7805            if (p & rm) == 0 {
7806                let out_idx =
7807                    usize::try_from(p / (rm + 1)).expect("sample index must be non-negative");
7808                i_out[out_idx] = induction_bucket[bucket];
7809            }
7810        }
7811    }
7812}
7813
7814/// Internal helper: final sorting scan left to right 8u.
7815#[doc(hidden)]
7816pub fn final_sorting_scan_left_to_right_8u(
7817    t: &[u8],
7818    sa: &mut [SaSint],
7819    induction_bucket: &mut [SaSint],
7820    omp_block_start: FastSint,
7821    omp_block_size: FastSint,
7822) {
7823    if omp_block_size <= 0 {
7824        return;
7825    }
7826
7827    let prefetch_distance = 64usize;
7828    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7829    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7830
7831    let mut i = start;
7832    let mut j = if size > prefetch_distance + 1 {
7833        start + size - (prefetch_distance + 1)
7834    } else {
7835        start
7836    };
7837    let sa_ptr = sa.as_ptr();
7838    let t_ptr = t.as_ptr();
7839    while i < j {
7840        libsais_prefetchw(sa_ptr.wrapping_add(i + 2 * prefetch_distance));
7841        let s0 = sa[i + prefetch_distance];
7842        let ts0 = if s0 > 0 { s0 as usize } else { 2 };
7843        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
7844        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(2));
7845        let s1 = sa[i + prefetch_distance + 1];
7846        let ts1 = if s1 > 0 { s1 as usize } else { 2 };
7847        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
7848        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(2));
7849
7850        let mut p0 = sa[i];
7851        sa[i] = p0 ^ SAINT_MIN;
7852        if p0 > 0 {
7853            p0 -= 1;
7854            let p0_usize = p0 as usize;
7855            let bucket0 = t[p0_usize] as usize;
7856            let slot0 = induction_bucket[bucket0] as usize;
7857            sa[slot0] = p0
7858                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] < t[p0_usize]) as SaSint)
7859                    << (SAINT_BIT - 1));
7860            induction_bucket[bucket0] += 1;
7861        }
7862
7863        let mut p1 = sa[i + 1];
7864        sa[i + 1] = p1 ^ SAINT_MIN;
7865        if p1 > 0 {
7866            p1 -= 1;
7867            let p1_usize = p1 as usize;
7868            let bucket1 = t[p1_usize] as usize;
7869            let slot1 = induction_bucket[bucket1] as usize;
7870            sa[slot1] = p1
7871                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] < t[p1_usize]) as SaSint)
7872                    << (SAINT_BIT - 1));
7873            induction_bucket[bucket1] += 1;
7874        }
7875
7876        i += 2;
7877    }
7878
7879    j = start + size;
7880    while i < j {
7881        let mut p = sa[i];
7882        sa[i] = p ^ SAINT_MIN;
7883        if p > 0 {
7884            p -= 1;
7885            let p_usize = p as usize;
7886            let bucket = t[p_usize] as usize;
7887            let slot = induction_bucket[bucket] as usize;
7888            sa[slot] = p
7889                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7890                    << (SAINT_BIT - 1));
7891            induction_bucket[bucket] += 1;
7892        }
7893        i += 1;
7894    }
7895}
7896
7897/// Internal helper: final sorting scan left to right 32s.
7898#[doc(hidden)]
7899pub fn final_sorting_scan_left_to_right_32s(
7900    t: &[SaSint],
7901    sa: &mut [SaSint],
7902    induction_bucket: &mut [SaSint],
7903    omp_block_start: FastSint,
7904    omp_block_size: FastSint,
7905) {
7906    if omp_block_size <= 0 {
7907        return;
7908    }
7909
7910    let prefetch_distance: FastSint = 64;
7911    let mut i = omp_block_start;
7912    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
7913
7914    let sa_ptr = sa.as_ptr();
7915    let t_ptr = t.as_ptr();
7916    let prefetch_distance_us = prefetch_distance as usize;
7917    while i < j {
7918        let i_us = i as usize;
7919        libsais_prefetchw(sa_ptr.wrapping_add(i_us + 3 * prefetch_distance_us));
7920        let s0 = sa[i_us + 2 * prefetch_distance_us];
7921        let ts0 = if s0 > 0 { s0 as usize } else { 1 };
7922        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
7923        let s1 = sa[i_us + 2 * prefetch_distance_us + 1];
7924        let ts1 = if s1 > 0 { s1 as usize } else { 1 };
7925        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
7926        let s2 = sa[i_us + prefetch_distance_us];
7927        if s2 > 0 {
7928            let s2u = s2 as usize;
7929            libsais_prefetchw(induction_bucket.as_ptr().wrapping_add(t[s2u - 1] as usize));
7930            libsais_prefetchr(t_ptr.wrapping_add(s2u).wrapping_sub(2));
7931        }
7932        let s3 = sa[i_us + prefetch_distance_us + 1];
7933        if s3 > 0 {
7934            let s3u = s3 as usize;
7935            libsais_prefetchw(induction_bucket.as_ptr().wrapping_add(t[s3u - 1] as usize));
7936            libsais_prefetchr(t_ptr.wrapping_add(s3u).wrapping_sub(2));
7937        }
7938
7939        let i0 = i as usize;
7940        let mut p0 = sa[i0];
7941        sa[i0] = p0 ^ SAINT_MIN;
7942        if p0 > 0 {
7943            p0 -= 1;
7944            let p0u = p0 as usize;
7945            let bucket0 = t[p0u] as usize;
7946            let slot0 = induction_bucket[bucket0] as usize;
7947            sa[slot0] = p0
7948                | ((usize::from(t[p0u - usize::from(p0 > 0)] < t[p0u]) as SaSint)
7949                    << (SAINT_BIT - 1));
7950            induction_bucket[bucket0] += 1;
7951        }
7952
7953        let i1 = (i + 1) as usize;
7954        let mut p1 = sa[i1];
7955        sa[i1] = p1 ^ SAINT_MIN;
7956        if p1 > 0 {
7957            p1 -= 1;
7958            let p1u = p1 as usize;
7959            let bucket1 = t[p1u] as usize;
7960            let slot1 = induction_bucket[bucket1] as usize;
7961            sa[slot1] = p1
7962                | ((usize::from(t[p1u - usize::from(p1 > 0)] < t[p1u]) as SaSint)
7963                    << (SAINT_BIT - 1));
7964            induction_bucket[bucket1] += 1;
7965        }
7966        i += 2;
7967    }
7968
7969    j += 2 * prefetch_distance + 1;
7970    while i < j {
7971        let iu = i as usize;
7972        let mut p = sa[iu];
7973        sa[iu] = p ^ SAINT_MIN;
7974        if p > 0 {
7975            p -= 1;
7976            let pu = p as usize;
7977            let bucket = t[pu] as usize;
7978            let slot = induction_bucket[bucket] as usize;
7979            sa[slot] = p
7980                | ((usize::from(t[pu - usize::from(p > 0)] < t[pu]) as SaSint) << (SAINT_BIT - 1));
7981            induction_bucket[bucket] += 1;
7982        }
7983        i += 1;
7984    }
7985}
7986
7987/// Internal helper: final bwt scan left to right 8u block prepare.
7988#[doc(hidden)]
7989pub fn final_bwt_scan_left_to_right_8u_block_prepare(
7990    t: &[u8],
7991    sa: &mut [SaSint],
7992    k: SaSint,
7993    buckets: &mut [SaSint],
7994    cache: &mut [ThreadCache],
7995    omp_block_start: FastSint,
7996    omp_block_size: FastSint,
7997) -> FastSint {
7998    if omp_block_size <= 0 {
7999        return 0;
8000    }
8001
8002    let k_usize = usize::try_from(k).expect("k must be non-negative");
8003    buckets[..k_usize].fill(0);
8004
8005    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8006    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8007    let mut count = 0usize;
8008    for i in start..start + size {
8009        let mut p = sa[i];
8010        sa[i] = p & SAINT_MAX;
8011        if p > 0 {
8012            p -= 1;
8013            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8014            let symbol = t[p_usize] as usize;
8015            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
8016            buckets[symbol] += 1;
8017            cache[count].symbol = symbol as SaSint;
8018            cache[count].index = p
8019                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
8020                    << (SAINT_BIT - 1));
8021            count += 1;
8022        }
8023    }
8024
8025    count as FastSint
8026}
8027
8028/// Internal helper: final sorting scan left to right 8u block prepare.
8029#[doc(hidden)]
8030pub fn final_sorting_scan_left_to_right_8u_block_prepare(
8031    t: &[u8],
8032    sa: &mut [SaSint],
8033    k: SaSint,
8034    buckets: &mut [SaSint],
8035    cache: &mut [ThreadCache],
8036    omp_block_start: FastSint,
8037    omp_block_size: FastSint,
8038) -> FastSint {
8039    if omp_block_size <= 0 {
8040        return 0;
8041    }
8042
8043    let k_usize = usize::try_from(k).expect("k must be non-negative");
8044    buckets[..k_usize].fill(0);
8045
8046    let prefetch_distance = 64usize;
8047    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8048    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8049    let mut count = 0usize;
8050    let mut i = start;
8051    let end = start + size;
8052    let unroll_end = if size > prefetch_distance + 1 {
8053        end - (prefetch_distance + 1)
8054    } else {
8055        start
8056    };
8057    let sa_ptr = sa.as_ptr();
8058    let t_ptr = t.as_ptr();
8059    while i < unroll_end {
8060        libsais_prefetchw(sa_ptr.wrapping_add(i + 2 * prefetch_distance));
8061        let s0 = sa[i + prefetch_distance];
8062        let ts0 = if s0 > 0 { s0 as usize } else { 2 };
8063        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
8064        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(2));
8065        let s1 = sa[i + prefetch_distance + 1];
8066        let ts1 = if s1 > 0 { s1 as usize } else { 2 };
8067        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
8068        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(2));
8069
8070        let mut p0 = sa[i];
8071        sa[i] = p0 ^ SAINT_MIN;
8072        if p0 > 0 {
8073            p0 -= 1;
8074            let p0u = p0 as usize;
8075            let symbol = t[p0u] as usize;
8076            buckets[symbol] += 1;
8077            cache[count].symbol = symbol as SaSint;
8078            cache[count].index = p0
8079                | ((usize::from(t[p0u - usize::from(p0 > 0)] < t[p0u]) as SaSint)
8080                    << (SAINT_BIT - 1));
8081            count += 1;
8082        }
8083        let mut p1 = sa[i + 1];
8084        sa[i + 1] = p1 ^ SAINT_MIN;
8085        if p1 > 0 {
8086            p1 -= 1;
8087            let p1u = p1 as usize;
8088            let symbol = t[p1u] as usize;
8089            buckets[symbol] += 1;
8090            cache[count].symbol = symbol as SaSint;
8091            cache[count].index = p1
8092                | ((usize::from(t[p1u - usize::from(p1 > 0)] < t[p1u]) as SaSint)
8093                    << (SAINT_BIT - 1));
8094            count += 1;
8095        }
8096        i += 2;
8097    }
8098    while i < end {
8099        let mut p = sa[i];
8100        sa[i] = p ^ SAINT_MIN;
8101        if p > 0 {
8102            p -= 1;
8103            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8104            let symbol = t[p_usize] as usize;
8105            buckets[symbol] += 1;
8106            cache[count].symbol = symbol as SaSint;
8107            cache[count].index = p
8108                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
8109                    << (SAINT_BIT - 1));
8110            count += 1;
8111        }
8112        i += 1;
8113    }
8114
8115    count as FastSint
8116}
8117
8118/// Internal helper: final order scan left to right 8u block place.
8119#[doc(hidden)]
8120pub fn final_order_scan_left_to_right_8u_block_place(
8121    sa: &mut [SaSint],
8122    buckets: &mut [SaSint],
8123    cache: &[ThreadCache],
8124    count: FastSint,
8125) {
8126    if count <= 0 {
8127        return;
8128    }
8129
8130    let count_usize = usize::try_from(count).expect("count must be non-negative");
8131    for entry in &cache[..count_usize] {
8132        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
8133        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
8134        sa[slot] = entry.index;
8135        buckets[symbol] += 1;
8136    }
8137}
8138
8139/// Internal helper: final bwt aux scan left to right 8u block place.
8140#[doc(hidden)]
8141pub fn final_bwt_aux_scan_left_to_right_8u_block_place(
8142    sa: &mut [SaSint],
8143    rm: SaSint,
8144    i_out: &mut [SaSint],
8145    buckets: &mut [SaSint],
8146    cache: &[ThreadCache],
8147    count: FastSint,
8148) {
8149    if count <= 0 {
8150        return;
8151    }
8152
8153    let count_usize = usize::try_from(count).expect("count must be non-negative");
8154    for entry in &cache[..count_usize] {
8155        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
8156        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
8157        sa[slot] = entry.index;
8158        buckets[symbol] += 1;
8159        if (entry.index & rm) == 0 {
8160            let sample_index = usize::try_from((entry.index & SAINT_MAX) / (rm + 1))
8161                .expect("sample index must be non-negative");
8162            i_out[sample_index] = buckets[symbol];
8163        }
8164    }
8165}
8166
8167/// Internal helper: final sorting scan left to right 32s block gather.
8168#[doc(hidden)]
8169pub fn final_sorting_scan_left_to_right_32s_block_gather(
8170    t: &[SaSint],
8171    sa: &mut [SaSint],
8172    cache: &mut [ThreadCache],
8173    omp_block_start: FastSint,
8174    omp_block_size: FastSint,
8175) {
8176    if omp_block_size <= 0 {
8177        return;
8178    }
8179    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8180    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8181    for offset in 0..size {
8182        let i = start + offset;
8183        let mut symbol = SAINT_MIN;
8184        let mut p = sa[i];
8185        sa[i] = p ^ SAINT_MIN;
8186        if p > 0 {
8187            p -= 1;
8188            let p_usize = p as usize;
8189            cache[offset].index = p
8190                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
8191                    << (SAINT_BIT - 1));
8192            symbol = t[p_usize];
8193        }
8194        cache[offset].symbol = symbol;
8195    }
8196}
8197
8198/// Internal helper: final sorting scan left to right 32s block sort.
8199#[doc(hidden)]
8200pub fn final_sorting_scan_left_to_right_32s_block_sort(
8201    t: &[SaSint],
8202    induction_bucket: &mut [SaSint],
8203    cache: &mut [ThreadCache],
8204    omp_block_start: FastSint,
8205    omp_block_size: FastSint,
8206) {
8207    if omp_block_size <= 0 {
8208        return;
8209    }
8210    let prefetch_distance = 64usize;
8211    let start = omp_block_start as usize;
8212    let block_end = start + omp_block_size as usize;
8213    let mut i = start;
8214    let mut j = start + (omp_block_size as usize).saturating_sub(prefetch_distance + 1);
8215
8216    while i < j {
8217        let ci = i - start;
8218        let v0 = cache[ci].symbol;
8219        if v0 >= 0 {
8220            let bucket_index0 = v0 as usize;
8221            cache[ci].symbol = induction_bucket[bucket_index0];
8222            induction_bucket[bucket_index0] += 1;
8223            if cache[ci].symbol < block_end as SaSint {
8224                let ni = cache[ci].symbol as usize;
8225                let cni = ni - start;
8226                let mut np = cache[ci].index;
8227                cache[ci].index = np ^ SAINT_MIN;
8228                if np > 0 {
8229                    np -= 1;
8230                    let np_usize = np as usize;
8231                    cache[cni].index = np
8232                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
8233                            as SaSint)
8234                            << (SAINT_BIT - 1));
8235                    cache[cni].symbol = t[np_usize];
8236                }
8237            }
8238        }
8239
8240        let i1 = i + 1;
8241        let ci1 = i1 - start;
8242        let v1 = cache[ci1].symbol;
8243        if v1 >= 0 {
8244            let bucket_index1 = v1 as usize;
8245            cache[ci1].symbol = induction_bucket[bucket_index1];
8246            induction_bucket[bucket_index1] += 1;
8247            if cache[ci1].symbol < block_end as SaSint {
8248                let ni = cache[ci1].symbol as usize;
8249                let cni = ni - start;
8250                let mut np = cache[ci1].index;
8251                cache[ci1].index = np ^ SAINT_MIN;
8252                if np > 0 {
8253                    np -= 1;
8254                    let np_usize = np as usize;
8255                    cache[cni].index = np
8256                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
8257                            as SaSint)
8258                            << (SAINT_BIT - 1));
8259                    cache[cni].symbol = t[np_usize];
8260                }
8261            }
8262        }
8263
8264        i += 2;
8265    }
8266
8267    j = block_end;
8268    while i < j {
8269        let ci = i - start;
8270        let v = cache[ci].symbol;
8271        if v >= 0 {
8272            let bucket_index = v as usize;
8273            cache[ci].symbol = induction_bucket[bucket_index];
8274            induction_bucket[bucket_index] += 1;
8275            if cache[ci].symbol < block_end as SaSint {
8276                let ni = cache[ci].symbol as usize;
8277                let cni = ni - start;
8278                let mut np = cache[ci].index;
8279                cache[ci].index = np ^ SAINT_MIN;
8280                if np > 0 {
8281                    np -= 1;
8282                    let np_usize = np as usize;
8283                    cache[cni].index = np
8284                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
8285                            as SaSint)
8286                            << (SAINT_BIT - 1));
8287                    cache[cni].symbol = t[np_usize];
8288                }
8289            }
8290        }
8291        i += 1;
8292    }
8293}
8294
8295/// Internal helper: final bwt scan left to right 8u block (OpenMP variant).
8296#[doc(hidden)]
8297pub fn final_bwt_scan_left_to_right_8u_block_omp(
8298    t: &[u8],
8299    sa: &mut [SaSint],
8300    k: SaSint,
8301    induction_bucket: &mut [SaSint],
8302    block_start: FastSint,
8303    block_size: FastSint,
8304    threads: SaSint,
8305    thread_state: &mut [ThreadState],
8306) {
8307    if block_size <= 0 {
8308        return;
8309    }
8310
8311    let k_usize = usize::try_from(k).expect("k must be non-negative");
8312    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8313    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8314        usize::try_from(threads)
8315            .expect("threads must be non-negative")
8316            .min(thread_state.len())
8317            .max(1)
8318    } else {
8319        1
8320    };
8321
8322    if omp_num_threads == 1 {
8323        final_bwt_scan_left_to_right_8u(t, sa, induction_bucket, block_start, block_size);
8324        return;
8325    }
8326
8327    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8328    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8329    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8330        let relative_start = thread_num * omp_block_stride;
8331        let size = if thread_num + 1 < omp_num_threads {
8332            omp_block_stride
8333        } else {
8334            block_size_usize - relative_start
8335        };
8336        state.count = final_bwt_scan_left_to_right_8u_block_prepare(
8337            t,
8338            sa,
8339            k,
8340            &mut state.buckets,
8341            &mut state.cache,
8342            (block_start_usize + relative_start) as FastSint,
8343            size as FastSint,
8344        );
8345    }
8346
8347    for state in thread_state.iter_mut().take(omp_num_threads) {
8348        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8349            let a = *bucket;
8350            let b = state.buckets[c];
8351            *bucket = a + b;
8352            state.buckets[c] = a;
8353        }
8354    }
8355
8356    for state in thread_state.iter_mut().take(omp_num_threads) {
8357        final_order_scan_left_to_right_8u_block_place(
8358            sa,
8359            &mut state.buckets,
8360            &state.cache,
8361            state.count,
8362        );
8363    }
8364}
8365
8366/// Internal helper: final bwt aux scan left to right 8u block (OpenMP variant).
8367#[doc(hidden)]
8368pub fn final_bwt_aux_scan_left_to_right_8u_block_omp(
8369    t: &[u8],
8370    sa: &mut [SaSint],
8371    k: SaSint,
8372    rm: SaSint,
8373    i_out: &mut [SaSint],
8374    induction_bucket: &mut [SaSint],
8375    block_start: FastSint,
8376    block_size: FastSint,
8377    threads: SaSint,
8378    thread_state: &mut [ThreadState],
8379) {
8380    if block_size <= 0 {
8381        return;
8382    }
8383
8384    let k_usize = usize::try_from(k).expect("k must be non-negative");
8385    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8386    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8387        usize::try_from(threads)
8388            .expect("threads must be non-negative")
8389            .min(thread_state.len())
8390            .max(1)
8391    } else {
8392        1
8393    };
8394
8395    if omp_num_threads == 1 {
8396        final_bwt_aux_scan_left_to_right_8u(
8397            t,
8398            sa,
8399            rm,
8400            i_out,
8401            induction_bucket,
8402            block_start,
8403            block_size,
8404        );
8405        return;
8406    }
8407
8408    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8409    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8410    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8411        let relative_start = thread_num * omp_block_stride;
8412        let size = if thread_num + 1 < omp_num_threads {
8413            omp_block_stride
8414        } else {
8415            block_size_usize - relative_start
8416        };
8417        state.count = final_bwt_scan_left_to_right_8u_block_prepare(
8418            t,
8419            sa,
8420            k,
8421            &mut state.buckets,
8422            &mut state.cache,
8423            (block_start_usize + relative_start) as FastSint,
8424            size as FastSint,
8425        );
8426    }
8427
8428    for state in thread_state.iter_mut().take(omp_num_threads) {
8429        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8430            let a = *bucket;
8431            let b = state.buckets[c];
8432            *bucket = a + b;
8433            state.buckets[c] = a;
8434        }
8435    }
8436
8437    for state in thread_state.iter_mut().take(omp_num_threads) {
8438        final_bwt_aux_scan_left_to_right_8u_block_place(
8439            sa,
8440            rm,
8441            i_out,
8442            &mut state.buckets,
8443            &state.cache,
8444            state.count,
8445        );
8446    }
8447}
8448
8449/// Internal helper: final sorting scan left to right 8u block (OpenMP variant).
8450#[doc(hidden)]
8451pub fn final_sorting_scan_left_to_right_8u_block_omp(
8452    t: &[u8],
8453    sa: &mut [SaSint],
8454    k: SaSint,
8455    induction_bucket: &mut [SaSint],
8456    block_start: FastSint,
8457    block_size: FastSint,
8458    threads: SaSint,
8459    thread_state: &mut [ThreadState],
8460) {
8461    if block_size <= 0 {
8462        return;
8463    }
8464
8465    let k_usize = usize::try_from(k).expect("k must be non-negative");
8466    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8467    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8468        usize::try_from(threads)
8469            .expect("threads must be non-negative")
8470            .min(thread_state.len())
8471            .max(1)
8472    } else {
8473        1
8474    };
8475
8476    if omp_num_threads == 1 {
8477        final_sorting_scan_left_to_right_8u(t, sa, induction_bucket, block_start, block_size);
8478        return;
8479    }
8480
8481    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8482    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8483    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8484        let relative_start = thread_num * omp_block_stride;
8485        let size = if thread_num + 1 < omp_num_threads {
8486            omp_block_stride
8487        } else {
8488            block_size_usize - relative_start
8489        };
8490        state.count = final_sorting_scan_left_to_right_8u_block_prepare(
8491            t,
8492            sa,
8493            k,
8494            &mut state.buckets,
8495            &mut state.cache,
8496            (block_start_usize + relative_start) as FastSint,
8497            size as FastSint,
8498        );
8499    }
8500
8501    for state in thread_state.iter_mut().take(omp_num_threads) {
8502        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8503            let a = *bucket;
8504            let b = state.buckets[c];
8505            *bucket = a + b;
8506            state.buckets[c] = a;
8507        }
8508    }
8509
8510    for state in thread_state.iter_mut().take(omp_num_threads) {
8511        final_order_scan_left_to_right_8u_block_place(
8512            sa,
8513            &mut state.buckets,
8514            &state.cache,
8515            state.count,
8516        );
8517    }
8518}
8519
8520/// Internal helper: final sorting scan left to right 32s block (OpenMP variant).
8521#[doc(hidden)]
8522pub fn final_sorting_scan_left_to_right_32s_block_omp(
8523    t: &[SaSint],
8524    sa: &mut [SaSint],
8525    buckets: &mut [SaSint],
8526    cache: &mut [ThreadCache],
8527    block_start: FastSint,
8528    block_size: FastSint,
8529    threads: SaSint,
8530) {
8531    if threads <= 1 || block_size < 16_384 {
8532        final_sorting_scan_left_to_right_32s(t, sa, buckets, block_start, block_size);
8533        return;
8534    }
8535
8536    final_sorting_scan_left_to_right_32s_block_gather(t, sa, cache, block_start, block_size);
8537    final_sorting_scan_left_to_right_32s_block_sort(t, buckets, cache, block_start, block_size);
8538    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8539    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
8540    let omp_num_threads = threads_usize.min(block_size_usize);
8541    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8542    {
8543        let sa_ptr = SyncMutPtr::new(sa);
8544        let cache_ptr = SyncMutPtr::new(cache);
8545        run_rayon_with_threads(omp_num_threads, || {
8546            (0..omp_num_threads)
8547                .into_par_iter()
8548                .for_each(|omp_thread_num| {
8549                    let omp_block_start = omp_thread_num * omp_block_stride;
8550                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
8551                        omp_block_stride
8552                    } else {
8553                        block_size_usize - omp_block_start
8554                    };
8555                    // SAFETY: each thread compacts cache[omp_block_start..+omp_block_size]
8556                    // (disjoint) and writes sa at symbol-indexed positions established by sort.
8557                    let sa = unsafe { sa_ptr.as_slice() };
8558                    let cache = unsafe { cache_ptr.as_slice() };
8559                    compact_and_place_cached_suffixes(
8560                        sa,
8561                        cache,
8562                        omp_block_start as FastSint,
8563                        omp_block_size as FastSint,
8564                    );
8565                });
8566        });
8567    }
8568}
8569
8570/// Internal helper: final bwt scan left to right 8u (OpenMP variant).
8571#[doc(hidden)]
8572pub fn final_bwt_scan_left_to_right_8u_omp(
8573    t: &[u8],
8574    sa: &mut [SaSint],
8575    n: FastSint,
8576    k: SaSint,
8577    induction_bucket: &mut [SaSint],
8578    threads: SaSint,
8579    thread_state: &mut [ThreadState],
8580) {
8581    let n_usize = usize::try_from(n).expect("n must be non-negative");
8582    let last = n_usize - 1;
8583    let bucket = t[last] as usize;
8584    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8585    sa[slot] =
8586        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8587    induction_bucket[bucket] += 1;
8588
8589    if threads == 1 || n < 65_536 {
8590        final_bwt_scan_left_to_right_8u(t, sa, induction_bucket, 0, n);
8591        return;
8592    }
8593
8594    let mut block_start = 0usize;
8595    while block_start < n_usize {
8596        if sa[block_start] == 0 {
8597            block_start += 1;
8598        } else {
8599            let threads_usize = usize::try_from(threads)
8600                .expect("threads must be non-negative")
8601                .min(thread_state.len())
8602                .max(1);
8603            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8604            let block_max_end = (block_start + max_span).min(n_usize);
8605            let mut block_end = block_start + 1;
8606            while block_end < block_max_end && sa[block_end] != 0 {
8607                block_end += 1;
8608            }
8609            let size = block_end - block_start;
8610
8611            if size < 32 {
8612                final_bwt_scan_left_to_right_8u(
8613                    t,
8614                    sa,
8615                    induction_bucket,
8616                    block_start as FastSint,
8617                    size as FastSint,
8618                );
8619            } else {
8620                final_bwt_scan_left_to_right_8u_block_omp(
8621                    t,
8622                    sa,
8623                    k,
8624                    induction_bucket,
8625                    block_start as FastSint,
8626                    size as FastSint,
8627                    threads,
8628                    thread_state,
8629                );
8630            }
8631            block_start = block_end;
8632        }
8633    }
8634}
8635
8636/// Internal helper: final bwt aux scan left to right 8u (OpenMP variant).
8637#[doc(hidden)]
8638pub fn final_bwt_aux_scan_left_to_right_8u_omp(
8639    t: &[u8],
8640    sa: &mut [SaSint],
8641    n: FastSint,
8642    k: SaSint,
8643    rm: SaSint,
8644    i_out: &mut [SaSint],
8645    induction_bucket: &mut [SaSint],
8646    threads: SaSint,
8647    thread_state: &mut [ThreadState],
8648) {
8649    let n_usize = usize::try_from(n).expect("n must be non-negative");
8650    let last = n_usize - 1;
8651    let bucket = t[last] as usize;
8652    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8653    sa[slot] =
8654        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8655    induction_bucket[bucket] += 1;
8656    if (((n as SaSint) - 1) & rm) == 0 {
8657        i_out[last / usize::try_from(rm + 1).expect("rm must allow positive step")] =
8658            induction_bucket[bucket];
8659    }
8660
8661    if threads == 1 || n < 65_536 {
8662        final_bwt_aux_scan_left_to_right_8u(t, sa, rm, i_out, induction_bucket, 0, n);
8663        return;
8664    }
8665
8666    let mut block_start = 0usize;
8667    while block_start < n_usize {
8668        if sa[block_start] == 0 {
8669            block_start += 1;
8670        } else {
8671            let threads_usize = usize::try_from(threads)
8672                .expect("threads must be non-negative")
8673                .min(thread_state.len())
8674                .max(1);
8675            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8676            let block_max_end = (block_start + max_span).min(n_usize);
8677            let mut block_end = block_start + 1;
8678            while block_end < block_max_end && sa[block_end] != 0 {
8679                block_end += 1;
8680            }
8681            let size = block_end - block_start;
8682
8683            if size < 32 {
8684                final_bwt_aux_scan_left_to_right_8u(
8685                    t,
8686                    sa,
8687                    rm,
8688                    i_out,
8689                    induction_bucket,
8690                    block_start as FastSint,
8691                    size as FastSint,
8692                );
8693            } else {
8694                final_bwt_aux_scan_left_to_right_8u_block_omp(
8695                    t,
8696                    sa,
8697                    k,
8698                    rm,
8699                    i_out,
8700                    induction_bucket,
8701                    block_start as FastSint,
8702                    size as FastSint,
8703                    threads,
8704                    thread_state,
8705                );
8706            }
8707            block_start = block_end;
8708        }
8709    }
8710}
8711
8712/// Internal helper: final sorting scan left to right 8u (OpenMP variant).
8713#[doc(hidden)]
8714pub fn final_sorting_scan_left_to_right_8u_omp(
8715    t: &[u8],
8716    sa: &mut [SaSint],
8717    n: FastSint,
8718    k: SaSint,
8719    induction_bucket: &mut [SaSint],
8720    threads: SaSint,
8721    thread_state: &mut [ThreadState],
8722) {
8723    let n_usize = usize::try_from(n).expect("n must be non-negative");
8724    let last = n_usize - 1;
8725    let bucket = t[last] as usize;
8726    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8727    sa[slot] =
8728        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8729    induction_bucket[bucket] += 1;
8730
8731    if threads == 1 || n < 65_536 {
8732        final_sorting_scan_left_to_right_8u(t, sa, induction_bucket, 0, n);
8733        return;
8734    }
8735
8736    let mut block_start = 0usize;
8737    while block_start < n_usize {
8738        if sa[block_start] == 0 {
8739            block_start += 1;
8740        } else {
8741            let threads_usize = usize::try_from(threads)
8742                .expect("threads must be non-negative")
8743                .min(thread_state.len())
8744                .max(1);
8745            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8746            let block_max_end = (block_start + max_span).min(n_usize);
8747            let mut block_end = block_start + 1;
8748            while block_end < block_max_end && sa[block_end] != 0 {
8749                block_end += 1;
8750            }
8751            let size = block_end - block_start;
8752
8753            if size < 32 {
8754                final_sorting_scan_left_to_right_8u(
8755                    t,
8756                    sa,
8757                    induction_bucket,
8758                    block_start as FastSint,
8759                    size as FastSint,
8760                );
8761            } else {
8762                final_sorting_scan_left_to_right_8u_block_omp(
8763                    t,
8764                    sa,
8765                    k,
8766                    induction_bucket,
8767                    block_start as FastSint,
8768                    size as FastSint,
8769                    threads,
8770                    thread_state,
8771                );
8772            }
8773            block_start = block_end;
8774        }
8775    }
8776}
8777
8778/// Internal helper: final sorting scan left to right 32s (OpenMP variant).
8779#[doc(hidden)]
8780pub fn final_sorting_scan_left_to_right_32s_omp(
8781    t: &[SaSint],
8782    sa: &mut [SaSint],
8783    n: SaSint,
8784    induction_bucket: &mut [SaSint],
8785    threads: SaSint,
8786    thread_state: &mut [ThreadState],
8787) {
8788    let n_usize = usize::try_from(n).expect("n must be non-negative");
8789    let last = n_usize - 1;
8790    let bucket = usize::try_from(t[last]).expect("bucket symbol must be non-negative");
8791    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8792    sa[slot] = (n - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8793    induction_bucket[bucket] += 1;
8794
8795    if threads == 1 || n < 65_536 {
8796        final_sorting_scan_left_to_right_32s(t, sa, induction_bucket, 0, n as FastSint);
8797        return;
8798    }
8799
8800    if thread_state.is_empty() {
8801        final_sorting_scan_left_to_right_32s(t, sa, induction_bucket, 0, n as FastSint);
8802        return;
8803    }
8804
8805    let threads_usize = usize::try_from(threads)
8806        .expect("threads must be non-negative")
8807        .max(1);
8808    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
8809    let mut block_start = 0usize;
8810    while block_start < n_usize {
8811        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(n_usize);
8812        final_sorting_scan_left_to_right_32s_block_omp(
8813            t,
8814            sa,
8815            induction_bucket,
8816            &mut cache,
8817            block_start as FastSint,
8818            (block_end - block_start) as FastSint,
8819            threads,
8820        );
8821        block_start = block_end;
8822    }
8823}
8824
8825/// Internal helper: final bwt scan right to left 8u.
8826#[doc(hidden)]
8827pub fn final_bwt_scan_right_to_left_8u(
8828    t: &[u8],
8829    sa: &mut [SaSint],
8830    induction_bucket: &mut [SaSint],
8831    omp_block_start: FastSint,
8832    omp_block_size: FastSint,
8833) -> SaSint {
8834    if omp_block_size <= 0 {
8835        return -1;
8836    }
8837
8838    let mut index = -1;
8839
8840    let start =
8841        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
8842    let mut i = omp_block_start + omp_block_size - 1;
8843    let mut j = start + 1;
8844    while i >= j {
8845        let i0 = usize::try_from(i).expect("loop index must be non-negative");
8846        let i1 = usize::try_from(i - 1).expect("loop index must be non-negative");
8847
8848        let mut p0 = sa[i0];
8849        if p0 == 0 {
8850            index = i0 as SaSint;
8851        }
8852        sa[i0] = p0 & SAINT_MAX;
8853        if p0 > 0 {
8854            p0 -= 1;
8855            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
8856            let c0 = t[p0_usize - usize::from(p0 > 0)] as SaSint;
8857            let c1 = t[p0_usize] as SaSint;
8858            sa[i0] = c1;
8859            induction_bucket[c1 as usize] -= 1;
8860            let slot = usize::try_from(induction_bucket[c1 as usize])
8861                .expect("bucket slot must be non-negative");
8862            let marked = c0 | SAINT_MIN;
8863            sa[slot] = if c0 <= c1 { p0 } else { marked };
8864        }
8865
8866        let mut p1 = sa[i1];
8867        if p1 == 0 {
8868            index = i1 as SaSint;
8869        }
8870        sa[i1] = p1 & SAINT_MAX;
8871        if p1 > 0 {
8872            p1 -= 1;
8873            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
8874            let c0 = t[p1_usize - usize::from(p1 > 0)] as SaSint;
8875            let c1 = t[p1_usize] as SaSint;
8876            sa[i1] = c1;
8877            induction_bucket[c1 as usize] -= 1;
8878            let slot = usize::try_from(induction_bucket[c1 as usize])
8879                .expect("bucket slot must be non-negative");
8880            let marked = c0 | SAINT_MIN;
8881            sa[slot] = if c0 <= c1 { p1 } else { marked };
8882        }
8883
8884        i -= 2;
8885    }
8886
8887    j -= 1;
8888    while i >= j {
8889        let idx = usize::try_from(i).expect("loop index must be non-negative");
8890        let mut p = sa[idx];
8891        if p == 0 {
8892            index = idx as SaSint;
8893        }
8894        sa[idx] = p & SAINT_MAX;
8895        if p > 0 {
8896            p -= 1;
8897            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8898            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8899            let c1 = t[p_usize] as SaSint;
8900            sa[idx] = c1;
8901            induction_bucket[c1 as usize] -= 1;
8902            let slot = usize::try_from(induction_bucket[c1 as usize])
8903                .expect("bucket slot must be non-negative");
8904            let marked = c0 | SAINT_MIN;
8905            sa[slot] = if c0 <= c1 { p } else { marked };
8906        }
8907
8908        i -= 1;
8909    }
8910
8911    index
8912}
8913
8914/// Internal helper: final bwt aux scan right to left 8u.
8915#[doc(hidden)]
8916pub fn final_bwt_aux_scan_right_to_left_8u(
8917    t: &[u8],
8918    sa: &mut [SaSint],
8919    rm: SaSint,
8920    i_out: &mut [SaSint],
8921    induction_bucket: &mut [SaSint],
8922    omp_block_start: FastSint,
8923    omp_block_size: FastSint,
8924) {
8925    if omp_block_size <= 0 {
8926        return;
8927    }
8928
8929    let start =
8930        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
8931    let mut i = omp_block_start + omp_block_size - 1;
8932    let mut j = start + 1;
8933    while i >= j {
8934        let i0 = usize::try_from(i).expect("loop index must be non-negative");
8935        let i1 = usize::try_from(i - 1).expect("loop index must be non-negative");
8936
8937        let mut p0 = sa[i0];
8938        sa[i0] = p0 & SAINT_MAX;
8939        if p0 > 0 {
8940            p0 -= 1;
8941            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
8942            let c0 = t[p0_usize - usize::from(p0 > 0)] as SaSint;
8943            let c1 = t[p0_usize] as SaSint;
8944            sa[i0] = c1;
8945            induction_bucket[c1 as usize] -= 1;
8946            let slot = usize::try_from(induction_bucket[c1 as usize])
8947                .expect("bucket slot must be non-negative");
8948            let marked = c0 | SAINT_MIN;
8949            sa[slot] = if c0 <= c1 { p0 } else { marked };
8950            if (p0 & rm) == 0 {
8951                let out_idx =
8952                    usize::try_from(p0 / (rm + 1)).expect("sample index must be non-negative");
8953                i_out[out_idx] = induction_bucket[t[p0_usize] as usize] + 1;
8954            }
8955        }
8956
8957        let mut p1 = sa[i1];
8958        sa[i1] = p1 & SAINT_MAX;
8959        if p1 > 0 {
8960            p1 -= 1;
8961            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
8962            let c0 = t[p1_usize - usize::from(p1 > 0)] as SaSint;
8963            let c1 = t[p1_usize] as SaSint;
8964            sa[i1] = c1;
8965            induction_bucket[c1 as usize] -= 1;
8966            let slot = usize::try_from(induction_bucket[c1 as usize])
8967                .expect("bucket slot must be non-negative");
8968            let marked = c0 | SAINT_MIN;
8969            sa[slot] = if c0 <= c1 { p1 } else { marked };
8970            if (p1 & rm) == 0 {
8971                let out_idx =
8972                    usize::try_from(p1 / (rm + 1)).expect("sample index must be non-negative");
8973                i_out[out_idx] = induction_bucket[t[p1_usize] as usize] + 1;
8974            }
8975        }
8976
8977        i -= 2;
8978    }
8979
8980    j -= 1;
8981    while i >= j {
8982        let idx = usize::try_from(i).expect("loop index must be non-negative");
8983        let mut p = sa[idx];
8984        sa[idx] = p & SAINT_MAX;
8985        if p > 0 {
8986            p -= 1;
8987            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8988            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8989            let c1 = t[p_usize] as SaSint;
8990            sa[idx] = c1;
8991            induction_bucket[c1 as usize] -= 1;
8992            let slot = usize::try_from(induction_bucket[c1 as usize])
8993                .expect("bucket slot must be non-negative");
8994            let marked = c0 | SAINT_MIN;
8995            sa[slot] = if c0 <= c1 { p } else { marked };
8996            if (p & rm) == 0 {
8997                let out_idx =
8998                    usize::try_from(p / (rm + 1)).expect("sample index must be non-negative");
8999                i_out[out_idx] = induction_bucket[t[p_usize] as usize] + 1;
9000            }
9001        }
9002
9003        i -= 1;
9004    }
9005}
9006
9007/// Internal helper: final sorting scan right to left 8u.
9008#[doc(hidden)]
9009pub fn final_sorting_scan_right_to_left_8u(
9010    t: &[u8],
9011    sa: &mut [SaSint],
9012    induction_bucket: &mut [SaSint],
9013    omp_block_start: FastSint,
9014    omp_block_size: FastSint,
9015) {
9016    if omp_block_size <= 0 {
9017        return;
9018    }
9019
9020    let prefetch_distance = 64usize;
9021    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9022    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9023    let mut i = start + size - 1;
9024    let mut j = start + prefetch_distance + 1;
9025
9026    let sa_ptr = sa.as_ptr();
9027    let t_ptr = t.as_ptr();
9028    while i >= j {
9029        libsais_prefetchw(sa_ptr.wrapping_add(i.wrapping_sub(2 * prefetch_distance)));
9030        let s0 = sa[i - prefetch_distance];
9031        let ts0 = if s0 > 0 { s0 as usize } else { 2 };
9032        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
9033        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(2));
9034        let s1 = sa[i - prefetch_distance - 1];
9035        let ts1 = if s1 > 0 { s1 as usize } else { 2 };
9036        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
9037        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(2));
9038
9039        let mut p0 = sa[i];
9040        sa[i] = p0 & SAINT_MAX;
9041        if p0 > 0 {
9042            p0 -= 1;
9043            let p0_usize = p0 as usize;
9044            let bucket0 = t[p0_usize] as usize;
9045            induction_bucket[bucket0] -= 1;
9046            let slot0 = induction_bucket[bucket0] as usize;
9047            sa[slot0] = p0
9048                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] > t[p0_usize]) as SaSint)
9049                    << (SAINT_BIT - 1));
9050        }
9051
9052        let mut p1 = sa[i - 1];
9053        sa[i - 1] = p1 & SAINT_MAX;
9054        if p1 > 0 {
9055            p1 -= 1;
9056            let p1_usize = p1 as usize;
9057            let bucket1 = t[p1_usize] as usize;
9058            induction_bucket[bucket1] -= 1;
9059            let slot1 = induction_bucket[bucket1] as usize;
9060            sa[slot1] = p1
9061                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] > t[p1_usize]) as SaSint)
9062                    << (SAINT_BIT - 1));
9063        }
9064
9065        i -= 2;
9066    }
9067
9068    j -= prefetch_distance + 1;
9069    while i >= j {
9070        let mut p = sa[i];
9071        sa[i] = p & SAINT_MAX;
9072        if p > 0 {
9073            p -= 1;
9074            let p_usize = p as usize;
9075            let bucket = t[p_usize] as usize;
9076            induction_bucket[bucket] -= 1;
9077            let slot = induction_bucket[bucket] as usize;
9078            sa[slot] = p
9079                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9080                    << (SAINT_BIT - 1));
9081        }
9082
9083        if i == 0 {
9084            break;
9085        }
9086        i -= 1;
9087    }
9088}
9089
9090/// Internal helper: final gsa scan right to left 8u.
9091#[doc(hidden)]
9092pub fn final_gsa_scan_right_to_left_8u(
9093    t: &[u8],
9094    sa: &mut [SaSint],
9095    induction_bucket: &mut [SaSint],
9096    omp_block_start: FastSint,
9097    omp_block_size: FastSint,
9098) {
9099    if omp_block_size <= 0 {
9100        return;
9101    }
9102
9103    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9104    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9105    let mut i = start + size;
9106    while i > start {
9107        i -= 1;
9108        let mut p = sa[i];
9109        sa[i] = p & SAINT_MAX;
9110        if p > 0 {
9111            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
9112            if t[p_usize - 1] > 0 {
9113                p -= 1;
9114                let bucket =
9115                    t[usize::try_from(p).expect("suffix index must be non-negative")] as usize;
9116                induction_bucket[bucket] -= 1;
9117                let slot = usize::try_from(induction_bucket[bucket])
9118                    .expect("bucket slot must be non-negative");
9119                let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
9120                sa[slot] = p
9121                    | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9122                        << (SAINT_BIT - 1));
9123            }
9124        }
9125    }
9126}
9127
9128/// Internal helper: final sorting scan right to left 32s.
9129#[doc(hidden)]
9130pub fn final_sorting_scan_right_to_left_32s(
9131    t: &[SaSint],
9132    sa: &mut [SaSint],
9133    induction_bucket: &mut [SaSint],
9134    omp_block_start: FastSint,
9135    omp_block_size: FastSint,
9136) {
9137    if omp_block_size <= 0 {
9138        return;
9139    }
9140
9141    let prefetch_distance: FastSint = 64;
9142    let mut i = omp_block_start + omp_block_size - 1;
9143    let mut j = omp_block_start + 2 * prefetch_distance + 1;
9144
9145    let sa_ptr = sa.as_ptr();
9146    let t_ptr = t.as_ptr();
9147    let prefetch_distance_us = prefetch_distance as usize;
9148    while i >= j {
9149        let i_us = i as usize;
9150        libsais_prefetchw(sa_ptr.wrapping_add(i_us.wrapping_sub(3 * prefetch_distance_us)));
9151        let s0 = sa[i_us - 2 * prefetch_distance_us];
9152        let ts0 = if s0 > 0 { s0 as usize } else { 1 };
9153        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
9154        let s1 = sa[i_us - 2 * prefetch_distance_us - 1];
9155        let ts1 = if s1 > 0 { s1 as usize } else { 1 };
9156        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
9157        let s2 = sa[i_us - prefetch_distance_us];
9158        if s2 > 0 {
9159            let s2u = s2 as usize;
9160            libsais_prefetchw(induction_bucket.as_ptr().wrapping_add(t[s2u - 1] as usize));
9161            libsais_prefetchr(t_ptr.wrapping_add(s2u).wrapping_sub(2));
9162        }
9163        let s3 = sa[i_us - prefetch_distance_us - 1];
9164        if s3 > 0 {
9165            let s3u = s3 as usize;
9166            libsais_prefetchw(induction_bucket.as_ptr().wrapping_add(t[s3u - 1] as usize));
9167            libsais_prefetchr(t_ptr.wrapping_add(s3u).wrapping_sub(2));
9168        }
9169
9170        let i0 = i as usize;
9171        let mut p0 = sa[i0];
9172        sa[i0] = p0 & SAINT_MAX;
9173        if p0 > 0 {
9174            p0 -= 1;
9175            let p0u = p0 as usize;
9176            let bucket0 = t[p0u] as usize;
9177            induction_bucket[bucket0] -= 1;
9178            let slot0 = induction_bucket[bucket0] as usize;
9179            sa[slot0] = p0
9180                | ((usize::from(t[p0u - usize::from(p0 > 0)] > t[p0u]) as SaSint)
9181                    << (SAINT_BIT - 1));
9182        }
9183
9184        let i1 = (i - 1) as usize;
9185        let mut p1 = sa[i1];
9186        sa[i1] = p1 & SAINT_MAX;
9187        if p1 > 0 {
9188            p1 -= 1;
9189            let p1u = p1 as usize;
9190            let bucket1 = t[p1u] as usize;
9191            induction_bucket[bucket1] -= 1;
9192            let slot1 = induction_bucket[bucket1] as usize;
9193            sa[slot1] = p1
9194                | ((usize::from(t[p1u - usize::from(p1 > 0)] > t[p1u]) as SaSint)
9195                    << (SAINT_BIT - 1));
9196        }
9197        i -= 2;
9198    }
9199
9200    j -= 2 * prefetch_distance + 1;
9201    while i >= j {
9202        let iu = i as usize;
9203        let mut p = sa[iu];
9204        sa[iu] = p & SAINT_MAX;
9205        if p > 0 {
9206            p -= 1;
9207            let pu = p as usize;
9208            let bucket = t[pu] as usize;
9209            induction_bucket[bucket] -= 1;
9210            let slot = induction_bucket[bucket] as usize;
9211            sa[slot] = p
9212                | ((usize::from(t[pu - usize::from(p > 0)] > t[pu]) as SaSint) << (SAINT_BIT - 1));
9213        }
9214        i -= 1;
9215    }
9216}
9217
9218/// Internal helper: final bwt scan right to left 8u block prepare.
9219#[doc(hidden)]
9220pub fn final_bwt_scan_right_to_left_8u_block_prepare(
9221    t: &[u8],
9222    sa: &mut [SaSint],
9223    k: SaSint,
9224    buckets: &mut [SaSint],
9225    cache: &mut [ThreadCache],
9226    omp_block_start: FastSint,
9227    omp_block_size: FastSint,
9228) -> FastSint {
9229    if omp_block_size <= 0 {
9230        return 0;
9231    }
9232    let k_usize = usize::try_from(k).expect("k must be non-negative");
9233    buckets[..k_usize].fill(0);
9234    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9235    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9236    let mut count = 0usize;
9237    let mut i = start + size;
9238    while i > start {
9239        i -= 1;
9240        let mut p = sa[i];
9241        sa[i] = p & SAINT_MAX;
9242        if p > 0 {
9243            p -= 1;
9244            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
9245            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
9246            let c1 = t[p_usize] as SaSint;
9247            sa[i] = c1;
9248            buckets[c1 as usize] += 1;
9249            cache[count].symbol = c1;
9250            cache[count].index = if c0 <= c1 { p } else { c0 | SAINT_MIN };
9251            count += 1;
9252        }
9253    }
9254    count as FastSint
9255}
9256
9257/// Internal helper: final bwt aux scan right to left 8u block prepare.
9258#[doc(hidden)]
9259pub fn final_bwt_aux_scan_right_to_left_8u_block_prepare(
9260    t: &[u8],
9261    sa: &mut [SaSint],
9262    k: SaSint,
9263    buckets: &mut [SaSint],
9264    cache: &mut [ThreadCache],
9265    omp_block_start: FastSint,
9266    omp_block_size: FastSint,
9267) -> FastSint {
9268    if omp_block_size <= 0 {
9269        return 0;
9270    }
9271    let k_usize = usize::try_from(k).expect("k must be non-negative");
9272    buckets[..k_usize].fill(0);
9273    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9274    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9275    let mut count = 0usize;
9276    let mut i = start + size;
9277    while i > start {
9278        i -= 1;
9279        let mut p = sa[i];
9280        sa[i] = p & SAINT_MAX;
9281        if p > 0 {
9282            p -= 1;
9283            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
9284            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
9285            let c1 = t[p_usize] as SaSint;
9286            sa[i] = c1;
9287            buckets[c1 as usize] += 1;
9288            cache[count].symbol = c1;
9289            cache[count].index = if c0 <= c1 { p } else { c0 | SAINT_MIN };
9290            cache[count + 1].index = p;
9291            count += 2;
9292        }
9293    }
9294    count as FastSint
9295}
9296
9297/// Internal helper: final sorting scan right to left 8u block prepare.
9298#[doc(hidden)]
9299pub fn final_sorting_scan_right_to_left_8u_block_prepare(
9300    t: &[u8],
9301    sa: &mut [SaSint],
9302    k: SaSint,
9303    buckets: &mut [SaSint],
9304    cache: &mut [ThreadCache],
9305    omp_block_start: FastSint,
9306    omp_block_size: FastSint,
9307) -> FastSint {
9308    if omp_block_size <= 0 {
9309        return 0;
9310    }
9311
9312    let k_usize = usize::try_from(k).expect("k must be non-negative");
9313    buckets[..k_usize].fill(0);
9314
9315    let prefetch_distance = 64usize;
9316    let start =
9317        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
9318    let start_us = start as usize;
9319    let mut i = omp_block_start + omp_block_size - 1;
9320    let mut count = 0usize;
9321
9322    let sa_ptr = sa.as_ptr();
9323    let t_ptr = t.as_ptr();
9324    let j_pf = (start_us + prefetch_distance + 1) as FastSint;
9325    while i >= j_pf {
9326        let i0 = i as usize;
9327        let i1 = (i - 1) as usize;
9328
9329        libsais_prefetchw(sa_ptr.wrapping_add(i0.wrapping_sub(2 * prefetch_distance)));
9330        let s0 = sa[i0 - prefetch_distance];
9331        let ts0 = if s0 > 0 { s0 as usize } else { 2 };
9332        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(1));
9333        libsais_prefetchr(t_ptr.wrapping_add(ts0).wrapping_sub(2));
9334        let s1 = sa[i0 - prefetch_distance - 1];
9335        let ts1 = if s1 > 0 { s1 as usize } else { 2 };
9336        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(1));
9337        libsais_prefetchr(t_ptr.wrapping_add(ts1).wrapping_sub(2));
9338
9339        let mut p0 = sa[i0];
9340        sa[i0] = p0 & SAINT_MAX;
9341        if p0 > 0 {
9342            p0 -= 1;
9343            let p0_usize = p0 as usize;
9344            let c0 = t[p0_usize] as SaSint;
9345            buckets[c0 as usize] += 1;
9346            cache[count].symbol = c0;
9347            cache[count].index = p0
9348                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] > t[p0_usize]) as SaSint)
9349                    << (SAINT_BIT - 1));
9350            count += 1;
9351        }
9352
9353        let mut p1 = sa[i1];
9354        sa[i1] = p1 & SAINT_MAX;
9355        if p1 > 0 {
9356            p1 -= 1;
9357            let p1_usize = p1 as usize;
9358            let c1 = t[p1_usize] as SaSint;
9359            buckets[c1 as usize] += 1;
9360            cache[count].symbol = c1;
9361            cache[count].index = p1
9362                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] > t[p1_usize]) as SaSint)
9363                    << (SAINT_BIT - 1));
9364            count += 1;
9365        }
9366
9367        i -= 2;
9368    }
9369
9370    while i >= start {
9371        let idx = i as usize;
9372        let mut p = sa[idx];
9373        sa[idx] = p & SAINT_MAX;
9374        if p > 0 {
9375            p -= 1;
9376            let p_usize = p as usize;
9377            let c = t[p_usize] as SaSint;
9378            buckets[c as usize] += 1;
9379            cache[count].symbol = c;
9380            cache[count].index = p
9381                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9382                    << (SAINT_BIT - 1));
9383            count += 1;
9384        }
9385
9386        if i == 0 {
9387            break;
9388        }
9389        i -= 1;
9390    }
9391
9392    count as FastSint
9393}
9394
9395/// Internal helper: final order scan right to left 8u block place.
9396#[doc(hidden)]
9397pub fn final_order_scan_right_to_left_8u_block_place(
9398    sa: &mut [SaSint],
9399    buckets: &mut [SaSint],
9400    cache: &[ThreadCache],
9401    count: FastSint,
9402) {
9403    if count <= 0 {
9404        return;
9405    }
9406    let count_usize = usize::try_from(count).expect("count must be non-negative");
9407    for entry in &cache[..count_usize] {
9408        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
9409        buckets[symbol] -= 1;
9410        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9411        sa[slot] = entry.index;
9412    }
9413}
9414
9415/// Internal helper: final gsa scan right to left 8u block place.
9416#[doc(hidden)]
9417pub fn final_gsa_scan_right_to_left_8u_block_place(
9418    sa: &mut [SaSint],
9419    buckets: &mut [SaSint],
9420    cache: &[ThreadCache],
9421    count: FastSint,
9422) {
9423    if count <= 0 {
9424        return;
9425    }
9426    let count_usize = usize::try_from(count).expect("count must be non-negative");
9427    for entry in &cache[..count_usize] {
9428        if entry.symbol > 0 {
9429            let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
9430            buckets[symbol] -= 1;
9431            let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9432            sa[slot] = entry.index;
9433        }
9434    }
9435}
9436
9437/// Internal helper: final bwt aux scan right to left 8u block place.
9438#[doc(hidden)]
9439pub fn final_bwt_aux_scan_right_to_left_8u_block_place(
9440    sa: &mut [SaSint],
9441    rm: SaSint,
9442    i_out: &mut [SaSint],
9443    buckets: &mut [SaSint],
9444    cache: &[ThreadCache],
9445    count: FastSint,
9446) {
9447    if count <= 0 {
9448        return;
9449    }
9450    let count_usize = usize::try_from(count).expect("count must be non-negative");
9451    let mut i = 0usize;
9452    while i < count_usize {
9453        let symbol = usize::try_from(cache[i].symbol).expect("cache symbol must be non-negative");
9454        buckets[symbol] -= 1;
9455        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9456        sa[slot] = cache[i].index;
9457        if (cache[i + 1].index & rm) == 0 {
9458            let sample_index = usize::try_from((cache[i + 1].index & SAINT_MAX) / (rm + 1))
9459                .expect("sample index must be non-negative");
9460            i_out[sample_index] = buckets[symbol] + 1;
9461        }
9462        i += 2;
9463    }
9464}
9465
9466/// Internal helper: final sorting scan right to left 32s block gather.
9467#[doc(hidden)]
9468pub fn final_sorting_scan_right_to_left_32s_block_gather(
9469    t: &[SaSint],
9470    sa: &mut [SaSint],
9471    cache: &mut [ThreadCache],
9472    omp_block_start: FastSint,
9473    omp_block_size: FastSint,
9474) {
9475    if omp_block_size <= 0 {
9476        return;
9477    }
9478    let prefetch_distance = 64usize;
9479    let start = omp_block_start as usize;
9480    let block_end = start + omp_block_size as usize;
9481    let mut i = start;
9482    let mut j = block_end.saturating_sub(prefetch_distance + 1);
9483
9484    while i < j {
9485        let ci = i - start;
9486        let mut symbol0 = SAINT_MIN;
9487        let mut p0 = sa[i];
9488        sa[i] = p0 & SAINT_MAX;
9489        if p0 > 0 {
9490            p0 -= 1;
9491            let p0_usize = p0 as usize;
9492            cache[ci].index = p0
9493                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] > t[p0_usize]) as SaSint)
9494                    << (SAINT_BIT - 1));
9495            symbol0 = t[p0_usize];
9496        }
9497        cache[ci].symbol = symbol0;
9498
9499        let i1 = i + 1;
9500        let ci1 = i1 - start;
9501        let mut symbol1 = SAINT_MIN;
9502        let mut p1 = sa[i1];
9503        sa[i1] = p1 & SAINT_MAX;
9504        if p1 > 0 {
9505            p1 -= 1;
9506            let p1_usize = p1 as usize;
9507            cache[ci1].index = p1
9508                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] > t[p1_usize]) as SaSint)
9509                    << (SAINT_BIT - 1));
9510            symbol1 = t[p1_usize];
9511        }
9512        cache[ci1].symbol = symbol1;
9513
9514        i += 2;
9515    }
9516
9517    j = block_end;
9518    while i < j {
9519        let ci = i - start;
9520        let mut symbol = SAINT_MIN;
9521        let mut p = sa[i];
9522        sa[i] = p & SAINT_MAX;
9523        if p > 0 {
9524            p -= 1;
9525            let p_usize = p as usize;
9526            cache[ci].index = p
9527                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9528                    << (SAINT_BIT - 1));
9529            symbol = t[p_usize];
9530        }
9531        cache[ci].symbol = symbol;
9532        i += 1;
9533    }
9534}
9535
9536/// Internal helper: final sorting scan right to left 32s block sort.
9537#[doc(hidden)]
9538pub fn final_sorting_scan_right_to_left_32s_block_sort(
9539    t: &[SaSint],
9540    induction_bucket: &mut [SaSint],
9541    cache: &mut [ThreadCache],
9542    omp_block_start: FastSint,
9543    omp_block_size: FastSint,
9544) {
9545    if omp_block_size <= 0 {
9546        return;
9547    }
9548    let prefetch_distance = 64usize;
9549    let start = omp_block_start as usize;
9550    let mut i = start + omp_block_size as usize - 1;
9551    let mut j = start + prefetch_distance + 1;
9552
9553    while i >= j {
9554        let ci = i - start;
9555        let v0 = cache[ci].symbol;
9556        if v0 >= 0 {
9557            let bucket_index0 = v0 as usize;
9558            induction_bucket[bucket_index0] -= 1;
9559            cache[ci].symbol = induction_bucket[bucket_index0];
9560            if cache[ci].symbol >= omp_block_start as SaSint {
9561                let ni = cache[ci].symbol as usize;
9562                let cni = ni - start;
9563                let mut np = cache[ci].index;
9564                cache[ci].index = np & SAINT_MAX;
9565                if np > 0 {
9566                    np -= 1;
9567                    let np_usize = np as usize;
9568                    cache[cni].index = np
9569                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9570                            as SaSint)
9571                            << (SAINT_BIT - 1));
9572                    cache[cni].symbol = t[np_usize];
9573                }
9574            }
9575        }
9576
9577        let i1 = i - 1;
9578        let ci1 = i1 - start;
9579        let v1 = cache[ci1].symbol;
9580        if v1 >= 0 {
9581            let bucket_index1 = v1 as usize;
9582            induction_bucket[bucket_index1] -= 1;
9583            cache[ci1].symbol = induction_bucket[bucket_index1];
9584            if cache[ci1].symbol >= omp_block_start as SaSint {
9585                let ni = cache[ci1].symbol as usize;
9586                let cni = ni - start;
9587                let mut np = cache[ci1].index;
9588                cache[ci1].index = np & SAINT_MAX;
9589                if np > 0 {
9590                    np -= 1;
9591                    let np_usize = np as usize;
9592                    cache[cni].index = np
9593                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9594                            as SaSint)
9595                            << (SAINT_BIT - 1));
9596                    cache[cni].symbol = t[np_usize];
9597                }
9598            }
9599        }
9600
9601        i -= 2;
9602    }
9603
9604    j -= prefetch_distance + 1;
9605    while i >= j {
9606        let ci = i - start;
9607        let v = cache[ci].symbol;
9608        if v >= 0 {
9609            let bucket_index = v as usize;
9610            induction_bucket[bucket_index] -= 1;
9611            cache[ci].symbol = induction_bucket[bucket_index];
9612            if cache[ci].symbol >= omp_block_start as SaSint {
9613                let ni = cache[ci].symbol as usize;
9614                let cni = ni - start;
9615                let mut np = cache[ci].index;
9616                cache[ci].index = np & SAINT_MAX;
9617                if np > 0 {
9618                    np -= 1;
9619                    let np_usize = np as usize;
9620                    cache[cni].index = np
9621                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9622                            as SaSint)
9623                            << (SAINT_BIT - 1));
9624                    cache[cni].symbol = t[np_usize];
9625                }
9626            }
9627        }
9628
9629        if i == 0 {
9630            break;
9631        }
9632        i -= 1;
9633    }
9634}
9635
9636/// Internal helper: final bwt scan right to left 8u block (OpenMP variant).
9637#[doc(hidden)]
9638pub fn final_bwt_scan_right_to_left_8u_block_omp(
9639    t: &[u8],
9640    sa: &mut [SaSint],
9641    k: SaSint,
9642    induction_bucket: &mut [SaSint],
9643    block_start: FastSint,
9644    block_size: FastSint,
9645    threads: SaSint,
9646    thread_state: &mut [ThreadState],
9647) {
9648    if block_size <= 0 {
9649        return;
9650    }
9651    let k_usize = usize::try_from(k).expect("k must be non-negative");
9652    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9653    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9654    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9655    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9656        let _ = final_bwt_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9657        return;
9658    }
9659
9660    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9661    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9662        let omp_block_start = omp_thread_num * omp_block_stride;
9663        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9664            omp_block_stride
9665        } else {
9666            block_size_usize - omp_block_start
9667        };
9668        state.count = final_bwt_scan_right_to_left_8u_block_prepare(
9669            t,
9670            sa,
9671            k,
9672            &mut state.buckets,
9673            &mut state.cache,
9674            block_start + omp_block_start as FastSint,
9675            omp_block_size as FastSint,
9676        );
9677    }
9678    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9679        for c in 0..k_usize {
9680            let a = induction_bucket[c];
9681            let b = state.buckets[c];
9682            induction_bucket[c] = a - b;
9683            state.buckets[c] = a;
9684        }
9685    }
9686    for state in thread_state.iter_mut().take(omp_num_threads) {
9687        final_order_scan_right_to_left_8u_block_place(
9688            sa,
9689            &mut state.buckets,
9690            &state.cache,
9691            state.count,
9692        );
9693    }
9694}
9695
9696/// Internal helper: final bwt aux scan right to left 8u block (OpenMP variant).
9697#[doc(hidden)]
9698pub fn final_bwt_aux_scan_right_to_left_8u_block_omp(
9699    t: &[u8],
9700    sa: &mut [SaSint],
9701    k: SaSint,
9702    rm: SaSint,
9703    i_out: &mut [SaSint],
9704    induction_bucket: &mut [SaSint],
9705    block_start: FastSint,
9706    block_size: FastSint,
9707    threads: SaSint,
9708    thread_state: &mut [ThreadState],
9709) {
9710    if block_size <= 0 {
9711        return;
9712    }
9713    let k_usize = usize::try_from(k).expect("k must be non-negative");
9714    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9715    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9716    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9717    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9718        final_bwt_aux_scan_right_to_left_8u(
9719            t,
9720            sa,
9721            rm,
9722            i_out,
9723            induction_bucket,
9724            block_start,
9725            block_size,
9726        );
9727        return;
9728    }
9729
9730    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9731    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9732        let omp_block_start = omp_thread_num * omp_block_stride;
9733        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9734            omp_block_stride
9735        } else {
9736            block_size_usize - omp_block_start
9737        };
9738        state.count = final_bwt_aux_scan_right_to_left_8u_block_prepare(
9739            t,
9740            sa,
9741            k,
9742            &mut state.buckets,
9743            &mut state.cache,
9744            block_start + omp_block_start as FastSint,
9745            omp_block_size as FastSint,
9746        );
9747    }
9748    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9749        for c in 0..k_usize {
9750            let a = induction_bucket[c];
9751            let b = state.buckets[c];
9752            induction_bucket[c] = a - b;
9753            state.buckets[c] = a;
9754        }
9755    }
9756    for state in thread_state.iter_mut().take(omp_num_threads) {
9757        final_bwt_aux_scan_right_to_left_8u_block_place(
9758            sa,
9759            rm,
9760            i_out,
9761            &mut state.buckets,
9762            &state.cache,
9763            state.count,
9764        );
9765    }
9766}
9767
9768/// Internal helper: final sorting scan right to left 8u block (OpenMP variant).
9769#[doc(hidden)]
9770pub fn final_sorting_scan_right_to_left_8u_block_omp(
9771    t: &[u8],
9772    sa: &mut [SaSint],
9773    k: SaSint,
9774    induction_bucket: &mut [SaSint],
9775    block_start: FastSint,
9776    block_size: FastSint,
9777    threads: SaSint,
9778    thread_state: &mut [ThreadState],
9779) {
9780    if block_size <= 0 {
9781        return;
9782    }
9783    let k_usize = usize::try_from(k).expect("k must be non-negative");
9784    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9785    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9786    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9787    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9788        final_sorting_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9789        return;
9790    }
9791
9792    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9793    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9794        let omp_block_start = omp_thread_num * omp_block_stride;
9795        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9796            omp_block_stride
9797        } else {
9798            block_size_usize - omp_block_start
9799        };
9800        state.count = final_sorting_scan_right_to_left_8u_block_prepare(
9801            t,
9802            sa,
9803            k,
9804            &mut state.buckets,
9805            &mut state.cache,
9806            block_start + omp_block_start as FastSint,
9807            omp_block_size as FastSint,
9808        );
9809    }
9810    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9811        for c in 0..k_usize {
9812            let a = induction_bucket[c];
9813            let b = state.buckets[c];
9814            induction_bucket[c] = a - b;
9815            state.buckets[c] = a;
9816        }
9817    }
9818    for state in thread_state.iter_mut().take(omp_num_threads) {
9819        final_order_scan_right_to_left_8u_block_place(
9820            sa,
9821            &mut state.buckets,
9822            &state.cache,
9823            state.count,
9824        );
9825    }
9826}
9827
9828/// Internal helper: final gsa scan right to left 8u block (OpenMP variant).
9829#[doc(hidden)]
9830pub fn final_gsa_scan_right_to_left_8u_block_omp(
9831    t: &[u8],
9832    sa: &mut [SaSint],
9833    k: SaSint,
9834    induction_bucket: &mut [SaSint],
9835    block_start: FastSint,
9836    block_size: FastSint,
9837    threads: SaSint,
9838    thread_state: &mut [ThreadState],
9839) {
9840    if block_size <= 0 {
9841        return;
9842    }
9843    let k_usize = usize::try_from(k).expect("k must be non-negative");
9844    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9845    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9846    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9847    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9848        final_gsa_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9849        return;
9850    }
9851
9852    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9853    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9854        let omp_block_start = omp_thread_num * omp_block_stride;
9855        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9856            omp_block_stride
9857        } else {
9858            block_size_usize - omp_block_start
9859        };
9860        state.count = final_sorting_scan_right_to_left_8u_block_prepare(
9861            t,
9862            sa,
9863            k,
9864            &mut state.buckets,
9865            &mut state.cache,
9866            block_start + omp_block_start as FastSint,
9867            omp_block_size as FastSint,
9868        );
9869    }
9870    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9871        for c in 0..k_usize {
9872            let a = induction_bucket[c];
9873            let b = state.buckets[c];
9874            induction_bucket[c] = a - b;
9875            state.buckets[c] = a;
9876        }
9877    }
9878    for state in thread_state.iter_mut().take(omp_num_threads) {
9879        final_gsa_scan_right_to_left_8u_block_place(
9880            sa,
9881            &mut state.buckets,
9882            &state.cache,
9883            state.count,
9884        );
9885    }
9886}
9887
9888/// Internal helper: final sorting scan right to left 32s block (OpenMP variant).
9889#[doc(hidden)]
9890pub fn final_sorting_scan_right_to_left_32s_block_omp(
9891    t: &[SaSint],
9892    sa: &mut [SaSint],
9893    buckets: &mut [SaSint],
9894    cache: &mut [ThreadCache],
9895    block_start: FastSint,
9896    block_size: FastSint,
9897    threads: SaSint,
9898) {
9899    if threads <= 1 || block_size < 16_384 {
9900        final_sorting_scan_right_to_left_32s(t, sa, buckets, block_start, block_size);
9901        return;
9902    }
9903
9904    final_sorting_scan_right_to_left_32s_block_gather(t, sa, cache, block_start, block_size);
9905    final_sorting_scan_right_to_left_32s_block_sort(t, buckets, cache, block_start, block_size);
9906    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9907    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9908    let omp_num_threads = threads_usize.min(block_size_usize);
9909    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9910    {
9911        let sa_ptr = SyncMutPtr::new(sa);
9912        let cache_ptr = SyncMutPtr::new(cache);
9913        run_rayon_with_threads(omp_num_threads, || {
9914            (0..omp_num_threads)
9915                .into_par_iter()
9916                .for_each(|omp_thread_num| {
9917                    let omp_block_start = omp_thread_num * omp_block_stride;
9918                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9919                        omp_block_stride
9920                    } else {
9921                        block_size_usize - omp_block_start
9922                    };
9923                    // SAFETY: disjoint cache range and symbol-indexed sa writes.
9924                    let sa = unsafe { sa_ptr.as_slice() };
9925                    let cache = unsafe { cache_ptr.as_slice() };
9926                    compact_and_place_cached_suffixes(
9927                        sa,
9928                        cache,
9929                        omp_block_start as FastSint,
9930                        omp_block_size as FastSint,
9931                    );
9932                });
9933        });
9934    }
9935}
9936
9937/// Internal helper: final bwt scan right to left 8u (OpenMP variant).
9938#[doc(hidden)]
9939pub fn final_bwt_scan_right_to_left_8u_omp(
9940    t: &[u8],
9941    sa: &mut [SaSint],
9942    n: SaSint,
9943    k: SaSint,
9944    induction_bucket: &mut [SaSint],
9945    threads: SaSint,
9946    thread_state: &mut [ThreadState],
9947) -> SaSint {
9948    if threads == 1 || n < 65_536 {
9949        return final_bwt_scan_right_to_left_8u(t, sa, induction_bucket, 0, n as FastSint);
9950    }
9951    let mut index = -1;
9952    let mut block_start = usize::try_from(n).expect("n must be non-negative");
9953    while block_start > 0 {
9954        block_start -= 1;
9955        if sa[block_start] == 0 {
9956            index = block_start as SaSint;
9957        } else {
9958            let threads_usize = usize::try_from(threads)
9959                .expect("threads must be non-negative")
9960                .min(thread_state.len())
9961                .max(1);
9962            let max_back =
9963                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
9964            let block_max_end = block_start.saturating_sub(max_back);
9965            let mut block_end = block_start;
9966            while block_end > block_max_end && sa[block_end - 1] != 0 {
9967                block_end -= 1;
9968            }
9969            let size = block_start - block_end + 1;
9970            if size < 32 {
9971                let res = final_bwt_scan_right_to_left_8u(
9972                    t,
9973                    sa,
9974                    induction_bucket,
9975                    block_end as FastSint,
9976                    size as FastSint,
9977                );
9978                if res >= 0 {
9979                    index = res;
9980                }
9981            } else {
9982                final_bwt_scan_right_to_left_8u_block_omp(
9983                    t,
9984                    sa,
9985                    k,
9986                    induction_bucket,
9987                    block_end as FastSint,
9988                    size as FastSint,
9989                    threads,
9990                    thread_state,
9991                );
9992            }
9993            block_start = block_end;
9994        }
9995    }
9996    index
9997}
9998
9999/// Internal helper: final bwt aux scan right to left 8u (OpenMP variant).
10000#[doc(hidden)]
10001pub fn final_bwt_aux_scan_right_to_left_8u_omp(
10002    t: &[u8],
10003    sa: &mut [SaSint],
10004    n: SaSint,
10005    k: SaSint,
10006    rm: SaSint,
10007    i_out: &mut [SaSint],
10008    induction_bucket: &mut [SaSint],
10009    threads: SaSint,
10010    thread_state: &mut [ThreadState],
10011) {
10012    if threads == 1 || n < 65_536 {
10013        final_bwt_aux_scan_right_to_left_8u(t, sa, rm, i_out, induction_bucket, 0, n as FastSint);
10014        return;
10015    }
10016    let mut block_start = usize::try_from(n).expect("n must be non-negative");
10017    while block_start > 0 {
10018        block_start -= 1;
10019        if sa[block_start] != 0 {
10020            let threads_usize = usize::try_from(threads)
10021                .expect("threads must be non-negative")
10022                .min(thread_state.len())
10023                .max(1);
10024            let max_back = threads_usize
10025                * (LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize) / 2);
10026            let block_max_end = block_start.saturating_sub(max_back);
10027            let mut block_end = block_start;
10028            while block_end > block_max_end && sa[block_end - 1] != 0 {
10029                block_end -= 1;
10030            }
10031            let size = block_start - block_end + 1;
10032            if size < 32 {
10033                final_bwt_aux_scan_right_to_left_8u(
10034                    t,
10035                    sa,
10036                    rm,
10037                    i_out,
10038                    induction_bucket,
10039                    block_end as FastSint,
10040                    size as FastSint,
10041                );
10042            } else {
10043                final_bwt_aux_scan_right_to_left_8u_block_omp(
10044                    t,
10045                    sa,
10046                    k,
10047                    rm,
10048                    i_out,
10049                    induction_bucket,
10050                    block_end as FastSint,
10051                    size as FastSint,
10052                    threads,
10053                    thread_state,
10054                );
10055            }
10056            block_start = block_end;
10057        }
10058    }
10059}
10060
10061/// Internal helper: final sorting scan right to left 8u (OpenMP variant).
10062#[doc(hidden)]
10063pub fn final_sorting_scan_right_to_left_8u_omp(
10064    t: &[u8],
10065    sa: &mut [SaSint],
10066    omp_block_start: FastSint,
10067    omp_block_size: FastSint,
10068    k: SaSint,
10069    induction_bucket: &mut [SaSint],
10070    threads: SaSint,
10071    thread_state: &mut [ThreadState],
10072) {
10073    if threads == 1 || omp_block_size < 65_536 {
10074        final_sorting_scan_right_to_left_8u(
10075            t,
10076            sa,
10077            induction_bucket,
10078            omp_block_start,
10079            omp_block_size,
10080        );
10081        return;
10082    }
10083    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10084    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10085    let mut block_start = start + size;
10086    while block_start > start {
10087        block_start -= 1;
10088        if sa[block_start] != 0 {
10089            let threads_usize = usize::try_from(threads)
10090                .expect("threads must be non-negative")
10091                .min(thread_state.len())
10092                .max(1);
10093            let max_back =
10094                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
10095            let block_max_end = block_start.saturating_sub(max_back).max(start);
10096            let mut block_end = block_start;
10097            while block_end > block_max_end && sa[block_end - 1] != 0 {
10098                block_end -= 1;
10099            }
10100            let span = block_start - block_end + 1;
10101            if span < 32 {
10102                final_sorting_scan_right_to_left_8u(
10103                    t,
10104                    sa,
10105                    induction_bucket,
10106                    block_end as FastSint,
10107                    span as FastSint,
10108                );
10109            } else {
10110                final_sorting_scan_right_to_left_8u_block_omp(
10111                    t,
10112                    sa,
10113                    k,
10114                    induction_bucket,
10115                    block_end as FastSint,
10116                    span as FastSint,
10117                    threads,
10118                    thread_state,
10119                );
10120            }
10121            block_start = block_end;
10122        }
10123    }
10124}
10125
10126/// Internal helper: final gsa scan right to left 8u (OpenMP variant).
10127#[doc(hidden)]
10128pub fn final_gsa_scan_right_to_left_8u_omp(
10129    t: &[u8],
10130    sa: &mut [SaSint],
10131    omp_block_start: FastSint,
10132    omp_block_size: FastSint,
10133    k: SaSint,
10134    induction_bucket: &mut [SaSint],
10135    threads: SaSint,
10136    thread_state: &mut [ThreadState],
10137) {
10138    if threads == 1 || omp_block_size < 65_536 {
10139        final_gsa_scan_right_to_left_8u(t, sa, induction_bucket, omp_block_start, omp_block_size);
10140        return;
10141    }
10142    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10143    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10144    let mut block_start = start + size;
10145    while block_start > start {
10146        block_start -= 1;
10147        if sa[block_start] != 0 {
10148            let threads_usize = usize::try_from(threads)
10149                .expect("threads must be non-negative")
10150                .min(thread_state.len())
10151                .max(1);
10152            let max_back =
10153                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
10154            let block_max_end = block_start.saturating_sub(max_back).max(start);
10155            let mut block_end = block_start;
10156            while block_end > block_max_end && sa[block_end - 1] != 0 {
10157                block_end -= 1;
10158            }
10159            let span = block_start - block_end + 1;
10160            if span < 32 {
10161                final_gsa_scan_right_to_left_8u(
10162                    t,
10163                    sa,
10164                    induction_bucket,
10165                    block_end as FastSint,
10166                    span as FastSint,
10167                );
10168            } else {
10169                final_gsa_scan_right_to_left_8u_block_omp(
10170                    t,
10171                    sa,
10172                    k,
10173                    induction_bucket,
10174                    block_end as FastSint,
10175                    span as FastSint,
10176                    threads,
10177                    thread_state,
10178                );
10179            }
10180            block_start = block_end;
10181        }
10182    }
10183}
10184
10185/// Internal helper: final sorting scan right to left 32s (OpenMP variant).
10186#[doc(hidden)]
10187pub fn final_sorting_scan_right_to_left_32s_omp(
10188    t: &[SaSint],
10189    sa: &mut [SaSint],
10190    n: SaSint,
10191    induction_bucket: &mut [SaSint],
10192    threads: SaSint,
10193    thread_state: &mut [ThreadState],
10194) {
10195    if threads == 1 || n < 65_536 {
10196        final_sorting_scan_right_to_left_32s(t, sa, induction_bucket, 0, n as FastSint);
10197        return;
10198    }
10199    if thread_state.is_empty() {
10200        final_sorting_scan_right_to_left_32s(t, sa, induction_bucket, 0, n as FastSint);
10201        return;
10202    }
10203    let threads_usize = usize::try_from(threads)
10204        .expect("threads must be non-negative")
10205        .max(1);
10206    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
10207    let mut block_start = isize::try_from(n).expect("n must fit isize") - 1;
10208    while block_start >= 0 {
10209        let block_end = (block_start
10210            - isize::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
10211                .expect("block span must fit isize"))
10212        .max(-1);
10213        final_sorting_scan_right_to_left_32s_block_omp(
10214            t,
10215            sa,
10216            induction_bucket,
10217            &mut cache,
10218            (block_end + 1) as FastSint,
10219            (block_start - block_end) as FastSint,
10220            threads,
10221        );
10222        block_start = block_end;
10223    }
10224}
10225
10226/// Internal helper: clear lms suffixes (OpenMP variant).
10227#[doc(hidden)]
10228pub fn clear_lms_suffixes_omp(
10229    sa: &mut [SaSint],
10230    n: SaSint,
10231    k: SaSint,
10232    bucket_start: &[SaSint],
10233    bucket_end: &[SaSint],
10234    threads: SaSint,
10235) {
10236    let k_usize = usize::try_from(k).expect("k must be non-negative");
10237    let thread_count = if threads > 1 && n >= 65536 {
10238        usize::try_from(threads).expect("threads must be positive")
10239    } else {
10240        1
10241    };
10242    {
10243        let sa_ptr = SyncMutPtr::new(sa);
10244        let bucket_start_ref: &[SaSint] = bucket_start;
10245        let bucket_end_ref: &[SaSint] = bucket_end;
10246        run_rayon_with_threads(thread_count, || {
10247            (0..thread_count).into_par_iter().for_each(|t| {
10248                let mut c = t;
10249                // SAFETY: each c maps to a disjoint sa[bucket_start[c]..bucket_end[c]]
10250                // range; threads iterate c with stride thread_count.
10251                let sa = unsafe { sa_ptr.as_slice() };
10252                while c < k_usize {
10253                    if bucket_end_ref[c] > bucket_start_ref[c] {
10254                        let start = usize::try_from(bucket_start_ref[c])
10255                            .expect("bucket start must be non-negative");
10256                        let end = usize::try_from(bucket_end_ref[c])
10257                            .expect("bucket end must be non-negative");
10258                        sa[start..end].fill(0);
10259                    }
10260                    c += thread_count;
10261                }
10262            });
10263        });
10264    }
10265}
10266
10267/// Internal helper: induce final order 8u (OpenMP variant).
10268#[doc(hidden)]
10269pub fn induce_final_order_8u_omp(
10270    t: &[u8],
10271    sa: &mut [SaSint],
10272    n: SaSint,
10273    k: SaSint,
10274    flags: SaSint,
10275    r: SaSint,
10276    i_out: Option<&mut [SaSint]>,
10277    buckets: &mut [SaSint],
10278    threads: SaSint,
10279    thread_state: &mut [ThreadState],
10280) -> SaSint {
10281    if (flags & LIBSAIS_FLAGS_BWT) == 0 {
10282        if (flags & LIBSAIS_FLAGS_GSA) != 0 {
10283            buckets[6 * ALPHABET_SIZE] = buckets[7 * ALPHABET_SIZE] - 1;
10284        }
10285
10286        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
10287        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
10288        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
10289
10290        final_sorting_scan_left_to_right_8u_omp(
10291            t,
10292            sa,
10293            n as FastSint,
10294            k,
10295            bucket_start,
10296            threads,
10297            thread_state,
10298        );
10299        if threads > 1 && n >= 65_536 {
10300            clear_lms_suffixes_omp(
10301                sa,
10302                n,
10303                ALPHABET_SIZE as SaSint,
10304                bucket_start,
10305                bucket_end,
10306                threads,
10307            );
10308        }
10309
10310        if (flags & LIBSAIS_FLAGS_GSA) != 0 {
10311            flip_suffix_markers_omp(sa, bucket_end[0], threads);
10312            final_gsa_scan_right_to_left_8u_omp(
10313                t,
10314                sa,
10315                bucket_end[0] as FastSint,
10316                n as FastSint - bucket_end[0] as FastSint,
10317                k,
10318                bucket_end,
10319                1,
10320                thread_state,
10321            );
10322        } else {
10323            final_sorting_scan_right_to_left_8u_omp(
10324                t,
10325                sa,
10326                0,
10327                n as FastSint,
10328                k,
10329                bucket_end,
10330                threads,
10331                thread_state,
10332            );
10333        }
10334
10335        0
10336    } else if let Some(i_out) = i_out {
10337        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
10338        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
10339        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
10340
10341        final_bwt_aux_scan_left_to_right_8u_omp(
10342            t,
10343            sa,
10344            n as FastSint,
10345            k,
10346            r - 1,
10347            i_out,
10348            bucket_start,
10349            threads,
10350            thread_state,
10351        );
10352        if threads > 1 && n >= 65_536 {
10353            clear_lms_suffixes_omp(
10354                sa,
10355                n,
10356                ALPHABET_SIZE as SaSint,
10357                bucket_start,
10358                bucket_end,
10359                threads,
10360            );
10361        }
10362        final_bwt_aux_scan_right_to_left_8u_omp(
10363            t,
10364            sa,
10365            n,
10366            k,
10367            r - 1,
10368            i_out,
10369            bucket_end,
10370            threads,
10371            thread_state,
10372        );
10373        0
10374    } else {
10375        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
10376        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
10377        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
10378
10379        final_bwt_scan_left_to_right_8u_omp(
10380            t,
10381            sa,
10382            n as FastSint,
10383            k,
10384            bucket_start,
10385            threads,
10386            thread_state,
10387        );
10388        if threads > 1 && n >= 65_536 {
10389            clear_lms_suffixes_omp(
10390                sa,
10391                n,
10392                ALPHABET_SIZE as SaSint,
10393                bucket_start,
10394                bucket_end,
10395                threads,
10396            );
10397        }
10398        final_bwt_scan_right_to_left_8u_omp(t, sa, n, k, bucket_end, threads, thread_state)
10399    }
10400}
10401
10402/// Internal helper: induce final order 32s 6k.
10403#[doc(hidden)]
10404pub fn induce_final_order_32s_6k(
10405    t: &[SaSint],
10406    sa: &mut [SaSint],
10407    n: SaSint,
10408    k: SaSint,
10409    buckets: &mut [SaSint],
10410    threads: SaSint,
10411    thread_state: &mut [ThreadState],
10412) {
10413    let k_usize = usize::try_from(k).expect("k must be non-negative");
10414    let (_head, tail) = buckets.split_at_mut(4 * k_usize);
10415    let (left, right) = tail.split_at_mut(k_usize);
10416    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10417    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10418}
10419
10420/// Internal helper: induce final order 32s 4k.
10421#[doc(hidden)]
10422pub fn induce_final_order_32s_4k(
10423    t: &[SaSint],
10424    sa: &mut [SaSint],
10425    n: SaSint,
10426    k: SaSint,
10427    buckets: &mut [SaSint],
10428    threads: SaSint,
10429    thread_state: &mut [ThreadState],
10430) {
10431    let k_usize = usize::try_from(k).expect("k must be non-negative");
10432    let (_head, tail) = buckets.split_at_mut(2 * k_usize);
10433    let (left, right) = tail.split_at_mut(k_usize);
10434    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10435    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10436}
10437
10438/// Internal helper: induce final order 32s 2k.
10439#[doc(hidden)]
10440pub fn induce_final_order_32s_2k(
10441    t: &[SaSint],
10442    sa: &mut [SaSint],
10443    n: SaSint,
10444    k: SaSint,
10445    buckets: &mut [SaSint],
10446    threads: SaSint,
10447    thread_state: &mut [ThreadState],
10448) {
10449    let k_usize = usize::try_from(k).expect("k must be non-negative");
10450    let (right, left) = buckets.split_at_mut(k_usize);
10451    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10452    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10453}
10454
10455/// Internal helper: induce final order 32s 1k.
10456#[doc(hidden)]
10457pub fn induce_final_order_32s_1k(
10458    t: &[SaSint],
10459    sa: &mut [SaSint],
10460    n: SaSint,
10461    k: SaSint,
10462    buckets: &mut [SaSint],
10463    threads: SaSint,
10464    thread_state: &mut [ThreadState],
10465) {
10466    count_suffixes_32s(t, n, k, buckets);
10467    initialize_buckets_start_32s_1k(k, buckets);
10468    final_sorting_scan_left_to_right_32s_omp(t, sa, n, buckets, threads, thread_state);
10469
10470    count_suffixes_32s(t, n, k, buckets);
10471    initialize_buckets_end_32s_1k(k, buckets);
10472    final_sorting_scan_right_to_left_32s_omp(t, sa, n, buckets, threads, thread_state);
10473}
10474
10475/// Internal helper: renumber unique and nonunique lms suffixes 32s.
10476#[doc(hidden)]
10477pub fn renumber_unique_and_nonunique_lms_suffixes_32s(
10478    t: &mut [SaSint],
10479    sa: &mut [SaSint],
10480    m: SaSint,
10481    mut f: SaSint,
10482    omp_block_start: FastSint,
10483    omp_block_size: FastSint,
10484) -> SaSint {
10485    if omp_block_size <= 0 {
10486        return f;
10487    }
10488
10489    let prefetch_distance = 64 as SaSint;
10490    let m_usize = usize::try_from(m).expect("m must be non-negative");
10491    let (sa_head, sam) = sa.split_at_mut(m_usize);
10492    let mut i = omp_block_start as SaSint;
10493    let mut j = omp_block_start as SaSint + omp_block_size as SaSint - 2 * prefetch_distance - 3;
10494
10495    while i < j {
10496        let p0 = sa_head[i as usize] as SaUint;
10497        let p0_half = (p0 >> 1) as usize;
10498        let mut s0 = sam[p0_half];
10499        if s0 < 0 {
10500            t[p0 as usize] |= SAINT_MIN;
10501            f += 1;
10502            s0 = i + SAINT_MIN + f;
10503        }
10504        sam[p0_half] = s0 - f;
10505
10506        let p1 = sa_head[(i + 1) as usize] as SaUint;
10507        let p1_half = (p1 >> 1) as usize;
10508        let mut s1 = sam[p1_half];
10509        if s1 < 0 {
10510            t[p1 as usize] |= SAINT_MIN;
10511            f += 1;
10512            s1 = i + 1 + SAINT_MIN + f;
10513        }
10514        sam[p1_half] = s1 - f;
10515
10516        let p2 = sa_head[(i + 2) as usize] as SaUint;
10517        let p2_half = (p2 >> 1) as usize;
10518        let mut s2 = sam[p2_half];
10519        if s2 < 0 {
10520            t[p2 as usize] |= SAINT_MIN;
10521            f += 1;
10522            s2 = i + 2 + SAINT_MIN + f;
10523        }
10524        sam[p2_half] = s2 - f;
10525
10526        let p3 = sa_head[(i + 3) as usize] as SaUint;
10527        let p3_half = (p3 >> 1) as usize;
10528        let mut s3 = sam[p3_half];
10529        if s3 < 0 {
10530            t[p3 as usize] |= SAINT_MIN;
10531            f += 1;
10532            s3 = i + 3 + SAINT_MIN + f;
10533        }
10534        sam[p3_half] = s3 - f;
10535
10536        i += 4;
10537    }
10538
10539    j += 2 * prefetch_distance + 3;
10540    while i < j {
10541        let p = sa_head[i as usize] as SaUint;
10542        let p_half = (p >> 1) as usize;
10543        let mut s = sam[p_half];
10544        if s < 0 {
10545            t[p as usize] |= SAINT_MIN;
10546            f += 1;
10547            s = i + SAINT_MIN + f;
10548        }
10549        sam[p_half] = s - f;
10550        i += 1;
10551    }
10552
10553    f
10554}
10555
10556/// Internal helper: compact unique and nonunique lms suffixes 32s.
10557#[doc(hidden)]
10558pub fn compact_unique_and_nonunique_lms_suffixes_32s(
10559    sa: &mut [SaSint],
10560    m: SaSint,
10561    pl: &mut FastSint,
10562    pr: &mut FastSint,
10563    omp_block_start: FastSint,
10564    omp_block_size: FastSint,
10565) {
10566    if omp_block_size <= 0 {
10567        return;
10568    }
10569
10570    let m_usize = usize::try_from(m).expect("m must be non-negative");
10571    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10572    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10573
10574    let source: Vec<SaSint> = sa[m_usize + start..m_usize + start + size].to_vec();
10575    let mut l = usize::try_from(*pl - 1).expect("left position must be positive");
10576    let mut r = usize::try_from(*pr - 1).expect("right position must be positive");
10577
10578    for &p in source.iter().rev() {
10579        let pu = p as SaUint;
10580        sa[l] = (pu & SAINT_MAX as SaUint) as SaSint;
10581        l = l.saturating_sub(usize::from((pu as SaSint) < 0));
10582
10583        sa[r] = pu.wrapping_sub(1) as SaSint;
10584        r = r.saturating_sub(usize::from((pu as SaSint) > 0));
10585    }
10586
10587    *pl = l as FastSint + 1;
10588    *pr = r as FastSint + 1;
10589}
10590
10591/// Internal helper: count unique suffixes.
10592#[doc(hidden)]
10593pub fn count_unique_suffixes(
10594    sa: &[SaSint],
10595    m: SaSint,
10596    omp_block_start: FastSint,
10597    omp_block_size: FastSint,
10598) -> SaSint {
10599    if omp_block_size <= 0 {
10600        return 0;
10601    }
10602
10603    let m_usize = usize::try_from(m).expect("m must be non-negative");
10604    let sam = &sa[m_usize..];
10605    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10606    let block_end =
10607        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10608    let j = block_end.saturating_sub(67);
10609    let mut f0 = 0;
10610    let mut f1 = 0;
10611    let mut f2 = 0;
10612    let mut f3 = 0;
10613
10614    while i < j {
10615        f0 += SaSint::from(
10616            sam[usize::try_from((sa[i] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10617        );
10618        f1 += SaSint::from(
10619            sam[usize::try_from((sa[i + 1] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10620        );
10621        f2 += SaSint::from(
10622            sam[usize::try_from((sa[i + 2] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10623        );
10624        f3 += SaSint::from(
10625            sam[usize::try_from((sa[i + 3] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10626        );
10627        i += 4;
10628    }
10629
10630    while i < block_end {
10631        f0 += SaSint::from(
10632            sam[usize::try_from((sa[i] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10633        );
10634        i += 1;
10635    }
10636
10637    f0 + f1 + f2 + f3
10638}
10639
10640/// Internal helper: renumber unique and nonunique lms suffixes 32s (OpenMP variant).
10641#[doc(hidden)]
10642pub fn renumber_unique_and_nonunique_lms_suffixes_32s_omp(
10643    t: &mut [SaSint],
10644    sa: &mut [SaSint],
10645    m: SaSint,
10646    threads: SaSint,
10647    thread_state: &mut [ThreadState],
10648) -> SaSint {
10649    let f = if threads == 1 || m < 65_536 {
10650        renumber_unique_and_nonunique_lms_suffixes_32s(t, sa, m, 0, 0, m as FastSint)
10651    } else {
10652        let threads_usize = usize::try_from(threads)
10653            .expect("threads must be non-negative")
10654            .max(1);
10655        let m_usize = usize::try_from(m).expect("m must be non-negative");
10656        let omp_num_threads = threads_usize.min(m_usize.max(1));
10657        let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
10658
10659        {
10660            let sa_ro: &[SaSint] = sa;
10661            run_rayon_with_threads(omp_num_threads, || {
10662                thread_state[..omp_num_threads]
10663                    .par_iter_mut()
10664                    .enumerate()
10665                    .for_each(|(omp_thread_num, state)| {
10666                        let omp_block_start = omp_thread_num * omp_block_stride;
10667                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10668                            omp_block_stride
10669                        } else {
10670                            m_usize - omp_block_start
10671                        };
10672                        state.count = count_unique_suffixes(
10673                            sa_ro,
10674                            m,
10675                            omp_block_start as FastSint,
10676                            omp_block_size as FastSint,
10677                        ) as FastSint;
10678                    });
10679            });
10680        }
10681
10682        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
10683            .iter()
10684            .map(|s| s.count)
10685            .collect();
10686        let f = counts.iter().sum::<FastSint>() as SaSint;
10687
10688        {
10689            let sa_ptr = SyncMutPtr::new(sa);
10690            let t_ptr = SyncMutPtr::new(t);
10691            let counts_ref: &[FastSint] = &counts;
10692            run_rayon_with_threads(omp_num_threads, || {
10693                (0..omp_num_threads)
10694                    .into_par_iter()
10695                    .for_each(|omp_thread_num| {
10696                        let omp_block_start = omp_thread_num * omp_block_stride;
10697                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10698                            omp_block_stride
10699                        } else {
10700                            m_usize - omp_block_start
10701                        };
10702
10703                        let mut count: FastSint = 0;
10704                        for tt in 0..omp_thread_num {
10705                            count += counts_ref[tt];
10706                        }
10707
10708                        // SAFETY: per-thread disjoint sa and t blocks (block_start..+block_size).
10709                        let sa = unsafe { sa_ptr.as_slice() };
10710                        let t = unsafe { t_ptr.as_slice() };
10711                        renumber_unique_and_nonunique_lms_suffixes_32s(
10712                            t,
10713                            sa,
10714                            m,
10715                            count as SaSint,
10716                            omp_block_start as FastSint,
10717                            omp_block_size as FastSint,
10718                        );
10719                    });
10720            });
10721        }
10722        f
10723    };
10724
10725    f
10726}
10727
10728/// Internal helper: compact unique and nonunique lms suffixes 32s (OpenMP variant).
10729#[doc(hidden)]
10730pub fn compact_unique_and_nonunique_lms_suffixes_32s_omp(
10731    sa: &mut [SaSint],
10732    n: SaSint,
10733    m: SaSint,
10734    fs: SaSint,
10735    f: SaSint,
10736    threads: SaSint,
10737    thread_state: &mut [ThreadState],
10738) {
10739    let half_n = (n as FastSint) >> 1;
10740    if threads == 1 || n < 131_072 || m >= fs {
10741        let mut l = m as FastSint;
10742        let mut r = n as FastSint + fs as FastSint;
10743        compact_unique_and_nonunique_lms_suffixes_32s(sa, m, &mut l, &mut r, 0, half_n);
10744    } else {
10745        let threads_usize = usize::try_from(threads)
10746            .expect("threads must be non-negative")
10747            .max(1);
10748        let half_n_usize = usize::try_from(half_n).expect("half_n must be non-negative");
10749        let omp_num_threads = threads_usize.min(half_n_usize.max(1));
10750        let omp_block_stride = (half_n_usize / omp_num_threads) & !15usize;
10751
10752        {
10753            let sa_ptr = SyncMutPtr::new(sa);
10754            run_rayon_with_threads(omp_num_threads, || {
10755                thread_state[..omp_num_threads]
10756                    .par_iter_mut()
10757                    .enumerate()
10758                    .for_each(|(omp_thread_num, state)| {
10759                        let omp_block_start = omp_thread_num * omp_block_stride;
10760                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10761                            omp_block_stride
10762                        } else {
10763                            half_n_usize - omp_block_start
10764                        };
10765
10766                        let mut position = m as FastSint
10767                            + half_n
10768                            + omp_block_start as FastSint
10769                            + omp_block_size as FastSint;
10770                        let mut count = m as FastSint
10771                            + omp_block_start as FastSint
10772                            + omp_block_size as FastSint;
10773
10774                        // SAFETY: per-thread disjoint sa block.
10775                        let sa = unsafe { sa_ptr.as_slice() };
10776                        compact_unique_and_nonunique_lms_suffixes_32s(
10777                            sa,
10778                            m,
10779                            &mut position,
10780                            &mut count,
10781                            omp_block_start as FastSint,
10782                            omp_block_size as FastSint,
10783                        );
10784                        state.position = position;
10785                        state.count = count;
10786                    });
10787            });
10788        }
10789
10790        let mut position = m as FastSint;
10791        for t in (0..omp_num_threads).rev() {
10792            let omp_block_end = if t + 1 < omp_num_threads {
10793                omp_block_stride * (t + 1)
10794            } else {
10795                half_n_usize
10796            };
10797            let count =
10798                m as FastSint + half_n + omp_block_end as FastSint - thread_state[t].position;
10799            if count > 0 {
10800                position -= count;
10801                let dst = usize::try_from(position).expect("destination must be non-negative");
10802                let src =
10803                    usize::try_from(thread_state[t].position).expect("source must be non-negative");
10804                let len = usize::try_from(count).expect("length must be non-negative");
10805                sa.copy_within(src..src + len, dst);
10806            }
10807        }
10808
10809        let mut position = n as FastSint + fs as FastSint;
10810        for t in (0..omp_num_threads).rev() {
10811            let omp_block_end = if t + 1 < omp_num_threads {
10812                omp_block_stride * (t + 1)
10813            } else {
10814                half_n_usize
10815            };
10816            let count = m as FastSint + omp_block_end as FastSint - thread_state[t].count;
10817            if count > 0 {
10818                position -= count;
10819                let dst = usize::try_from(position).expect("destination must be non-negative");
10820                let src =
10821                    usize::try_from(thread_state[t].count).expect("source must be non-negative");
10822                let len = usize::try_from(count).expect("length must be non-negative");
10823                sa.copy_within(src..src + len, dst);
10824            }
10825        }
10826    }
10827
10828    let copy_dst = usize::try_from(n + fs - m).expect("copy destination must be non-negative");
10829    let copy_src = usize::try_from(m - f).expect("copy source must be non-negative");
10830    let copy_len = usize::try_from(f).expect("copy length must be non-negative");
10831    sa.copy_within(copy_src..copy_src + copy_len, copy_dst);
10832}
10833
10834/// Internal helper: compact lms suffixes 32s (OpenMP variant).
10835#[doc(hidden)]
10836pub fn compact_lms_suffixes_32s_omp(
10837    t: &mut [SaSint],
10838    sa: &mut [SaSint],
10839    n: SaSint,
10840    m: SaSint,
10841    fs: SaSint,
10842    threads: SaSint,
10843    thread_state: &mut [ThreadState],
10844) -> SaSint {
10845    let f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(t, sa, m, threads, thread_state);
10846    compact_unique_and_nonunique_lms_suffixes_32s_omp(sa, n, m, fs, f, threads, thread_state);
10847    f
10848}
10849
10850/// Internal helper: merge unique lms suffixes 32s.
10851#[doc(hidden)]
10852pub fn merge_unique_lms_suffixes_32s(
10853    t: &mut [SaSint],
10854    sa: &mut [SaSint],
10855    n: SaSint,
10856    m: SaSint,
10857    l: FastSint,
10858    omp_block_start: FastSint,
10859    omp_block_size: FastSint,
10860) {
10861    if omp_block_size <= 0 {
10862        return;
10863    }
10864
10865    let n_usize = usize::try_from(n).expect("n must be non-negative");
10866    let m_usize = usize::try_from(m).expect("m must be non-negative");
10867    let mut src_index = n_usize - m_usize - 1 + usize::try_from(l).expect("l must be non-negative");
10868    let mut tmp = sa[src_index] as FastSint;
10869    src_index += 1;
10870
10871    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10872    let block_end =
10873        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10874    let j = block_end.saturating_sub(6);
10875    while i < j {
10876        let c0 = t[i];
10877        if c0 < 0 {
10878            t[i] = c0 & SAINT_MAX;
10879            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint;
10880            i += 1;
10881            tmp = sa[src_index] as FastSint;
10882            src_index += 1;
10883        }
10884
10885        let c1 = t[i + 1];
10886        if c1 < 0 {
10887            t[i + 1] = c1 & SAINT_MAX;
10888            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 1;
10889            i += 1;
10890            tmp = sa[src_index] as FastSint;
10891            src_index += 1;
10892        }
10893
10894        let c2 = t[i + 2];
10895        if c2 < 0 {
10896            t[i + 2] = c2 & SAINT_MAX;
10897            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 2;
10898            i += 1;
10899            tmp = sa[src_index] as FastSint;
10900            src_index += 1;
10901        }
10902
10903        let c3 = t[i + 3];
10904        if c3 < 0 {
10905            t[i + 3] = c3 & SAINT_MAX;
10906            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 3;
10907            i += 1;
10908            tmp = sa[src_index] as FastSint;
10909            src_index += 1;
10910        }
10911
10912        i += 4;
10913    }
10914
10915    while i < block_end {
10916        let c = t[i];
10917        if c < 0 {
10918            t[i] = c & SAINT_MAX;
10919            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint;
10920            i += 1;
10921            tmp = sa[src_index] as FastSint;
10922            src_index += 1;
10923        }
10924        i += 1;
10925    }
10926}
10927
10928/// Internal helper: merge nonunique lms suffixes 32s.
10929#[doc(hidden)]
10930pub fn merge_nonunique_lms_suffixes_32s(
10931    sa: &mut [SaSint],
10932    n: SaSint,
10933    m: SaSint,
10934    l: FastSint,
10935    omp_block_start: FastSint,
10936    omp_block_size: FastSint,
10937) {
10938    if omp_block_size <= 0 {
10939        return;
10940    }
10941
10942    let n_usize = usize::try_from(n).expect("n must be non-negative");
10943    let m_usize = usize::try_from(m).expect("m must be non-negative");
10944    let mut src_index = n_usize - m_usize - 1 + usize::try_from(l).expect("l must be non-negative");
10945    let mut tmp = sa[src_index];
10946    src_index += 1;
10947
10948    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10949    let block_end =
10950        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10951    let j = block_end.saturating_sub(3);
10952    while i < j {
10953        if sa[i] == 0 {
10954            sa[i] = tmp;
10955            tmp = sa[src_index];
10956            src_index += 1;
10957        }
10958        if sa[i + 1] == 0 {
10959            sa[i + 1] = tmp;
10960            tmp = sa[src_index];
10961            src_index += 1;
10962        }
10963        if sa[i + 2] == 0 {
10964            sa[i + 2] = tmp;
10965            tmp = sa[src_index];
10966            src_index += 1;
10967        }
10968        if sa[i + 3] == 0 {
10969            sa[i + 3] = tmp;
10970            tmp = sa[src_index];
10971            src_index += 1;
10972        }
10973        i += 4;
10974    }
10975
10976    while i < block_end {
10977        if sa[i] == 0 {
10978            sa[i] = tmp;
10979            tmp = sa[src_index];
10980            src_index += 1;
10981        }
10982        i += 1;
10983    }
10984}
10985
10986/// Internal helper: merge unique lms suffixes 32s (OpenMP variant).
10987#[doc(hidden)]
10988pub fn merge_unique_lms_suffixes_32s_omp(
10989    t: &mut [SaSint],
10990    sa: &mut [SaSint],
10991    n: SaSint,
10992    m: SaSint,
10993    threads: SaSint,
10994    thread_state: &mut [ThreadState],
10995) {
10996    if threads == 1 || n < 65_536 {
10997        merge_unique_lms_suffixes_32s(t, sa, n, m, 0, 0, n as FastSint);
10998        return;
10999    }
11000
11001    let threads_usize = usize::try_from(threads)
11002        .expect("threads must be non-negative")
11003        .max(1);
11004    let n_usize = usize::try_from(n).expect("n must be non-negative");
11005    let omp_num_threads = threads_usize.min(n_usize.max(1));
11006    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
11007
11008    {
11009        let t_ro: &[SaSint] = t;
11010        run_rayon_with_threads(omp_num_threads, || {
11011            thread_state[..omp_num_threads]
11012                .par_iter_mut()
11013                .enumerate()
11014                .for_each(|(omp_thread_num, state)| {
11015                    let omp_block_start = omp_thread_num * omp_block_stride;
11016                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
11017                        omp_block_stride
11018                    } else {
11019                        n_usize - omp_block_start
11020                    };
11021                    state.count = count_negative_marked_suffixes(
11022                        t_ro,
11023                        omp_block_start as FastSint,
11024                        omp_block_size as FastSint,
11025                    ) as FastSint;
11026                });
11027        });
11028    }
11029
11030    let counts: Vec<FastSint> = thread_state[..omp_num_threads]
11031        .iter()
11032        .map(|s| s.count)
11033        .collect();
11034
11035    {
11036        let sa_ptr = SyncMutPtr::new(sa);
11037        let t_ptr = SyncMutPtr::new(t);
11038        let counts_ref: &[FastSint] = &counts;
11039        run_rayon_with_threads(omp_num_threads, || {
11040            (0..omp_num_threads)
11041                .into_par_iter()
11042                .for_each(|omp_thread_num| {
11043                    let omp_block_start = omp_thread_num * omp_block_stride;
11044                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
11045                        omp_block_stride
11046                    } else {
11047                        n_usize - omp_block_start
11048                    };
11049
11050                    let mut count: FastSint = 0;
11051                    for tt in 0..omp_thread_num {
11052                        count += counts_ref[tt];
11053                    }
11054
11055                    // SAFETY: per-thread disjoint block in both t (write) and sa.
11056                    let sa = unsafe { sa_ptr.as_slice() };
11057                    let t = unsafe { t_ptr.as_slice() };
11058                    merge_unique_lms_suffixes_32s(
11059                        t,
11060                        sa,
11061                        n,
11062                        m,
11063                        count,
11064                        omp_block_start as FastSint,
11065                        omp_block_size as FastSint,
11066                    );
11067                });
11068        });
11069    }
11070}
11071
11072/// Internal helper: merge nonunique lms suffixes 32s (OpenMP variant).
11073#[doc(hidden)]
11074pub fn merge_nonunique_lms_suffixes_32s_omp(
11075    sa: &mut [SaSint],
11076    n: SaSint,
11077    m: SaSint,
11078    f: SaSint,
11079    threads: SaSint,
11080    thread_state: &mut [ThreadState],
11081) {
11082    if threads == 1 || m < 65_536 {
11083        merge_nonunique_lms_suffixes_32s(sa, n, m, f as FastSint, 0, m as FastSint);
11084        return;
11085    }
11086
11087    let threads_usize = usize::try_from(threads)
11088        .expect("threads must be non-negative")
11089        .max(1);
11090    let m_usize = usize::try_from(m).expect("m must be non-negative");
11091    let omp_num_threads = threads_usize.min(m_usize.max(1));
11092    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
11093
11094    {
11095        let sa_ro: &[SaSint] = sa;
11096        run_rayon_with_threads(omp_num_threads, || {
11097            thread_state[..omp_num_threads]
11098                .par_iter_mut()
11099                .enumerate()
11100                .for_each(|(omp_thread_num, state)| {
11101                    let omp_block_start = omp_thread_num * omp_block_stride;
11102                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
11103                        omp_block_stride
11104                    } else {
11105                        m_usize - omp_block_start
11106                    };
11107                    state.count = count_zero_marked_suffixes(
11108                        sa_ro,
11109                        omp_block_start as FastSint,
11110                        omp_block_size as FastSint,
11111                    ) as FastSint;
11112                });
11113        });
11114    }
11115
11116    let counts: Vec<FastSint> = thread_state[..omp_num_threads]
11117        .iter()
11118        .map(|s| s.count)
11119        .collect();
11120
11121    {
11122        let sa_ptr = SyncMutPtr::new(sa);
11123        let counts_ref: &[FastSint] = &counts;
11124        run_rayon_with_threads(omp_num_threads, || {
11125            (0..omp_num_threads)
11126                .into_par_iter()
11127                .for_each(|omp_thread_num| {
11128                    let omp_block_start = omp_thread_num * omp_block_stride;
11129                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
11130                        omp_block_stride
11131                    } else {
11132                        m_usize - omp_block_start
11133                    };
11134
11135                    let mut count: FastSint = f as FastSint;
11136                    for tt in 0..omp_thread_num {
11137                        count += counts_ref[tt];
11138                    }
11139
11140                    // SAFETY: per-thread disjoint sa block.
11141                    let sa = unsafe { sa_ptr.as_slice() };
11142                    merge_nonunique_lms_suffixes_32s(
11143                        sa,
11144                        n,
11145                        m,
11146                        count,
11147                        omp_block_start as FastSint,
11148                        omp_block_size as FastSint,
11149                    );
11150                });
11151        });
11152    }
11153}
11154
11155/// Internal helper: merge compacted lms suffixes 32s (OpenMP variant).
11156#[doc(hidden)]
11157pub fn merge_compacted_lms_suffixes_32s_omp(
11158    t: &mut [SaSint],
11159    sa: &mut [SaSint],
11160    n: SaSint,
11161    m: SaSint,
11162    f: SaSint,
11163    threads: SaSint,
11164    thread_state: &mut [ThreadState],
11165) {
11166    merge_unique_lms_suffixes_32s_omp(t, sa, n, m, threads, thread_state);
11167    merge_nonunique_lms_suffixes_32s_omp(sa, n, m, f, threads, thread_state);
11168}
11169
11170/// Internal helper: reconstruct compacted lms suffixes 32s 2k (OpenMP variant).
11171#[doc(hidden)]
11172pub fn reconstruct_compacted_lms_suffixes_32s_2k_omp(
11173    t: &mut [SaSint],
11174    sa: &mut [SaSint],
11175    n: SaSint,
11176    k: SaSint,
11177    m: SaSint,
11178    fs: SaSint,
11179    f: SaSint,
11180    buckets: &mut [SaSint],
11181    local_buckets: SaSint,
11182    threads: SaSint,
11183    thread_state: &mut [ThreadState],
11184) {
11185    if f > 0 {
11186        let dst = usize::try_from(n - m - 1).expect("destination must be non-negative");
11187        let src = usize::try_from(n + fs - m).expect("source must be non-negative");
11188        let len = usize::try_from(f).expect("length must be non-negative");
11189        sa.copy_within(src..src + len, dst);
11190
11191        let _ = count_and_gather_compacted_lms_suffixes_32s_2k_omp(
11192            t,
11193            sa,
11194            n,
11195            k,
11196            buckets,
11197            local_buckets,
11198            threads,
11199            thread_state,
11200        );
11201        reconstruct_lms_suffixes_omp(sa, n, m - f, threads);
11202
11203        let src_copy = 0usize;
11204        let dst_copy = usize::try_from(n - m - 1 + f).expect("destination must be non-negative");
11205        let copy_len = usize::try_from(m - f).expect("copy length must be non-negative");
11206        sa.copy_within(src_copy..src_copy + copy_len, dst_copy);
11207        sa[..usize::try_from(m).expect("m must be non-negative")].fill(0);
11208
11209        merge_compacted_lms_suffixes_32s_omp(t, sa, n, m, f, threads, thread_state);
11210    } else {
11211        let _ = count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
11212        reconstruct_lms_suffixes_omp(sa, n, m, threads);
11213    }
11214}
11215
11216/// Internal helper: reconstruct compacted lms suffixes 32s 1k (OpenMP variant).
11217#[doc(hidden)]
11218pub fn reconstruct_compacted_lms_suffixes_32s_1k_omp(
11219    t: &mut [SaSint],
11220    sa: &mut [SaSint],
11221    n: SaSint,
11222    m: SaSint,
11223    fs: SaSint,
11224    f: SaSint,
11225    threads: SaSint,
11226    thread_state: &mut [ThreadState],
11227) {
11228    if f > 0 {
11229        let dst = usize::try_from(n - m - 1).expect("destination must be non-negative");
11230        let src = usize::try_from(n + fs - m).expect("source must be non-negative");
11231        let len = usize::try_from(f).expect("length must be non-negative");
11232        sa.copy_within(src..src + len, dst);
11233
11234        let _ = gather_compacted_lms_suffixes_32s(t, sa, n);
11235        reconstruct_lms_suffixes_omp(sa, n, m - f, threads);
11236
11237        let dst_copy = usize::try_from(n - m - 1 + f).expect("destination must be non-negative");
11238        let copy_len = usize::try_from(m - f).expect("copy length must be non-negative");
11239        sa.copy_within(0..copy_len, dst_copy);
11240        sa[..usize::try_from(m).expect("m must be non-negative")].fill(0);
11241
11242        merge_compacted_lms_suffixes_32s_omp(t, sa, n, m, f, threads, thread_state);
11243    } else {
11244        let _ = gather_lms_suffixes_32s(t, sa, n);
11245        reconstruct_lms_suffixes_omp(sa, n, m, threads);
11246    }
11247}
11248
11249fn normalize_omp_threads(threads: SaSint) -> SaSint {
11250    if threads > 0 {
11251        threads
11252    } else {
11253        std::thread::available_parallelism()
11254            .map(|value| value.get() as SaSint)
11255            .unwrap_or(1)
11256            .max(1)
11257    }
11258}
11259
11260fn libsais_main_32s_recursion(
11261    t_ptr: *mut SaSint,
11262    sa_ptr: *mut SaSint,
11263    sa_capacity: usize,
11264    n: SaSint,
11265    k: SaSint,
11266    fs: SaSint,
11267    threads: SaSint,
11268    thread_state: &mut [ThreadState],
11269    _local_buffer: &mut [SaSint],
11270) -> SaSint {
11271    let fs = fs.min(SAINT_MAX - n);
11272    let local_buffer_size = SaSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("fits");
11273    let n_usize = usize::try_from(n).expect("n must be non-negative");
11274    let fs_usize = usize::try_from(fs).expect("fs must be non-negative");
11275    let total_len = n_usize + fs_usize;
11276    assert!(total_len <= sa_capacity);
11277
11278    if k > 0 && ((fs / k) >= 6 || (local_buffer_size / k) >= 6) {
11279        let k_usize = usize::try_from(k).expect("k must be non-negative");
11280        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 6 {
11281            1024usize
11282        } else {
11283            16usize
11284        };
11285        let need = 6 * k_usize;
11286        let use_local_buffer = local_buffer_size > fs;
11287        let mut bucket_free_space = SaSint::from(use_local_buffer);
11288        let buckets_ptr = if use_local_buffer {
11289            _local_buffer.as_mut_ptr()
11290        } else {
11291            unsafe {
11292                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11293                let start =
11294                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 6 {
11295                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
11296                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11297                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11298                    } else {
11299                        total_len - need
11300                    };
11301                bucket_free_space =
11302                    SaSint::try_from(start - n_usize).expect("bucket free space must fit SaSint");
11303                sa[start..].as_mut_ptr()
11304            }
11305        };
11306
11307        let m = unsafe {
11308            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11309            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11310            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11311            count_and_gather_lms_suffixes_32s_4k_omp(
11312                t,
11313                sa,
11314                n,
11315                k,
11316                buckets,
11317                bucket_free_space,
11318                threads,
11319                thread_state,
11320            )
11321        };
11322        if m > 1 {
11323            let m_usize = usize::try_from(m).expect("m must be non-negative");
11324            unsafe {
11325                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11326                sa[..n_usize - m_usize].fill(0);
11327            }
11328
11329            let first_lms_suffix = unsafe {
11330                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11331                sa[n_usize - m_usize]
11332            };
11333            let left_suffixes_count = unsafe {
11334                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11335                initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(
11336                    std::slice::from_raw_parts_mut(t_ptr, n_usize),
11337                    k,
11338                    buckets,
11339                    first_lms_suffix,
11340                )
11341            };
11342
11343            unsafe {
11344                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11345                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11346                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11347                let (_, induction_bucket) = buckets.split_at_mut(4 * k_usize);
11348                radix_sort_lms_suffixes_32s_6k_omp(
11349                    t,
11350                    sa,
11351                    n,
11352                    m,
11353                    induction_bucket,
11354                    threads,
11355                    thread_state,
11356                );
11357                if (n / 8192) < k {
11358                    radix_sort_set_markers_32s_6k_omp(sa, k, induction_bucket, threads);
11359                }
11360                if threads > 1 && n >= 65_536 {
11361                    sa[n_usize - m_usize..n_usize].fill(0);
11362                }
11363                initialize_buckets_for_partial_sorting_32s_6k(
11364                    t,
11365                    k,
11366                    buckets,
11367                    first_lms_suffix,
11368                    left_suffixes_count,
11369                );
11370                induce_partial_order_32s_6k_omp(
11371                    t,
11372                    sa,
11373                    n,
11374                    k,
11375                    buckets,
11376                    first_lms_suffix,
11377                    left_suffixes_count,
11378                    threads,
11379                    thread_state,
11380                );
11381            }
11382
11383            let names = unsafe {
11384                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11385                if (n / 8192) < k {
11386                    renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
11387                        sa,
11388                        n,
11389                        m,
11390                        threads,
11391                        thread_state,
11392                    )
11393                } else {
11394                    renumber_and_gather_lms_suffixes_omp(sa, n, m, fs, threads, thread_state)
11395                }
11396            };
11397
11398            if names < m {
11399                let f = if (n / 8192) < k {
11400                    unsafe {
11401                        let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11402                        let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11403                        compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11404                    }
11405                } else {
11406                    0
11407                };
11408
11409                let new_t_start =
11410                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11411                let recursive_n = m - f;
11412                let recursive_fs = fs + n - 2 * m + f;
11413                if libsais_main_32s_recursion(
11414                    unsafe {
11415                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11416                            .as_mut_ptr()
11417                    },
11418                    sa_ptr,
11419                    sa_capacity,
11420                    recursive_n,
11421                    names - f,
11422                    recursive_fs,
11423                    threads,
11424                    thread_state,
11425                    _local_buffer,
11426                ) != 0
11427                {
11428                    return -2;
11429                }
11430
11431                unsafe {
11432                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11433                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11434                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11435                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11436                        t,
11437                        sa,
11438                        n,
11439                        k,
11440                        m,
11441                        fs,
11442                        f,
11443                        buckets,
11444                        SaSint::from(use_local_buffer),
11445                        threads,
11446                        thread_state,
11447                    );
11448                }
11449            } else {
11450                unsafe {
11451                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11452                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11453                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11454                }
11455            }
11456
11457            unsafe {
11458                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11459                initialize_buckets_start_and_end_32s_4k(k, buckets);
11460                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11461                place_lms_suffixes_histogram_32s_4k(sa, n, k, m, buckets);
11462                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11463                induce_final_order_32s_4k(t, sa, n, k, buckets, threads, thread_state);
11464            }
11465        } else {
11466            unsafe {
11467                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11468                sa[0] = sa[n_usize - 1];
11469            }
11470
11471            unsafe {
11472                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11473                initialize_buckets_start_and_end_32s_6k(k, buckets);
11474                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11475                place_lms_suffixes_histogram_32s_6k(sa, n, k, m, buckets);
11476                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11477                induce_final_order_32s_6k(t, sa, n, k, buckets, threads, thread_state);
11478            }
11479        }
11480
11481        return 0;
11482    } else if k > 0 && n <= SAINT_MAX / 2 && ((fs / k) >= 4 || (local_buffer_size / k) >= 4) {
11483        let k_usize = usize::try_from(k).expect("k must be non-negative");
11484        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 4 {
11485            1024usize
11486        } else {
11487            16usize
11488        };
11489        let need = 4 * k_usize;
11490        let use_local_buffer = local_buffer_size > fs;
11491        let mut bucket_free_space = SaSint::from(use_local_buffer);
11492        let buckets_ptr = if use_local_buffer {
11493            _local_buffer.as_mut_ptr()
11494        } else {
11495            unsafe {
11496                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11497                let start =
11498                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 4 {
11499                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
11500                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11501                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11502                    } else {
11503                        total_len - need
11504                    };
11505                bucket_free_space =
11506                    SaSint::try_from(start - n_usize).expect("bucket free space must fit SaSint");
11507                sa[start..].as_mut_ptr()
11508            }
11509        };
11510
11511        let m = unsafe {
11512            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11513            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11514            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11515            count_and_gather_lms_suffixes_32s_2k_omp(
11516                t,
11517                sa,
11518                n,
11519                k,
11520                buckets,
11521                bucket_free_space,
11522                threads,
11523                thread_state,
11524            )
11525        };
11526        if m > 1 {
11527            let m_usize = usize::try_from(m).expect("m must be non-negative");
11528            unsafe {
11529                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11530                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11531                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11532                initialize_buckets_for_radix_and_partial_sorting_32s_4k(
11533                    t,
11534                    k,
11535                    buckets,
11536                    sa[n_usize - m_usize],
11537                );
11538                let (_, induction_bucket) = buckets.split_at_mut(1);
11539                radix_sort_lms_suffixes_32s_2k_omp(
11540                    t,
11541                    sa,
11542                    n,
11543                    m,
11544                    induction_bucket,
11545                    threads,
11546                    thread_state,
11547                );
11548                radix_sort_set_markers_32s_4k_omp(sa, k, induction_bucket, threads);
11549                place_lms_suffixes_interval_32s_4k(sa, n, k, m - 1, buckets);
11550                induce_partial_order_32s_4k_omp(t, sa, n, k, buckets, threads, thread_state);
11551            }
11552
11553            let names = unsafe {
11554                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11555                renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(sa, n, m, threads, thread_state)
11556            };
11557            if names < m {
11558                let f = unsafe {
11559                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11560                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11561                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11562                };
11563
11564                let new_t_start =
11565                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11566                if libsais_main_32s_recursion(
11567                    unsafe {
11568                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11569                            .as_mut_ptr()
11570                    },
11571                    sa_ptr,
11572                    sa_capacity,
11573                    m - f,
11574                    names - f,
11575                    fs + n - 2 * m + f,
11576                    threads,
11577                    thread_state,
11578                    _local_buffer,
11579                ) != 0
11580                {
11581                    return -2;
11582                }
11583
11584                unsafe {
11585                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11586                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11587                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11588                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11589                        t,
11590                        sa,
11591                        n,
11592                        k,
11593                        m,
11594                        fs,
11595                        f,
11596                        buckets,
11597                        SaSint::from(use_local_buffer),
11598                        threads,
11599                        thread_state,
11600                    );
11601                }
11602            } else {
11603                unsafe {
11604                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11605                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11606                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11607                }
11608            }
11609        } else {
11610            unsafe {
11611                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11612                sa[0] = sa[n_usize - 1];
11613            }
11614        }
11615
11616        unsafe {
11617            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11618            initialize_buckets_start_and_end_32s_4k(k, buckets);
11619            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11620            place_lms_suffixes_histogram_32s_4k(sa, n, k, m, buckets);
11621            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11622            induce_final_order_32s_4k(t, sa, n, k, buckets, threads, thread_state);
11623        }
11624
11625        return 0;
11626    } else if k > 0 && ((fs / k) >= 2 || (local_buffer_size / k) >= 2) {
11627        let k_usize = usize::try_from(k).expect("k must be non-negative");
11628        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 2 {
11629            1024usize
11630        } else {
11631            16usize
11632        };
11633        let need = 2 * k_usize;
11634        let use_local_buffer = local_buffer_size > fs;
11635        let mut bucket_free_space = SaSint::from(use_local_buffer);
11636        let buckets_ptr = if use_local_buffer {
11637            _local_buffer.as_mut_ptr()
11638        } else {
11639            unsafe {
11640                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11641                let start =
11642                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 2 {
11643                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
11644                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11645                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11646                    } else {
11647                        total_len - need
11648                    };
11649                bucket_free_space =
11650                    SaSint::try_from(start - n_usize).expect("bucket free space must fit SaSint");
11651                sa[start..].as_mut_ptr()
11652            }
11653        };
11654
11655        let m = unsafe {
11656            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11657            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11658            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11659            count_and_gather_lms_suffixes_32s_2k_omp(
11660                t,
11661                sa,
11662                n,
11663                k,
11664                buckets,
11665                bucket_free_space,
11666                threads,
11667                thread_state,
11668            )
11669        };
11670        if m > 1 {
11671            let m_usize = usize::try_from(m).expect("m must be non-negative");
11672            unsafe {
11673                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11674                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11675                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11676                initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(
11677                    t,
11678                    k,
11679                    buckets,
11680                    sa[n_usize - m_usize],
11681                );
11682                let (_, induction_bucket) = buckets.split_at_mut(1);
11683                radix_sort_lms_suffixes_32s_2k_omp(
11684                    t,
11685                    sa,
11686                    n,
11687                    m,
11688                    induction_bucket,
11689                    threads,
11690                    thread_state,
11691                );
11692                place_lms_suffixes_interval_32s_2k(sa, n, k, m - 1, buckets);
11693            }
11694
11695            unsafe {
11696                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11697                initialize_buckets_start_and_end_32s_2k(k, buckets);
11698                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11699                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11700                induce_partial_order_32s_2k_omp(t, sa, n, k, buckets, threads, thread_state);
11701            }
11702
11703            let names = unsafe {
11704                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11705                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11706                renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(t, sa, n, m, threads)
11707            };
11708            if names < m {
11709                let f = unsafe {
11710                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11711                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11712                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11713                };
11714
11715                let new_t_start =
11716                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11717                if libsais_main_32s_recursion(
11718                    unsafe {
11719                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11720                            .as_mut_ptr()
11721                    },
11722                    sa_ptr,
11723                    sa_capacity,
11724                    m - f,
11725                    names - f,
11726                    fs + n - 2 * m + f,
11727                    threads,
11728                    thread_state,
11729                    _local_buffer,
11730                ) != 0
11731                {
11732                    return -2;
11733                }
11734
11735                unsafe {
11736                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11737                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11738                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11739                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11740                        t,
11741                        sa,
11742                        n,
11743                        k,
11744                        m,
11745                        fs,
11746                        f,
11747                        buckets,
11748                        SaSint::from(use_local_buffer),
11749                        threads,
11750                        thread_state,
11751                    );
11752                }
11753            } else {
11754                unsafe {
11755                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11756                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11757                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11758                }
11759            }
11760        } else {
11761            unsafe {
11762                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11763                sa[0] = sa[n_usize - 1];
11764            }
11765        }
11766
11767        unsafe {
11768            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11769            initialize_buckets_end_32s_2k(k, buckets);
11770            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11771            place_lms_suffixes_histogram_32s_2k(sa, n, k, m, buckets);
11772        }
11773
11774        unsafe {
11775            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11776            initialize_buckets_start_and_end_32s_2k(k, buckets);
11777            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11778            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11779            induce_final_order_32s_2k(t, sa, n, k, buckets, threads, thread_state);
11780        }
11781
11782        return 0;
11783    } else {
11784        let k_usize = usize::try_from(k).expect("k must be non-negative");
11785        let mut heap_buckets = if fs < k { Some(vec![0; k_usize]) } else { None };
11786        let alignment = if fs >= 1024 && (fs - 1024) >= k {
11787            1024usize
11788        } else {
11789            16usize
11790        };
11791        let mut buckets_ptr = if let Some(ref mut heap) = heap_buckets {
11792            heap.as_mut_ptr()
11793        } else {
11794            unsafe {
11795                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11796                let start = if fs_usize >= k_usize + alignment {
11797                    let byte_ptr = sa[total_len - k_usize - alignment..].as_mut_ptr() as usize;
11798                    let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11799                    (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11800                } else {
11801                    total_len - k_usize
11802                };
11803                sa[start..].as_mut_ptr()
11804            }
11805        };
11806
11807        if buckets_ptr.is_null() {
11808            return -2;
11809        }
11810
11811        unsafe {
11812            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11813            sa[..n_usize].fill(0);
11814        }
11815
11816        unsafe {
11817            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11818            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11819            count_suffixes_32s(t, n, k, buckets);
11820        }
11821        unsafe {
11822            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11823            initialize_buckets_end_32s_1k(k, buckets);
11824        }
11825
11826        let m = unsafe {
11827            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11828            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11829            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11830            radix_sort_lms_suffixes_32s_1k(t, sa, n, buckets)
11831        };
11832        if m > 1 {
11833            unsafe {
11834                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11835                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11836                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11837                induce_partial_order_32s_1k_omp(t, sa, n, k, buckets, threads, thread_state);
11838            }
11839
11840            let names = unsafe {
11841                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11842                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11843                renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(t, sa, n, m, threads)
11844            };
11845            if names < m {
11846                if heap_buckets.is_some() {
11847                    let _ = heap_buckets.take();
11848                    buckets_ptr = std::ptr::null_mut();
11849                }
11850
11851                let f = unsafe {
11852                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11853                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11854                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11855                };
11856
11857                let new_t_start =
11858                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11859                if libsais_main_32s_recursion(
11860                    unsafe {
11861                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11862                            .as_mut_ptr()
11863                    },
11864                    sa_ptr,
11865                    sa_capacity,
11866                    m - f,
11867                    names - f,
11868                    fs + n - 2 * m + f,
11869                    threads,
11870                    thread_state,
11871                    _local_buffer,
11872                ) != 0
11873                {
11874                    return -2;
11875                }
11876
11877                unsafe {
11878                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11879                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11880                    reconstruct_compacted_lms_suffixes_32s_1k_omp(
11881                        t,
11882                        sa,
11883                        n,
11884                        m,
11885                        fs,
11886                        f,
11887                        threads,
11888                        thread_state,
11889                    );
11890                }
11891
11892                if buckets_ptr.is_null() {
11893                    heap_buckets = Some(vec![0; k_usize]);
11894                    buckets_ptr = heap_buckets
11895                        .as_mut()
11896                        .expect("heap buckets must exist")
11897                        .as_mut_ptr();
11898                    if buckets_ptr.is_null() {
11899                        return -2;
11900                    }
11901                }
11902            }
11903
11904            unsafe {
11905                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11906                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11907                count_suffixes_32s(t, n, k, buckets);
11908            }
11909            unsafe {
11910                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11911                initialize_buckets_end_32s_1k(k, buckets);
11912            }
11913            unsafe {
11914                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11915                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11916                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11917                place_lms_suffixes_interval_32s_1k(t, sa, k, m, buckets);
11918            }
11919        }
11920
11921        unsafe {
11922            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11923            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11924            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11925            induce_final_order_32s_1k(t, sa, n, k, buckets, threads, thread_state);
11926        }
11927
11928        0
11929    }
11930}
11931
11932fn libsais_main_32s_entry(
11933    t: &mut [SaSint],
11934    sa: &mut [SaSint],
11935    n: SaSint,
11936    k: SaSint,
11937    fs: SaSint,
11938    threads: SaSint,
11939    thread_state: &mut [ThreadState],
11940) -> SaSint {
11941    let mut local_buffer = [0; 2 * LIBSAIS_LOCAL_BUFFER_SIZE];
11942    libsais_main_32s_recursion(
11943        t.as_mut_ptr(),
11944        sa.as_mut_ptr(),
11945        sa.len(),
11946        n,
11947        k,
11948        fs,
11949        threads,
11950        thread_state,
11951        &mut local_buffer[LIBSAIS_LOCAL_BUFFER_SIZE..],
11952    )
11953}
11954
11955fn libsais_main_8u(
11956    t: &[u8],
11957    sa: &mut [SaSint],
11958    buckets: &mut [SaSint],
11959    flags: SaSint,
11960    r: SaSint,
11961    i: Option<&mut [SaSint]>,
11962    fs: SaSint,
11963    freq: Option<&mut [SaSint]>,
11964    threads: SaSint,
11965    thread_state: &mut [ThreadState],
11966) -> SaSint {
11967    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
11968    let n_usize = usize::try_from(n).expect("n must be non-negative");
11969    let fs = fs.min(SAINT_MAX - n);
11970
11971    let m = count_and_gather_lms_suffixes_8u_omp(t, sa, n, buckets, threads, thread_state);
11972    let k = initialize_buckets_start_and_end_8u(buckets, freq);
11973
11974    if (flags & LIBSAIS_FLAGS_GSA) != 0 && (buckets[0] != 0 || buckets[2] != 0 || buckets[3] != 1) {
11975        return -1;
11976    }
11977
11978    if m > 0 {
11979        let m_usize = usize::try_from(m).expect("m must be non-negative");
11980        let first_lms_suffix = sa[n_usize - m_usize];
11981        let left_suffixes_count =
11982            initialize_buckets_for_lms_suffixes_radix_sort_8u(t, buckets, first_lms_suffix);
11983
11984        if threads > 1 && n >= 65_536 {
11985            sa[..n_usize - m_usize].fill(0);
11986        }
11987        radix_sort_lms_suffixes_8u_omp(t, sa, n, m, flags, buckets, threads, thread_state);
11988        if threads > 1 && n >= 65_536 {
11989            sa[n_usize - m_usize..n_usize].fill(0);
11990        }
11991
11992        initialize_buckets_for_partial_sorting_8u(
11993            t,
11994            buckets,
11995            first_lms_suffix,
11996            left_suffixes_count,
11997        );
11998        induce_partial_order_8u_omp(
11999            t,
12000            sa,
12001            n,
12002            k,
12003            flags,
12004            buckets,
12005            first_lms_suffix,
12006            left_suffixes_count,
12007            threads,
12008            thread_state,
12009        );
12010
12011        let names = renumber_and_gather_lms_suffixes_omp(sa, n, m, fs, threads, thread_state);
12012        if names < m {
12013            let recursive_text_start =
12014                n_usize + usize::try_from(fs).expect("fs must be non-negative") - m_usize;
12015            let recursive_fs = fs + n - 2 * m;
12016
12017            let index = libsais_main_32s_entry(
12018                unsafe {
12019                    std::slice::from_raw_parts_mut(sa[recursive_text_start..].as_mut_ptr(), m_usize)
12020                },
12021                sa,
12022                m,
12023                names,
12024                recursive_fs,
12025                threads,
12026                thread_state,
12027            );
12028
12029            if index != 0 {
12030                return -2;
12031            }
12032
12033            gather_lms_suffixes_8u_omp(t, sa, n, threads, thread_state);
12034            reconstruct_lms_suffixes_omp(sa, n, m, threads);
12035        }
12036
12037        place_lms_suffixes_interval_8u(sa, n, m, flags, buckets);
12038    } else {
12039        sa[..n_usize].fill(0);
12040    }
12041
12042    induce_final_order_8u_omp(t, sa, n, k, flags, r, i, buckets, threads, thread_state)
12043}
12044
12045fn libsais_main(
12046    t: &[u8],
12047    sa: &mut [SaSint],
12048    flags: SaSint,
12049    r: SaSint,
12050    i: Option<&mut [SaSint]>,
12051    fs: SaSint,
12052    freq: Option<&mut [SaSint]>,
12053    threads: SaSint,
12054) -> SaSint {
12055    let threads = normalize_omp_threads(threads);
12056    if threads > 1 {
12057        let mut thread_state = match alloc_thread_state(threads) {
12058            Some(thread_state) => thread_state,
12059            None => return -2,
12060        };
12061        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
12062
12063        libsais_main_8u(
12064            t,
12065            sa,
12066            &mut buckets,
12067            flags,
12068            r,
12069            i,
12070            fs,
12071            freq,
12072            threads,
12073            &mut thread_state,
12074        )
12075    } else {
12076        let mut thread_state = [];
12077        let mut buckets = [0; 8 * ALPHABET_SIZE];
12078
12079        libsais_main_8u(
12080            t,
12081            sa,
12082            &mut buckets,
12083            flags,
12084            r,
12085            i,
12086            fs,
12087            freq,
12088            threads,
12089            &mut thread_state,
12090        )
12091    }
12092}
12093
12094fn libsais_main_int(
12095    t: &mut [SaSint],
12096    sa: &mut [SaSint],
12097    k: SaSint,
12098    fs: SaSint,
12099    threads: SaSint,
12100) -> SaSint {
12101    let threads = normalize_omp_threads(threads);
12102    let mut thread_state = if threads > 1 {
12103        match alloc_thread_state(threads) {
12104            Some(thread_state) => thread_state,
12105            None => return -2,
12106        }
12107    } else {
12108        Vec::new()
12109    };
12110
12111    libsais_main_32s_entry(
12112        t,
12113        sa,
12114        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
12115        k,
12116        fs,
12117        threads,
12118        &mut thread_state,
12119    )
12120}
12121
12122fn libsais_main_ctx(
12123    ctx: &mut Context,
12124    t: &[u8],
12125    sa: &mut [SaSint],
12126    flags: SaSint,
12127    r: SaSint,
12128    i: Option<&mut [SaSint]>,
12129    fs: SaSint,
12130    freq: Option<&mut [SaSint]>,
12131) -> SaSint {
12132    if ctx.threads <= 0 || ctx.buckets.len() != 8 * ALPHABET_SIZE {
12133        return -2;
12134    }
12135
12136    let mut empty_thread_state = [];
12137    let thread_state = if ctx.threads > 1 {
12138        match ctx.thread_state.as_deref_mut() {
12139            Some(thread_state) if thread_state.len() >= ctx.threads as usize => thread_state,
12140            None => return -2,
12141            Some(_) => return -2,
12142        }
12143    } else {
12144        &mut empty_thread_state
12145    };
12146
12147    libsais_main_8u(
12148        t,
12149        sa,
12150        &mut ctx.buckets,
12151        flags,
12152        r,
12153        i,
12154        fs,
12155        freq,
12156        ctx.threads as SaSint,
12157        thread_state,
12158    )
12159}
12160
12161#[cfg(feature = "upstream-c")]
12162unsafe extern "C" {
12163    fn probe_public_libsais_freq(
12164        t: *const u8,
12165        sa: *mut SaSint,
12166        n: SaSint,
12167        fs: SaSint,
12168        freq: *mut SaSint,
12169    ) -> SaSint;
12170
12171    fn probe_public_libsais_omp_freq(
12172        t: *const u8,
12173        sa: *mut SaSint,
12174        n: SaSint,
12175        fs: SaSint,
12176        freq: *mut SaSint,
12177        threads: SaSint,
12178    ) -> SaSint;
12179}
12180
12181/// Wrapper around the bundled upstream C `libsais` implementation.
12182///
12183/// Available only with the `upstream-c` feature. Provides the same semantics as the Rust [`libsais`] function but defers all work to the C library; intended for the differential test suite and benchmarks.
12184///
12185/// - `t` (`[0..n-1]`): the input string.
12186/// - `sa` (`[0..n-1+fs]`): the output array of suffixes.
12187/// - `fs`: extra space available at the end of `sa`.
12188/// - `freq` (`[0..255]`): optional output symbol frequency table.
12189///
12190/// Returns 0 on success, -1 or -2 on error.
12191#[cfg(feature = "upstream-c")]
12192pub fn libsais_upstream_c(
12193    t: &[u8],
12194    sa: &mut [SaSint],
12195    fs: SaSint,
12196    freq: Option<&mut [SaSint]>,
12197) -> SaSint {
12198    if fs < 0
12199        || t.len() > SaSint::MAX as usize
12200        || sa.len()
12201            < t.len()
12202                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12203    {
12204        return -1;
12205    }
12206    if let Some(freq) = freq.as_ref() {
12207        if freq.len() < ALPHABET_SIZE {
12208            return -1;
12209        }
12210    }
12211
12212    let n = t.len() as SaSint;
12213    let freq_ptr = freq.map_or(std::ptr::null_mut(), |freq| freq.as_mut_ptr());
12214    unsafe { probe_public_libsais_freq(t.as_ptr(), sa.as_mut_ptr(), n, fs, freq_ptr) }
12215}
12216
12217/// Wrapper around the bundled upstream C `libsais_omp` implementation.
12218///
12219/// Available only with the `upstream-c` feature. Same semantics as the Rust [`libsais_omp`] function but defers all work to the C library; intended for the differential test suite and benchmarks.
12220///
12221/// - `t` (`[0..n-1]`): the input string.
12222/// - `sa` (`[0..n-1+fs]`): the output array of suffixes.
12223/// - `fs`: extra space available at the end of `sa`.
12224/// - `freq` (`[0..255]`): optional output symbol frequency table.
12225/// - `threads`: number of worker threads (can be 0 for the implementation default).
12226///
12227/// Returns 0 on success, -1 or -2 on error.
12228#[cfg(feature = "upstream-c")]
12229pub fn libsais_upstream_c_omp(
12230    t: &[u8],
12231    sa: &mut [SaSint],
12232    fs: SaSint,
12233    freq: Option<&mut [SaSint]>,
12234    threads: SaSint,
12235) -> SaSint {
12236    if threads < 0 {
12237        return -1;
12238    }
12239    if fs < 0
12240        || t.len() > SaSint::MAX as usize
12241        || sa.len()
12242            < t.len()
12243                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12244    {
12245        return -1;
12246    }
12247    if let Some(freq) = freq.as_ref() {
12248        if freq.len() < ALPHABET_SIZE {
12249            return -1;
12250        }
12251    }
12252
12253    let n = t.len() as SaSint;
12254    let freq_ptr = freq.map_or(std::ptr::null_mut(), |freq| freq.as_mut_ptr());
12255    unsafe {
12256        probe_public_libsais_omp_freq(t.as_ptr(), sa.as_mut_ptr(), n, fs, freq_ptr, threads.max(1))
12257    }
12258}
12259
12260/// Constructs the suffix array of a given string.
12261///
12262/// # Arguments
12263/// - `T`: [0..n-1] The input string.
12264/// - `SA`: [0..n-1+fs] The output array of suffixes.
12265/// - `n`: The length of the given string.
12266/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12267/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12268///
12269/// # Returns
12270/// 0 if no error occurred, -1 or -2 otherwise.
12271pub fn libsais(t: &[u8], sa: &mut [SaSint], fs: SaSint, freq: Option<&mut [SaSint]>) -> SaSint {
12272    if fs < 0
12273        || sa.len()
12274            < t.len()
12275                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12276    {
12277        return -1;
12278    }
12279    if let Some(freq) = freq.as_ref() {
12280        if freq.len() < ALPHABET_SIZE {
12281            return -1;
12282        }
12283    }
12284
12285    let n = t.len();
12286    if n <= 1 {
12287        if let Some(freq) = freq {
12288            freq[..ALPHABET_SIZE].fill(0);
12289            if n == 1 {
12290                freq[t[0] as usize] += 1;
12291            }
12292        }
12293        if n == 1 {
12294            sa[0] = 0;
12295        }
12296        return 0;
12297    }
12298
12299    libsais_main(t, sa, LIBSAIS_FLAGS_NONE, 0, None, fs, freq, 1)
12300}
12301
12302/// Constructs the generalized suffix array (GSA) of given string set.
12303///
12304/// # Arguments
12305/// - `T`: [0..n-1] The input string set using 0 as separators (T[n-1] must be 0).
12306/// - `SA`: [0..n-1+fs] The output array of suffixes.
12307/// - `n`: The length of the given string set.
12308/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12309/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12310///
12311/// # Returns
12312/// 0 if no error occurred, -1 or -2 otherwise.
12313pub fn libsais_gsa(t: &[u8], sa: &mut [SaSint], fs: SaSint, freq: Option<&mut [SaSint]>) -> SaSint {
12314    if fs < 0
12315        || sa.len()
12316            < t.len()
12317                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12318    {
12319        return -1;
12320    }
12321    if let Some(freq) = freq.as_ref() {
12322        if freq.len() < ALPHABET_SIZE {
12323            return -1;
12324        }
12325    }
12326
12327    let n = t.len();
12328    if n > 0 && t[n - 1] != 0 {
12329        return -1;
12330    }
12331
12332    if n <= 1 {
12333        if let Some(freq) = freq {
12334            freq[..ALPHABET_SIZE].fill(0);
12335            if n == 1 {
12336                freq[t[0] as usize] += 1;
12337            }
12338        }
12339        if n == 1 {
12340            sa[0] = 0;
12341        }
12342        return 0;
12343    }
12344
12345    libsais_main(t, sa, LIBSAIS_FLAGS_GSA, 0, None, fs, freq, 1)
12346}
12347
12348/// Constructs the suffix array of a given integer array.
12349/// Note, during construction input array will be modified, but restored at the end if no errors occurred.
12350///
12351/// # Arguments
12352/// - `T`: [0..n-1] The input integer array.
12353/// - `SA`: [0..n-1+fs] The output array of suffixes.
12354/// - `n`: The length of the integer array.
12355/// - `k`: The alphabet size of the input integer array.
12356/// - `fs`: Extra space available at the end of SA array (can be 0, but 4k or better 6k is recommended for optimal performance).
12357///
12358/// # Returns
12359/// 0 if no error occurred, -1 or -2 otherwise.
12360pub fn libsais_int(t: &mut [SaSint], sa: &mut [SaSint], k: SaSint, fs: SaSint) -> SaSint {
12361    if fs < 0
12362        || sa.len()
12363            < t.len()
12364                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12365    {
12366        return -1;
12367    }
12368
12369    if t.len() <= 1 {
12370        if t.len() == 1 {
12371            sa[0] = 0;
12372        }
12373        return 0;
12374    }
12375
12376    libsais_main_int(t, sa, k, fs, 1)
12377}
12378
12379/// Constructs the suffix array of a given string using libsais context.
12380///
12381/// # Arguments
12382/// - `ctx`: The libsais context.
12383/// - `T`: [0..n-1] The input string.
12384/// - `SA`: [0..n-1+fs] The output array of suffixes.
12385/// - `n`: The length of the given string.
12386/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12387/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12388///
12389/// # Returns
12390/// 0 if no error occurred, -1 or -2 otherwise.
12391pub fn libsais_ctx(
12392    ctx: &mut Context,
12393    t: &[u8],
12394    sa: &mut [SaSint],
12395    fs: SaSint,
12396    freq: Option<&mut [SaSint]>,
12397) -> SaSint {
12398    if fs < 0
12399        || sa.len()
12400            < t.len()
12401                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12402    {
12403        return -1;
12404    }
12405    if let Some(freq) = freq.as_ref() {
12406        if freq.len() < ALPHABET_SIZE {
12407            return -1;
12408        }
12409    }
12410
12411    let n = t.len();
12412    if n <= 1 {
12413        if let Some(freq) = freq {
12414            freq[..ALPHABET_SIZE].fill(0);
12415            if n == 1 {
12416                freq[t[0] as usize] += 1;
12417            }
12418        }
12419        if n == 1 {
12420            sa[0] = 0;
12421        }
12422        return 0;
12423    }
12424
12425    libsais_main_ctx(ctx, t, sa, LIBSAIS_FLAGS_NONE, 0, None, fs, freq)
12426}
12427
12428/// Constructs the generalized suffix array (GSA) of given string set using libsais context.
12429///
12430/// # Arguments
12431/// - `ctx`: The libsais context.
12432/// - `T`: [0..n-1] The input string set using 0 as separators (T[n-1] must be 0).
12433/// - `SA`: [0..n-1+fs] The output array of suffixes.
12434/// - `n`: The length of the given string set.
12435/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12436/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12437///
12438/// # Returns
12439/// 0 if no error occurred, -1 or -2 otherwise.
12440pub fn libsais_gsa_ctx(
12441    ctx: &mut Context,
12442    t: &[u8],
12443    sa: &mut [SaSint],
12444    fs: SaSint,
12445    freq: Option<&mut [SaSint]>,
12446) -> SaSint {
12447    if fs < 0
12448        || sa.len()
12449            < t.len()
12450                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12451    {
12452        return -1;
12453    }
12454    if let Some(freq) = freq.as_ref() {
12455        if freq.len() < ALPHABET_SIZE {
12456            return -1;
12457        }
12458    }
12459
12460    let n = t.len();
12461    if n > 0 && t[n - 1] != 0 {
12462        return -1;
12463    }
12464
12465    if n <= 1 {
12466        if let Some(freq) = freq {
12467            freq[..ALPHABET_SIZE].fill(0);
12468            if n == 1 {
12469                freq[t[0] as usize] += 1;
12470            }
12471        }
12472        if n == 1 {
12473            sa[0] = 0;
12474        }
12475        return 0;
12476    }
12477
12478    libsais_main_ctx(ctx, t, sa, LIBSAIS_FLAGS_GSA, 0, None, fs, freq)
12479}
12480
12481/// Constructs the burrows-wheeler transformed string (BWT) of a given string.
12482///
12483/// # Arguments
12484/// - `T`: [0..n-1] The input string.
12485/// - `U`: [0..n-1] The output string (can be T).
12486/// - `A`: [0..n-1+fs] The temporary array.
12487/// - `n`: The length of the given string.
12488/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12489/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12490///
12491/// # Returns
12492/// The primary index if no error occurred, -1 or -2 otherwise.
12493pub fn libsais_bwt(
12494    t: &[u8],
12495    u: &mut [u8],
12496    a: &mut [SaSint],
12497    fs: SaSint,
12498    freq: Option<&mut [SaSint]>,
12499) -> SaSint {
12500    if fs < 0
12501        || u.len() < t.len()
12502        || a.len()
12503            < t.len()
12504                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12505    {
12506        return -1;
12507    }
12508    if let Some(freq) = freq.as_ref() {
12509        if freq.len() < ALPHABET_SIZE {
12510            return -1;
12511        }
12512    }
12513
12514    let n = t.len();
12515    if n <= 1 {
12516        if let Some(freq) = freq {
12517            freq[..ALPHABET_SIZE].fill(0);
12518            if n == 1 {
12519                u[0] = t[0];
12520                freq[t[0] as usize] += 1;
12521            }
12522        } else if n == 1 {
12523            u[0] = t[0];
12524        }
12525        return n as SaSint;
12526    }
12527
12528    let mut index = libsais_main(t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq, 1);
12529    if index >= 0 {
12530        index += 1;
12531        let split = usize::try_from(index).expect("index must be non-negative");
12532        u[0] = t[n - 1];
12533        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], index - 1, 1);
12534        bwt_copy_8u_omp(
12535            &mut u[split..n],
12536            &a[split..n],
12537            SaSint::try_from(n - split).expect("fits"),
12538            1,
12539        );
12540    }
12541    index
12542}
12543
12544/// Constructs the burrows-wheeler transformed string (BWT) of a given string with auxiliary indexes.
12545///
12546/// # Arguments
12547/// - `T`: [0..n-1] The input string.
12548/// - `U`: [0..n-1] The output string (can be T).
12549/// - `A`: [0..n-1+fs] The temporary array.
12550/// - `n`: The length of the given string.
12551/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12552/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12553/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
12554/// - `I`: [0..(n-1)/r] The output auxiliary indexes.
12555///
12556/// # Returns
12557/// 0 if no error occurred, -1 or -2 otherwise.
12558pub fn libsais_bwt_aux(
12559    t: &[u8],
12560    u: &mut [u8],
12561    a: &mut [SaSint],
12562    fs: SaSint,
12563    freq: Option<&mut [SaSint]>,
12564    r: SaSint,
12565    i: &mut [SaSint],
12566) -> SaSint {
12567    let n = t.len();
12568    if fs < 0
12569        || r < 2
12570        || (r & (r - 1)) != 0
12571        || u.len() < n
12572        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12573        || freq.as_ref().is_some_and(|freq| freq.len() < ALPHABET_SIZE)
12574    {
12575        return -1;
12576    }
12577    let sample_count = if n == 0 {
12578        1
12579    } else {
12580        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
12581            .expect("sample count must be non-negative")
12582            + 1
12583    };
12584    if i.len() < sample_count {
12585        return -1;
12586    }
12587
12588    if n <= 1 {
12589        if let Some(freq) = freq {
12590            freq[..ALPHABET_SIZE].fill(0);
12591            if n == 1 {
12592                u[0] = t[0];
12593                freq[t[0] as usize] += 1;
12594            }
12595        } else if n == 1 {
12596            u[0] = t[0];
12597        }
12598        i[0] = n as SaSint;
12599        return 0;
12600    }
12601
12602    let index = libsais_main(t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq, 1);
12603    if index == 0 {
12604        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
12605        u[0] = t[n - 1];
12606        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], i[0] - 1, 1);
12607        bwt_copy_8u_omp(
12608            &mut u[split..n],
12609            &a[split..n],
12610            SaSint::try_from(n - split).expect("fits"),
12611            1,
12612        );
12613    }
12614    index
12615}
12616
12617/// Constructs the burrows-wheeler transformed string (BWT) of a given string using libsais context.
12618///
12619/// # Arguments
12620/// - `ctx`: The libsais context.
12621/// - `T`: [0..n-1] The input string.
12622/// - `U`: [0..n-1] The output string (can be T).
12623/// - `A`: [0..n-1+fs] The temporary array.
12624/// - `n`: The length of the given string.
12625/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12626/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12627///
12628/// # Returns
12629/// The primary index if no error occurred, -1 or -2 otherwise.
12630pub fn libsais_bwt_ctx(
12631    ctx: &mut Context,
12632    t: &[u8],
12633    u: &mut [u8],
12634    a: &mut [SaSint],
12635    fs: SaSint,
12636    freq: Option<&mut [SaSint]>,
12637) -> SaSint {
12638    if fs < 0
12639        || u.len() < t.len()
12640        || a.len()
12641            < t.len()
12642                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12643    {
12644        return -1;
12645    }
12646    if let Some(freq) = freq.as_ref() {
12647        if freq.len() < ALPHABET_SIZE {
12648            return -1;
12649        }
12650    }
12651
12652    let n = t.len();
12653    if n <= 1 {
12654        if let Some(freq) = freq {
12655            freq[..ALPHABET_SIZE].fill(0);
12656            if n == 1 {
12657                u[0] = t[0];
12658                freq[t[0] as usize] += 1;
12659            }
12660        } else if n == 1 {
12661            u[0] = t[0];
12662        }
12663        return n as SaSint;
12664    }
12665
12666    let mut index = libsais_main_ctx(ctx, t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq);
12667    if index >= 0 {
12668        index += 1;
12669        let split = usize::try_from(index).expect("index must be non-negative");
12670        u[0] = t[n - 1];
12671        bwt_copy_8u_omp(
12672            &mut u[1..split],
12673            &a[..split - 1],
12674            index - 1,
12675            ctx.threads as SaSint,
12676        );
12677        bwt_copy_8u_omp(
12678            &mut u[split..n],
12679            &a[split..n],
12680            SaSint::try_from(n - split).expect("fits"),
12681            ctx.threads as SaSint,
12682        );
12683    }
12684    index
12685}
12686
12687/// Constructs the burrows-wheeler transformed string (BWT) of a given string with auxiliary indexes using libsais context.
12688///
12689/// # Arguments
12690/// - `ctx`: The libsais context.
12691/// - `T`: [0..n-1] The input string.
12692/// - `U`: [0..n-1] The output string (can be T).
12693/// - `A`: [0..n-1+fs] The temporary array.
12694/// - `n`: The length of the given string.
12695/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12696/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12697/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
12698/// - `I`: [0..(n-1)/r] The output auxiliary indexes.
12699///
12700/// # Returns
12701/// 0 if no error occurred, -1 or -2 otherwise.
12702pub fn libsais_bwt_aux_ctx(
12703    ctx: &mut Context,
12704    t: &[u8],
12705    u: &mut [u8],
12706    a: &mut [SaSint],
12707    fs: SaSint,
12708    freq: Option<&mut [SaSint]>,
12709    r: SaSint,
12710    i: &mut [SaSint],
12711) -> SaSint {
12712    let n = t.len();
12713    if fs < 0
12714        || r < 2
12715        || (r & (r - 1)) != 0
12716        || u.len() < n
12717        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12718    {
12719        return -1;
12720    }
12721    if let Some(freq) = freq.as_ref() {
12722        if freq.len() < ALPHABET_SIZE {
12723            return -1;
12724        }
12725    }
12726    let sample_count = if n == 0 {
12727        1
12728    } else {
12729        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
12730            .expect("sample count must be non-negative")
12731            + 1
12732    };
12733    if i.len() < sample_count {
12734        return -1;
12735    }
12736
12737    if n <= 1 {
12738        if let Some(freq) = freq {
12739            freq[..ALPHABET_SIZE].fill(0);
12740            if n == 1 {
12741                u[0] = t[0];
12742                freq[t[0] as usize] += 1;
12743            }
12744        } else if n == 1 {
12745            u[0] = t[0];
12746        }
12747        i[0] = n as SaSint;
12748        return 0;
12749    }
12750
12751    let index = libsais_main_ctx(ctx, t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq);
12752    if index == 0 {
12753        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
12754        u[0] = t[n - 1];
12755        bwt_copy_8u_omp(
12756            &mut u[1..split],
12757            &a[..split - 1],
12758            i[0] - 1,
12759            ctx.threads as SaSint,
12760        );
12761        bwt_copy_8u_omp(
12762            &mut u[split..n],
12763            &a[split..n],
12764            SaSint::try_from(n - split).expect("fits"),
12765            ctx.threads as SaSint,
12766        );
12767    }
12768    index
12769}
12770
12771/// Creates the libsais context that allows reusing allocated memory with each parallel libsais operation using OpenMP.
12772/// In multi-threaded environments, use one context per thread for parallel executions.
12773///
12774/// # Arguments
12775/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12776///
12777/// # Returns
12778/// the libsais context, NULL otherwise.
12779pub fn create_ctx_omp(threads: SaSint) -> Option<Context> {
12780    if threads < 0 {
12781        return None;
12782    }
12783
12784    create_ctx_main(normalize_omp_threads(threads))
12785}
12786
12787/// Constructs the suffix array of a given string in parallel using OpenMP.
12788///
12789/// # Arguments
12790/// - `T`: [0..n-1] The input string.
12791/// - `SA`: [0..n-1+fs] The output array of suffixes.
12792/// - `n`: The length of the given string.
12793/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12794/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12795/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12796///
12797/// # Returns
12798/// 0 if no error occurred, -1 or -2 otherwise.
12799pub fn libsais_omp(
12800    t: &[u8],
12801    sa: &mut [SaSint],
12802    fs: SaSint,
12803    freq: Option<&mut [SaSint]>,
12804    threads: SaSint,
12805) -> SaSint {
12806    if threads < 0 {
12807        return -1;
12808    }
12809    if let Some(freq) = freq.as_ref() {
12810        if freq.len() < ALPHABET_SIZE {
12811            return -1;
12812        }
12813    }
12814    let n = t.len();
12815    if n <= 1 {
12816        if let Some(freq) = freq {
12817            freq[..ALPHABET_SIZE].fill(0);
12818            if n == 1 {
12819                sa[0] = 0;
12820                freq[t[0] as usize] += 1;
12821            }
12822        } else if n == 1 {
12823            sa[0] = 0;
12824        }
12825        return 0;
12826    }
12827
12828    libsais_main(
12829        t,
12830        sa,
12831        LIBSAIS_FLAGS_NONE,
12832        0,
12833        None,
12834        fs,
12835        freq,
12836        normalize_omp_threads(threads),
12837    )
12838}
12839
12840/// Constructs the generalized suffix array (GSA) of given string set in parallel using OpenMP.
12841///
12842/// # Arguments
12843/// - `T`: [0..n-1] The input string set using 0 as separators (T[n-1] must be 0).
12844/// - `SA`: [0..n-1+fs] The output array of suffixes.
12845/// - `n`: The length of the given string set.
12846/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12847/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12848/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12849///
12850/// # Returns
12851/// 0 if no error occurred, -1 or -2 otherwise.
12852pub fn libsais_gsa_omp(
12853    t: &[u8],
12854    sa: &mut [SaSint],
12855    fs: SaSint,
12856    freq: Option<&mut [SaSint]>,
12857    threads: SaSint,
12858) -> SaSint {
12859    if threads < 0 || t.last().copied().unwrap_or(0) != 0 {
12860        return -1;
12861    }
12862    if let Some(freq) = freq.as_ref() {
12863        if freq.len() < ALPHABET_SIZE {
12864            return -1;
12865        }
12866    }
12867    let n = t.len();
12868    if n <= 1 {
12869        if let Some(freq) = freq {
12870            freq[..ALPHABET_SIZE].fill(0);
12871            if n == 1 {
12872                sa[0] = 0;
12873                freq[t[0] as usize] += 1;
12874            }
12875        } else if n == 1 {
12876            sa[0] = 0;
12877        }
12878        return 0;
12879    }
12880
12881    libsais_main(
12882        t,
12883        sa,
12884        LIBSAIS_FLAGS_GSA,
12885        0,
12886        None,
12887        fs,
12888        freq,
12889        normalize_omp_threads(threads),
12890    )
12891}
12892
12893/// Constructs the suffix array of a given integer array in parallel using OpenMP.
12894/// Note, during construction input array will be modified, but restored at the end if no errors occurred.
12895///
12896/// # Arguments
12897/// - `T`: [0..n-1] The input integer array.
12898/// - `SA`: [0..n-1+fs] The output array of suffixes.
12899/// - `n`: The length of the integer array.
12900/// - `k`: The alphabet size of the input integer array.
12901/// - `fs`: Extra space available at the end of SA array (can be 0, but 4k or better 6k is recommended for optimal performance).
12902/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12903///
12904/// # Returns
12905/// 0 if no error occurred, -1 or -2 otherwise.
12906pub fn libsais_int_omp(
12907    t: &mut [SaSint],
12908    sa: &mut [SaSint],
12909    k: SaSint,
12910    fs: SaSint,
12911    threads: SaSint,
12912) -> SaSint {
12913    if threads < 0 {
12914        return -1;
12915    }
12916    if t.len() <= 1 {
12917        if t.len() == 1 {
12918            sa[0] = 0;
12919        }
12920        return 0;
12921    }
12922
12923    libsais_main_int(t, sa, k, fs, normalize_omp_threads(threads))
12924}
12925
12926/// Constructs the burrows-wheeler transformed string (BWT) of a given string in parallel using OpenMP.
12927///
12928/// # Arguments
12929/// - `T`: [0..n-1] The input string.
12930/// - `U`: [0..n-1] The output string (can be T).
12931/// - `A`: [0..n-1+fs] The temporary array.
12932/// - `n`: The length of the given string.
12933/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12934/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
12935/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12936///
12937/// # Returns
12938/// The primary index if no error occurred, -1 or -2 otherwise.
12939pub fn libsais_bwt_omp(
12940    t: &[u8],
12941    u: &mut [u8],
12942    a: &mut [SaSint],
12943    fs: SaSint,
12944    freq: Option<&mut [SaSint]>,
12945    threads: SaSint,
12946) -> SaSint {
12947    let n = t.len();
12948    if threads < 0
12949        || fs < 0
12950        || u.len() < n
12951        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12952        || freq.as_ref().is_some_and(|freq| freq.len() < ALPHABET_SIZE)
12953    {
12954        return -1;
12955    }
12956
12957    if n <= 1 {
12958        if let Some(freq) = freq {
12959            freq[..ALPHABET_SIZE].fill(0);
12960            if n == 1 {
12961                u[0] = t[0];
12962                freq[t[0] as usize] += 1;
12963            }
12964        } else if n == 1 {
12965            u[0] = t[0];
12966        }
12967        return n as SaSint;
12968    }
12969
12970    let threads = if threads > 0 { threads } else { 1 };
12971    let mut index = libsais_main(t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq, threads);
12972    if index >= 0 {
12973        index += 1;
12974        let index_usize = usize::try_from(index).expect("index must be non-negative");
12975        u[0] = t[n - 1];
12976        bwt_copy_8u_omp(
12977            &mut u[1..index_usize],
12978            &a[..index_usize - 1],
12979            index - 1,
12980            threads,
12981        );
12982        bwt_copy_8u_omp(
12983            &mut u[index_usize..n],
12984            &a[index_usize..n],
12985            SaSint::try_from(n - index_usize).expect("fits"),
12986            threads,
12987        );
12988    }
12989    index
12990}
12991
12992/// Constructs the burrows-wheeler transformed string (BWT) of a given string with auxiliary indexes in parallel using OpenMP.
12993///
12994/// # Arguments
12995/// - `T`: [0..n-1] The input string.
12996/// - `U`: [0..n-1] The output string (can be T).
12997/// - `A`: [0..n-1+fs] The temporary array.
12998/// - `n`: The length of the given string.
12999/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
13000/// - `freq`: [0..255] The output symbol frequency table (can be NULL).
13001/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
13002/// - `I`: [0..(n-1)/r] The output auxiliary indexes.
13003/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13004///
13005/// # Returns
13006/// 0 if no error occurred, -1 or -2 otherwise.
13007pub fn libsais_bwt_aux_omp(
13008    t: &[u8],
13009    u: &mut [u8],
13010    a: &mut [SaSint],
13011    fs: SaSint,
13012    freq: Option<&mut [SaSint]>,
13013    r: SaSint,
13014    i: &mut [SaSint],
13015    threads: SaSint,
13016) -> SaSint {
13017    let n = t.len();
13018    if threads < 0
13019        || fs < 0
13020        || r < 2
13021        || (r & (r - 1)) != 0
13022        || u.len() < n
13023        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
13024    {
13025        return -1;
13026    }
13027    if let Some(freq) = freq.as_ref() {
13028        if freq.len() < ALPHABET_SIZE {
13029            return -1;
13030        }
13031    }
13032    let sample_count = if n == 0 {
13033        1
13034    } else {
13035        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
13036            .expect("sample count must be non-negative")
13037            + 1
13038    };
13039    if i.len() < sample_count {
13040        return -1;
13041    }
13042    if n <= 1 {
13043        if let Some(freq) = freq {
13044            freq[..ALPHABET_SIZE].fill(0);
13045            if n == 1 {
13046                u[0] = t[0];
13047                freq[t[0] as usize] += 1;
13048            }
13049        } else if n == 1 {
13050            u[0] = t[0];
13051        }
13052        i[0] = n as SaSint;
13053        return 0;
13054    }
13055
13056    let threads = normalize_omp_threads(threads);
13057    let index = libsais_main(t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq, threads);
13058    if index == 0 {
13059        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
13060        u[0] = t[n - 1];
13061        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], i[0] - 1, threads);
13062        bwt_copy_8u_omp(
13063            &mut u[split..n],
13064            &a[split..n],
13065            SaSint::try_from(n - split).expect("fits"),
13066            threads,
13067        );
13068    }
13069    index
13070}
13071
13072/// Internal helper: compute phi.
13073#[doc(hidden)]
13074pub fn compute_phi(
13075    sa: &[SaSint],
13076    plcp: &mut [SaSint],
13077    n: SaSint,
13078    omp_block_start: FastSint,
13079    omp_block_size: FastSint,
13080) {
13081    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13082    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13083    let end = start + size;
13084    let n_usize = usize::try_from(n).expect("n must be non-negative");
13085    let mut i = start;
13086    let mut k = if omp_block_start > 0 {
13087        sa[start - 1]
13088    } else {
13089        n
13090    };
13091
13092    let fast_end = omp_block_start + omp_block_size - 64 - 3;
13093    while (i as FastSint) < fast_end {
13094        plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")] = k;
13095        k = sa[i];
13096        plcp[usize::try_from(sa[i + 1]).expect("suffix index must be non-negative")] = k;
13097        k = sa[i + 1];
13098        plcp[usize::try_from(sa[i + 2]).expect("suffix index must be non-negative")] = k;
13099        k = sa[i + 2];
13100        plcp[usize::try_from(sa[i + 3]).expect("suffix index must be non-negative")] = k;
13101        k = sa[i + 3];
13102        i += 4;
13103    }
13104
13105    while i < end.min(n_usize) {
13106        plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")] = k;
13107        k = sa[i];
13108        i += 1;
13109    }
13110}
13111
13112/// Internal helper: compute phi (OpenMP variant).
13113#[doc(hidden)]
13114pub fn compute_phi_omp(sa: &[SaSint], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13115    if threads == 1 || n < 65_536 {
13116        compute_phi(sa, plcp, n, 0, n as FastSint);
13117        return;
13118    }
13119
13120    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13121    let block_stride = ((n as FastSint) / (threads as FastSint)) & !15;
13122    let plcp_addr = plcp.as_mut_ptr() as usize;
13123    let n_usize = usize::try_from(n).expect("n must be non-negative");
13124
13125    run_rayon_with_threads(threads_usize, || {
13126        (0..threads_usize).into_par_iter().for_each(|thread| {
13127            let block_start = thread as FastSint * block_stride;
13128            let block_size = if thread + 1 < threads_usize {
13129                block_stride
13130            } else {
13131                n as FastSint - block_start
13132            };
13133            let start = usize::try_from(block_start).expect("omp_block_start must be non-negative");
13134            let size = usize::try_from(block_size).expect("omp_block_size must be non-negative");
13135            let end = start + size;
13136            let mut i = start;
13137            let mut k = if block_start > 0 { sa[start - 1] } else { n };
13138            let plcp_ptr = plcp_addr as *mut SaSint;
13139
13140            let fast_end = block_start + block_size - 64 - 3;
13141            while (i as FastSint) < fast_end {
13142                unsafe {
13143                    // SA is a suffix-array permutation, so each thread writes a disjoint PLCP slot.
13144                    *plcp_ptr
13145                        .add(usize::try_from(sa[i]).expect("suffix index must be non-negative")) =
13146                        k;
13147                    k = sa[i];
13148                    *plcp_ptr.add(
13149                        usize::try_from(sa[i + 1]).expect("suffix index must be non-negative"),
13150                    ) = k;
13151                    k = sa[i + 1];
13152                    *plcp_ptr.add(
13153                        usize::try_from(sa[i + 2]).expect("suffix index must be non-negative"),
13154                    ) = k;
13155                    k = sa[i + 2];
13156                    *plcp_ptr.add(
13157                        usize::try_from(sa[i + 3]).expect("suffix index must be non-negative"),
13158                    ) = k;
13159                    k = sa[i + 3];
13160                }
13161                i += 4;
13162            }
13163
13164            while i < end.min(n_usize) {
13165                unsafe {
13166                    // SA is a suffix-array permutation, so each thread writes a disjoint PLCP slot.
13167                    *plcp_ptr
13168                        .add(usize::try_from(sa[i]).expect("suffix index must be non-negative")) =
13169                        k;
13170                }
13171                k = sa[i];
13172                i += 1;
13173            }
13174        });
13175    });
13176}
13177
13178/// Internal helper: compute plcp.
13179#[doc(hidden)]
13180pub fn compute_plcp(
13181    t: &[u8],
13182    plcp: &mut [SaSint],
13183    n: FastSint,
13184    omp_block_start: FastSint,
13185    omp_block_size: FastSint,
13186) {
13187    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13188    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13189    let end = start + size;
13190    let n_usize = usize::try_from(n).expect("n must be non-negative");
13191    let mut l = 0usize;
13192
13193    for i in start..end.min(n_usize) {
13194        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13195        let m = n_usize - i.max(k);
13196        while l < m && t[i + l] == t[k + l] {
13197            l += 1;
13198        }
13199        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13200        l = l.saturating_sub(1);
13201    }
13202}
13203
13204/// Internal helper: compute plcp (OpenMP variant).
13205#[doc(hidden)]
13206pub fn compute_plcp_omp(t: &[u8], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13207    if threads == 1 || n < 65_536 {
13208        compute_plcp(t, plcp, n as FastSint, 0, n as FastSint);
13209        return;
13210    }
13211
13212    let n_usize = usize::try_from(n).expect("n must be non-negative");
13213    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13214    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13215    run_rayon_with_threads(threads_usize, || {
13216        plcp[..n_usize]
13217            .par_chunks_mut(chunk_size)
13218            .enumerate()
13219            .for_each(|(chunk_index, chunk)| {
13220                let start = chunk_index * chunk_size;
13221                let mut l = 0usize;
13222                for (offset, value) in chunk.iter_mut().enumerate() {
13223                    let i = start + offset;
13224                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13225                    let m = n_usize - i.max(k);
13226                    while l < m && t[i + l] == t[k + l] {
13227                        l += 1;
13228                    }
13229                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13230                    l = l.saturating_sub(1);
13231                }
13232            });
13233    });
13234}
13235
13236/// Internal helper: compute plcp gsa.
13237#[doc(hidden)]
13238pub fn compute_plcp_gsa(
13239    t: &[u8],
13240    plcp: &mut [SaSint],
13241    omp_block_start: FastSint,
13242    omp_block_size: FastSint,
13243) {
13244    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13245    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13246    let end = start + size;
13247    let mut l = 0usize;
13248
13249    for i in start..end.min(t.len()) {
13250        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13251        while t[i + l] > 0 && t[i + l] == t[k + l] {
13252            l += 1;
13253        }
13254        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13255        l = l.saturating_sub(1);
13256    }
13257}
13258
13259/// Internal helper: compute plcp gsa (OpenMP variant).
13260#[doc(hidden)]
13261pub fn compute_plcp_gsa_omp(t: &[u8], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13262    if threads == 1 || n < 65_536 {
13263        compute_plcp_gsa(t, plcp, 0, n as FastSint);
13264        return;
13265    }
13266
13267    let n_usize = usize::try_from(n).expect("n must be non-negative");
13268    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13269    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13270    run_rayon_with_threads(threads_usize, || {
13271        plcp[..n_usize]
13272            .par_chunks_mut(chunk_size)
13273            .enumerate()
13274            .for_each(|(chunk_index, chunk)| {
13275                let start = chunk_index * chunk_size;
13276                let mut l = 0usize;
13277                for (offset, value) in chunk.iter_mut().enumerate() {
13278                    let i = start + offset;
13279                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13280                    while t[i + l] > 0 && t[i + l] == t[k + l] {
13281                        l += 1;
13282                    }
13283                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13284                    l = l.saturating_sub(1);
13285                }
13286            });
13287    });
13288}
13289
13290/// Internal helper: compute plcp int.
13291#[doc(hidden)]
13292pub fn compute_plcp_int(
13293    t: &[SaSint],
13294    plcp: &mut [SaSint],
13295    n: FastSint,
13296    omp_block_start: FastSint,
13297    omp_block_size: FastSint,
13298) {
13299    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13300    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13301    let end = start + size;
13302    let n_usize = usize::try_from(n).expect("n must be non-negative");
13303    let mut l = 0usize;
13304
13305    for i in start..end.min(n_usize) {
13306        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13307        let m = n_usize - i.max(k);
13308        while l < m && t[i + l] == t[k + l] {
13309            l += 1;
13310        }
13311        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13312        l = l.saturating_sub(1);
13313    }
13314}
13315
13316/// Internal helper: compute plcp int (OpenMP variant).
13317#[doc(hidden)]
13318pub fn compute_plcp_int_omp(t: &[SaSint], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13319    if threads == 1 || n < 65_536 {
13320        compute_plcp_int(t, plcp, n as FastSint, 0, n as FastSint);
13321        return;
13322    }
13323
13324    let n_usize = usize::try_from(n).expect("n must be non-negative");
13325    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13326    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13327    run_rayon_with_threads(threads_usize, || {
13328        plcp[..n_usize]
13329            .par_chunks_mut(chunk_size)
13330            .enumerate()
13331            .for_each(|(chunk_index, chunk)| {
13332                let start = chunk_index * chunk_size;
13333                let mut l = 0usize;
13334                for (offset, value) in chunk.iter_mut().enumerate() {
13335                    let i = start + offset;
13336                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13337                    let m = n_usize - i.max(k);
13338                    while l < m && t[i + l] == t[k + l] {
13339                        l += 1;
13340                    }
13341                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13342                    l = l.saturating_sub(1);
13343                }
13344            });
13345    });
13346}
13347
13348/// Internal helper: compute lcp.
13349#[doc(hidden)]
13350pub fn compute_lcp(
13351    plcp: &[SaSint],
13352    sa: &[SaSint],
13353    lcp: &mut [SaSint],
13354    omp_block_start: FastSint,
13355    omp_block_size: FastSint,
13356) {
13357    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13358    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13359    let end = start + size;
13360
13361    for i in start..end.min(sa.len()) {
13362        lcp[i] = plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")];
13363    }
13364}
13365
13366/// Internal helper: compute lcp (OpenMP variant).
13367#[doc(hidden)]
13368pub fn compute_lcp_omp(
13369    plcp: &[SaSint],
13370    sa: &[SaSint],
13371    lcp: &mut [SaSint],
13372    n: SaSint,
13373    threads: SaSint,
13374) {
13375    if threads == 1 || n < 65_536 {
13376        compute_lcp(plcp, sa, lcp, 0, n as FastSint);
13377        return;
13378    }
13379
13380    let n_usize = usize::try_from(n).expect("n must be non-negative");
13381    assert!(plcp.len() >= n_usize);
13382    assert!(sa.len() >= n_usize);
13383    assert!(lcp.len() >= n_usize);
13384    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13385    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13386    let plcp_ptr = plcp.as_ptr() as usize;
13387    let sa_ptr = sa.as_ptr() as usize;
13388    run_rayon_with_threads(threads_usize, || {
13389        lcp[..n_usize]
13390            .par_chunks_mut(chunk_size)
13391            .enumerate()
13392            .for_each(|(chunk_index, chunk)| {
13393                let start = chunk_index * chunk_size;
13394                let dst_ptr = chunk.as_mut_ptr();
13395                let sa_ptr = sa_ptr as *const SaSint;
13396                let plcp_ptr = plcp_ptr as *const SaSint;
13397                for offset in 0..chunk.len() {
13398                    let i = start + offset;
13399                    let suffix = unsafe { *sa_ptr.add(i) };
13400                    let suffix =
13401                        usize::try_from(suffix).expect("suffix index must be non-negative");
13402                    assert!(suffix < plcp.len());
13403                    unsafe {
13404                        *dst_ptr.add(offset) = *plcp_ptr.add(suffix);
13405                    }
13406                }
13407            });
13408    });
13409}
13410
13411/// Constructs the permuted longest common prefix array (PLCP) of a given string and a suffix array.
13412///
13413/// # Arguments
13414/// - `T`: [0..n-1] The input string.
13415/// - `SA`: [0..n-1] The input suffix array.
13416/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13417/// - `n`: The length of the string and the suffix array.
13418///
13419/// # Returns
13420/// 0 if no error occurred, -1 otherwise.
13421pub fn libsais_plcp(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13422    if sa.len() != t.len() || plcp.len() != t.len() {
13423        return -1;
13424    }
13425    if t.len() <= 1 {
13426        if t.len() == 1 {
13427            plcp[0] = 0;
13428        }
13429        return 0;
13430    }
13431
13432    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13433    compute_phi_omp(sa, plcp, n, 1);
13434    compute_plcp_omp(t, plcp, n, 1);
13435    0
13436}
13437
13438/// Constructs the permuted longest common prefix array (PLCP) of a given string set and a generalized suffix array (GSA).
13439///
13440/// # Arguments
13441/// - `T`: [0..n-1] The input string set using 0 as separators (T[n-1] must be 0).
13442/// - `SA`: [0..n-1] The input generalized suffix array.
13443/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13444/// - `n`: The length of the string set and the generalized suffix array.
13445///
13446/// # Returns
13447/// 0 if no error occurred, -1 otherwise.
13448pub fn libsais_plcp_gsa(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13449    if t.last().copied().unwrap_or(0) != 0 {
13450        return -1;
13451    }
13452    if sa.len() != t.len() || plcp.len() != t.len() {
13453        return -1;
13454    }
13455    if t.len() <= 1 {
13456        if t.len() == 1 {
13457            plcp[0] = 0;
13458        }
13459        return 0;
13460    }
13461
13462    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13463    compute_phi_omp(sa, plcp, n, 1);
13464    compute_plcp_gsa_omp(t, plcp, n, 1);
13465    0
13466}
13467
13468/// Constructs the permuted longest common prefix array (PLCP) of a integer array and a suffix array.
13469///
13470/// # Arguments
13471/// - `T`: [0..n-1] The input integer array.
13472/// - `SA`: [0..n-1] The input suffix array.
13473/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13474/// - `n`: The length of the integer array and the suffix array.
13475///
13476/// # Returns
13477/// 0 if no error occurred, -1 otherwise.
13478pub fn libsais_plcp_int(t: &[SaSint], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13479    if sa.len() != t.len() || plcp.len() != t.len() {
13480        return -1;
13481    }
13482    if t.len() <= 1 {
13483        if t.len() == 1 {
13484            plcp[0] = 0;
13485        }
13486        return 0;
13487    }
13488
13489    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13490    compute_phi_omp(sa, plcp, n, 1);
13491    compute_plcp_int_omp(t, plcp, n, 1);
13492    0
13493}
13494
13495/// Constructs the longest common prefix array (LCP) of a given permuted longest common prefix array (PLCP) and a suffix array.
13496///
13497/// # Arguments
13498/// - `PLCP`: [0..n-1] The input permuted longest common prefix array.
13499/// - `SA`: [0..n-1] The input suffix array or generalized suffix array (GSA).
13500/// - `LCP`: [0..n-1] The output longest common prefix array (can be SA).
13501/// - `n`: The length of the permuted longest common prefix array and the suffix array.
13502///
13503/// # Returns
13504/// 0 if no error occurred, -1 otherwise.
13505pub fn libsais_lcp(plcp: &[SaSint], sa: &[SaSint], lcp: &mut [SaSint]) -> SaSint {
13506    if plcp.len() != sa.len() || lcp.len() != sa.len() {
13507        return -1;
13508    }
13509    if sa.len() <= 1 {
13510        if sa.len() == 1 {
13511            lcp[0] = plcp[usize::try_from(sa[0]).expect("suffix index must be non-negative")];
13512        }
13513        return 0;
13514    }
13515
13516    compute_lcp_omp(
13517        plcp,
13518        sa,
13519        lcp,
13520        SaSint::try_from(sa.len()).expect("suffix array length must fit SaSint"),
13521        1,
13522    );
13523    0
13524}
13525
13526/// Constructs the permuted longest common prefix array (PLCP) of a given string and a suffix array in parallel using OpenMP.
13527///
13528/// # Arguments
13529/// - `T`: [0..n-1] The input string.
13530/// - `SA`: [0..n-1] The input suffix array.
13531/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13532/// - `n`: The length of the string and the suffix array.
13533/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13534///
13535/// # Returns
13536/// 0 if no error occurred, -1 otherwise.
13537pub fn libsais_plcp_omp(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint], threads: SaSint) -> SaSint {
13538    if threads < 0 {
13539        return -1;
13540    }
13541    if sa.len() != t.len() || plcp.len() != t.len() {
13542        return -1;
13543    }
13544    if t.len() <= 1 {
13545        if t.len() == 1 {
13546            plcp[0] = 0;
13547        }
13548        return 0;
13549    }
13550
13551    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13552    let threads = normalize_omp_threads(threads);
13553    compute_phi_omp(sa, plcp, n, threads);
13554    compute_plcp_omp(t, plcp, n, threads);
13555    0
13556}
13557
13558/// Constructs the permuted longest common prefix array (PLCP) of a given string set and a generalized suffix array (GSA) in parallel using OpenMP.
13559///
13560/// # Arguments
13561/// - `T`: [0..n-1] The input string set using 0 as separators (T[n-1] must be 0).
13562/// - `SA`: [0..n-1] The input generalized suffix array.
13563/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13564/// - `n`: The length of the string set and the generalized suffix array.
13565/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13566///
13567/// # Returns
13568/// 0 if no error occurred, -1 otherwise.
13569pub fn libsais_plcp_gsa_omp(
13570    t: &[u8],
13571    sa: &[SaSint],
13572    plcp: &mut [SaSint],
13573    threads: SaSint,
13574) -> SaSint {
13575    if threads < 0 || t.last().copied().unwrap_or(0) != 0 {
13576        return -1;
13577    }
13578    if sa.len() != t.len() || plcp.len() != t.len() {
13579        return -1;
13580    }
13581    if t.len() <= 1 {
13582        if t.len() == 1 {
13583            plcp[0] = 0;
13584        }
13585        return 0;
13586    }
13587
13588    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13589    let threads = normalize_omp_threads(threads);
13590    compute_phi_omp(sa, plcp, n, threads);
13591    compute_plcp_gsa_omp(t, plcp, n, threads);
13592    0
13593}
13594
13595/// Constructs the permuted longest common prefix array (PLCP) of a given integer array and a suffix array in parallel using OpenMP.
13596///
13597/// # Arguments
13598/// - `T`: [0..n-1] The input integer array.
13599/// - `SA`: [0..n-1] The input suffix array.
13600/// - `PLCP`: [0..n-1] The output permuted longest common prefix array.
13601/// - `n`: The length of the integer array and the suffix array.
13602/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13603///
13604/// # Returns
13605/// 0 if no error occurred, -1 otherwise.
13606pub fn libsais_plcp_int_omp(
13607    t: &[SaSint],
13608    sa: &[SaSint],
13609    plcp: &mut [SaSint],
13610    threads: SaSint,
13611) -> SaSint {
13612    if threads < 0 {
13613        return -1;
13614    }
13615    if sa.len() != t.len() || plcp.len() != t.len() {
13616        return -1;
13617    }
13618    if t.len() <= 1 {
13619        if t.len() == 1 {
13620            plcp[0] = 0;
13621        }
13622        return 0;
13623    }
13624
13625    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13626    let threads = normalize_omp_threads(threads);
13627    compute_phi_omp(sa, plcp, n, threads);
13628    compute_plcp_int_omp(t, plcp, n, threads);
13629    0
13630}
13631
13632/// Constructs the longest common prefix array (LCP) of a given permuted longest common prefix array (PLCP) and a suffix array in parallel using OpenMP.
13633///
13634/// # Arguments
13635/// - `PLCP`: [0..n-1] The input permuted longest common prefix array.
13636/// - `SA`: [0..n-1] The input suffix array or generalized suffix array (GSA).
13637/// - `LCP`: [0..n-1] The output longest common prefix array (can be SA).
13638/// - `n`: The length of the permuted longest common prefix array and the suffix array.
13639/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13640///
13641/// # Returns
13642/// 0 if no error occurred, -1 otherwise.
13643pub fn libsais_lcp_omp(
13644    plcp: &[SaSint],
13645    sa: &[SaSint],
13646    lcp: &mut [SaSint],
13647    threads: SaSint,
13648) -> SaSint {
13649    if threads < 0 {
13650        return -1;
13651    }
13652    if plcp.len() != sa.len() || lcp.len() != sa.len() {
13653        return -1;
13654    }
13655    if sa.len() <= 1 {
13656        if sa.len() == 1 {
13657            lcp[0] = plcp[usize::try_from(sa[0]).expect("suffix index must be non-negative")];
13658        }
13659        return 0;
13660    }
13661
13662    compute_lcp_omp(
13663        plcp,
13664        sa,
13665        lcp,
13666        SaSint::try_from(sa.len()).expect("suffix array length must fit SaSint"),
13667        normalize_omp_threads(threads),
13668    );
13669    0
13670}
13671
13672/// Internal helper: unbwt compute histogram.
13673#[doc(hidden)]
13674pub fn unbwt_compute_histogram(t: &[u8], n: FastSint, count: &mut [SaUint]) {
13675    let n = usize::try_from(n).expect("n must be non-negative");
13676    assert!(count.len() >= ALPHABET_SIZE);
13677    for &byte in &t[..n] {
13678        count[byte as usize] += 1;
13679    }
13680}
13681
13682/// Internal helper: unbwt transpose bucket2.
13683#[doc(hidden)]
13684pub fn unbwt_transpose_bucket2(bucket2: &mut [SaUint]) {
13685    assert!(bucket2.len() >= ALPHABET_SIZE * ALPHABET_SIZE);
13686    for x in 0..ALPHABET_SIZE {
13687        for y in x + 1..ALPHABET_SIZE {
13688            bucket2.swap((y << 8) + x, (x << 8) + y);
13689        }
13690    }
13691}
13692
13693/// Internal helper: unbwt compute bigram histogram single.
13694#[doc(hidden)]
13695pub fn unbwt_compute_bigram_histogram_single(
13696    t: &[u8],
13697    bucket1: &mut [SaUint],
13698    bucket2: &mut [SaUint],
13699    index: FastUint,
13700) {
13701    let mut sum = 1usize;
13702    for c in 0..ALPHABET_SIZE {
13703        let prev = sum;
13704        sum += bucket1[c] as usize;
13705        bucket1[c] = prev as SaUint;
13706        if prev != sum {
13707            let bucket2_p = &mut bucket2[c << 8..(c + 1) << 8];
13708
13709            let hi = sum.min(index);
13710            if hi > prev {
13711                unbwt_compute_histogram(&t[prev..], (hi - prev) as FastSint, bucket2_p);
13712            }
13713
13714            let lo = prev.max(index + 1);
13715            if sum > lo {
13716                unbwt_compute_histogram(&t[lo - 1..], (sum - lo) as FastSint, bucket2_p);
13717            }
13718        }
13719    }
13720
13721    unbwt_transpose_bucket2(bucket2);
13722}
13723
13724/// Internal helper: unbwt calculate fastbits.
13725#[doc(hidden)]
13726pub fn unbwt_calculate_fastbits(
13727    bucket2: &mut [SaUint],
13728    fastbits: &mut [u16],
13729    lastc: FastUint,
13730    shift: FastUint,
13731) {
13732    let mut v = 0usize;
13733    let mut w = 0usize;
13734    let mut sum = 1usize;
13735
13736    for c in 0..ALPHABET_SIZE {
13737        if c == lastc {
13738            sum += 1;
13739        }
13740
13741        for _d in 0..ALPHABET_SIZE {
13742            let prev = sum;
13743            sum += bucket2[w] as usize;
13744            bucket2[w] = prev as SaUint;
13745            if prev != sum {
13746                while v <= ((sum - 1) >> shift) {
13747                    fastbits[v] = w as u16;
13748                    v += 1;
13749                }
13750            }
13751            w += 1;
13752        }
13753    }
13754}
13755
13756/// Internal helper: unbwt calculate bi psi.
13757#[doc(hidden)]
13758pub fn unbwt_calculate_bi_psi(
13759    t: &[u8],
13760    p: &mut [SaUint],
13761    bucket1: &mut [SaUint],
13762    bucket2: &mut [SaUint],
13763    index: FastUint,
13764    omp_block_start: FastSint,
13765    omp_block_end: FastSint,
13766) {
13767    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13768    let mut j = index;
13769    let block_end = usize::try_from(omp_block_end).expect("omp_block_end must be non-negative");
13770    if block_end < j {
13771        j = block_end;
13772    }
13773    while i < j {
13774        let c = t[i] as usize;
13775        let pidx = bucket1[c] as usize;
13776        bucket1[c] += 1;
13777        let tidx = index as isize - pidx as isize;
13778        if tidx != 0 {
13779            let src =
13780                pidx.wrapping_add((tidx >> ((std::mem::size_of::<FastSint>() * 8) - 1)) as usize);
13781            let w = ((t[src] as usize) << 8) + c;
13782            let dst = bucket2[w] as usize;
13783            p[dst] = i as SaUint;
13784            bucket2[w] += 1;
13785        }
13786        i += 1;
13787    }
13788
13789    let mut i = index;
13790    if usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") > i {
13791        i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13792    }
13793    i += 1;
13794    while i <= block_end {
13795        let c = t[i - 1] as usize;
13796        let pidx = bucket1[c] as usize;
13797        bucket1[c] += 1;
13798        let tidx = index as isize - pidx as isize;
13799        if tidx != 0 {
13800            let src =
13801                pidx.wrapping_add((tidx >> ((std::mem::size_of::<FastSint>() * 8) - 1)) as usize);
13802            let w = ((t[src] as usize) << 8) + c;
13803            let dst = bucket2[w] as usize;
13804            p[dst] = i as SaUint;
13805            bucket2[w] += 1;
13806        }
13807        i += 1;
13808    }
13809}
13810
13811/// Internal helper: unbwt calculate biPSI.
13812#[doc(hidden)]
13813#[allow(dead_code, non_snake_case)]
13814pub fn unbwt_calculate_biPSI(
13815    t: &[u8],
13816    p: &mut [SaUint],
13817    bucket1: &mut [SaUint],
13818    bucket2: &mut [SaUint],
13819    index: FastUint,
13820    omp_block_start: FastSint,
13821    omp_block_end: FastSint,
13822) {
13823    unbwt_calculate_bi_psi(
13824        t,
13825        p,
13826        bucket1,
13827        bucket2,
13828        index,
13829        omp_block_start,
13830        omp_block_end,
13831    );
13832}
13833
13834/// Internal helper: unbwt init single.
13835#[doc(hidden)]
13836pub fn unbwt_init_single(
13837    t: &[u8],
13838    p: &mut [SaUint],
13839    n: SaSint,
13840    freq: Option<&[SaSint]>,
13841    i: &[SaUint],
13842    bucket2: &mut [SaUint],
13843    fastbits: &mut [u16],
13844) {
13845    let mut bucket1 = vec![0u32; ALPHABET_SIZE];
13846    let index = i[0] as usize;
13847    let lastc = t[0] as usize;
13848    let mut shift = 0usize;
13849    while (usize::try_from(n).expect("n must be non-negative") >> shift)
13850        > (1usize << UNBWT_FASTBITS)
13851    {
13852        shift += 1;
13853    }
13854
13855    if let Some(freq) = freq {
13856        for c in 0..ALPHABET_SIZE {
13857            bucket1[c] = freq[c] as SaUint;
13858        }
13859    } else {
13860        unbwt_compute_histogram(t, n as FastSint, &mut bucket1);
13861    }
13862
13863    bucket2.fill(0);
13864    unbwt_compute_bigram_histogram_single(t, &mut bucket1, bucket2, index);
13865    unbwt_calculate_fastbits(bucket2, fastbits, lastc, shift);
13866    unbwt_calculate_bi_psi(t, p, &mut bucket1, bucket2, index, 0, n as FastSint);
13867}
13868
13869/// Internal helper: unbwt compute bigram histogram parallel.
13870#[doc(hidden)]
13871pub fn unbwt_compute_bigram_histogram_parallel(
13872    t: &[u8],
13873    index: FastUint,
13874    bucket1: &mut [SaUint],
13875    bucket2: &mut [SaUint],
13876    omp_block_start: FastSint,
13877    omp_block_size: FastSint,
13878) {
13879    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13880    let end = start + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13881    for &c_u8 in &t[start..end] {
13882        let c = c_u8 as usize;
13883        let p = bucket1[c] as usize;
13884        bucket1[c] += 1;
13885        let tidx = index as isize - p as isize;
13886        if tidx != 0 {
13887            let src =
13888                p.wrapping_add((tidx >> ((std::mem::size_of::<FastSint>() * 8) - 1)) as usize);
13889            let w = ((t[src] as usize) << 8) + c;
13890            bucket2[w] += 1;
13891        }
13892    }
13893}
13894
13895/// Internal helper: unbwt init parallel.
13896#[doc(hidden)]
13897pub fn unbwt_init_parallel(
13898    t: &[u8],
13899    p: &mut [SaUint],
13900    n: SaSint,
13901    freq: Option<&[SaSint]>,
13902    i: &[SaUint],
13903    bucket2: &mut [SaUint],
13904    fastbits: &mut [u16],
13905    buckets: Option<&mut [SaUint]>,
13906    threads: SaSint,
13907) {
13908    let num_threads = usize::try_from(threads.max(1)).expect("threads must be non-negative");
13909    if num_threads <= 1 || usize::try_from(n).expect("n must be non-negative") < 65_536 {
13910        unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
13911        return;
13912    }
13913
13914    let buckets = match buckets {
13915        Some(buckets) => buckets,
13916        None => {
13917            unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
13918            return;
13919        }
13920    };
13921
13922    let segment_len = ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE;
13923    assert!(buckets.len() >= num_threads * segment_len);
13924
13925    let index = i[0] as usize;
13926    let lastc = t[0] as usize;
13927    let mut shift = 0usize;
13928    while (usize::try_from(n).expect("n must be non-negative") >> shift)
13929        > (1usize << UNBWT_FASTBITS)
13930    {
13931        shift += 1;
13932    }
13933
13934    let mut bucket1 = vec![0u32; ALPHABET_SIZE];
13935    bucket2.fill(0);
13936
13937    let n_fast = n as FastSint;
13938    let block_stride = (n_fast / num_threads as FastSint) & (-16);
13939    let mut block_starts = vec![0usize; num_threads];
13940    let mut block_sizes = vec![0usize; num_threads];
13941
13942    for thread in 0..num_threads {
13943        let start = usize::try_from(thread as FastSint * block_stride)
13944            .expect("block start must be non-negative");
13945        let size = if thread + 1 < num_threads {
13946            usize::try_from(block_stride).expect("block stride must be non-negative")
13947        } else {
13948            usize::try_from(n_fast - thread as FastSint * block_stride)
13949                .expect("block size must be non-negative")
13950        };
13951        block_starts[thread] = start;
13952        block_sizes[thread] = size;
13953
13954        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13955        let (bucket1_local, _) = segment.split_at_mut(ALPHABET_SIZE);
13956        bucket1_local.fill(0);
13957        unbwt_compute_histogram(&t[start..], size as FastSint, bucket1_local);
13958    }
13959
13960    for thread in 0..num_threads {
13961        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13962        let (bucket1_temp, _) = segment.split_at_mut(ALPHABET_SIZE);
13963        for c in 0..ALPHABET_SIZE {
13964            let a = bucket1[c];
13965            let b = bucket1_temp[c];
13966            bucket1[c] = a + b;
13967            bucket1_temp[c] = a;
13968        }
13969    }
13970
13971    let mut sum = 1usize;
13972    for c in 0..ALPHABET_SIZE {
13973        let prev = sum;
13974        sum += bucket1[c] as usize;
13975        bucket1[c] = prev as SaUint;
13976    }
13977
13978    for thread in 0..num_threads {
13979        let start = block_starts[thread];
13980        let size = block_sizes[thread];
13981        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13982        let (bucket1_local, bucket2_local) = segment.split_at_mut(ALPHABET_SIZE);
13983        for c in 0..ALPHABET_SIZE {
13984            bucket1_local[c] += bucket1[c];
13985        }
13986        bucket2_local.fill(0);
13987        unbwt_compute_bigram_histogram_parallel(
13988            t,
13989            index,
13990            bucket1_local,
13991            bucket2_local,
13992            start as FastSint,
13993            size as FastSint,
13994        );
13995    }
13996
13997    for thread in 0..num_threads {
13998        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13999        let (_, bucket2_temp) = segment.split_at_mut(ALPHABET_SIZE);
14000        for c in 0..ALPHABET_SIZE * ALPHABET_SIZE {
14001            let a = bucket2[c];
14002            let b = bucket2_temp[c];
14003            bucket2[c] = a + b;
14004            bucket2_temp[c] = a;
14005        }
14006    }
14007
14008    unbwt_calculate_fastbits(bucket2, fastbits, lastc, shift);
14009
14010    for thread in (1..num_threads).rev() {
14011        let src_start = (thread - 1) * segment_len;
14012        let dst_start = thread * segment_len;
14013        let (head, tail) = buckets.split_at_mut(dst_start);
14014        let src = &head[src_start..src_start + ALPHABET_SIZE];
14015        let dst = &mut tail[..ALPHABET_SIZE];
14016        dst.copy_from_slice(src);
14017    }
14018    buckets[..ALPHABET_SIZE].copy_from_slice(&bucket1);
14019
14020    for thread in 0..num_threads {
14021        let start = block_starts[thread];
14022        let size = block_sizes[thread];
14023        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
14024        let (bucket1_local, bucket2_local) = segment.split_at_mut(ALPHABET_SIZE);
14025        for c in 0..ALPHABET_SIZE * ALPHABET_SIZE {
14026            bucket2_local[c] += bucket2[c];
14027        }
14028        unbwt_calculate_bi_psi(
14029            t,
14030            p,
14031            bucket1_local,
14032            bucket2_local,
14033            index,
14034            start as FastSint,
14035            (start + size) as FastSint,
14036        );
14037    }
14038
14039    let last_segment = &buckets[(num_threads - 1) * segment_len..num_threads * segment_len];
14040    let (_, last_bucket2) = last_segment.split_at(ALPHABET_SIZE);
14041    bucket2.copy_from_slice(last_bucket2);
14042}
14043
14044fn bswap16(value: u16) -> u16 {
14045    value.swap_bytes()
14046}
14047
14048fn unbwt_resolve_symbol(bucket2: &[SaUint], fastbits: &[u16], shift: FastUint, p: SaUint) -> u16 {
14049    let mut c = fastbits[(p as usize) >> shift];
14050    while bucket2[c as usize] <= p {
14051        c += 1;
14052    }
14053    c
14054}
14055
14056/// Internal helper: unbwt decode 1.
14057#[doc(hidden)]
14058pub fn unbwt_decode_1(
14059    u: &mut [u8],
14060    p: &[SaUint],
14061    bucket2: &[SaUint],
14062    fastbits: &[u16],
14063    shift: FastUint,
14064    i0: &mut FastUint,
14065    k: FastUint,
14066) {
14067    let words = &mut u[..2 * k];
14068    let mut p0 = *i0 as SaUint;
14069
14070    for i in 0..k {
14071        let c0 = unbwt_resolve_symbol(bucket2, fastbits, shift, p0);
14072        p0 = p[p0 as usize];
14073        let bytes = bswap16(c0).to_ne_bytes();
14074        words[2 * i] = bytes[0];
14075        words[2 * i + 1] = bytes[1];
14076    }
14077
14078    *i0 = p0 as FastUint;
14079}
14080
14081/// Internal helper: unbwt decode 2.
14082#[doc(hidden)]
14083pub fn unbwt_decode_2(
14084    u: &mut [u8],
14085    p: &[SaUint],
14086    bucket2: &[SaUint],
14087    fastbits: &[u16],
14088    shift: FastUint,
14089    r: FastUint,
14090    i0: &mut FastUint,
14091    i1: &mut FastUint,
14092    k: FastUint,
14093) {
14094    let width = 2 * k;
14095    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14096    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14097}
14098
14099/// Internal helper: unbwt decode 3.
14100#[doc(hidden)]
14101pub fn unbwt_decode_3(
14102    u: &mut [u8],
14103    p: &[SaUint],
14104    bucket2: &[SaUint],
14105    fastbits: &[u16],
14106    shift: FastUint,
14107    r: FastUint,
14108    i0: &mut FastUint,
14109    i1: &mut FastUint,
14110    i2: &mut FastUint,
14111    k: FastUint,
14112) {
14113    let width = 2 * k;
14114    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14115    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14116    unbwt_decode_1(
14117        &mut u[2 * r..2 * r + width],
14118        p,
14119        bucket2,
14120        fastbits,
14121        shift,
14122        i2,
14123        k,
14124    );
14125}
14126
14127/// Internal helper: unbwt decode 4.
14128#[doc(hidden)]
14129pub fn unbwt_decode_4(
14130    u: &mut [u8],
14131    p: &[SaUint],
14132    bucket2: &[SaUint],
14133    fastbits: &[u16],
14134    shift: FastUint,
14135    r: FastUint,
14136    i0: &mut FastUint,
14137    i1: &mut FastUint,
14138    i2: &mut FastUint,
14139    i3: &mut FastUint,
14140    k: FastUint,
14141) {
14142    let width = 2 * k;
14143    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14144    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14145    unbwt_decode_1(
14146        &mut u[2 * r..2 * r + width],
14147        p,
14148        bucket2,
14149        fastbits,
14150        shift,
14151        i2,
14152        k,
14153    );
14154    unbwt_decode_1(
14155        &mut u[3 * r..3 * r + width],
14156        p,
14157        bucket2,
14158        fastbits,
14159        shift,
14160        i3,
14161        k,
14162    );
14163}
14164
14165/// Internal helper: unbwt decode 5.
14166#[doc(hidden)]
14167pub fn unbwt_decode_5(
14168    u: &mut [u8],
14169    p: &[SaUint],
14170    bucket2: &[SaUint],
14171    fastbits: &[u16],
14172    shift: FastUint,
14173    r: FastUint,
14174    i0: &mut FastUint,
14175    i1: &mut FastUint,
14176    i2: &mut FastUint,
14177    i3: &mut FastUint,
14178    i4: &mut FastUint,
14179    k: FastUint,
14180) {
14181    let width = 2 * k;
14182    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14183    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14184    unbwt_decode_1(
14185        &mut u[2 * r..2 * r + width],
14186        p,
14187        bucket2,
14188        fastbits,
14189        shift,
14190        i2,
14191        k,
14192    );
14193    unbwt_decode_1(
14194        &mut u[3 * r..3 * r + width],
14195        p,
14196        bucket2,
14197        fastbits,
14198        shift,
14199        i3,
14200        k,
14201    );
14202    unbwt_decode_1(
14203        &mut u[4 * r..4 * r + width],
14204        p,
14205        bucket2,
14206        fastbits,
14207        shift,
14208        i4,
14209        k,
14210    );
14211}
14212
14213/// Internal helper: unbwt decode 6.
14214#[doc(hidden)]
14215pub fn unbwt_decode_6(
14216    u: &mut [u8],
14217    p: &[SaUint],
14218    bucket2: &[SaUint],
14219    fastbits: &[u16],
14220    shift: FastUint,
14221    r: FastUint,
14222    i0: &mut FastUint,
14223    i1: &mut FastUint,
14224    i2: &mut FastUint,
14225    i3: &mut FastUint,
14226    i4: &mut FastUint,
14227    i5: &mut FastUint,
14228    k: FastUint,
14229) {
14230    let width = 2 * k;
14231    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14232    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14233    unbwt_decode_1(
14234        &mut u[2 * r..2 * r + width],
14235        p,
14236        bucket2,
14237        fastbits,
14238        shift,
14239        i2,
14240        k,
14241    );
14242    unbwt_decode_1(
14243        &mut u[3 * r..3 * r + width],
14244        p,
14245        bucket2,
14246        fastbits,
14247        shift,
14248        i3,
14249        k,
14250    );
14251    unbwt_decode_1(
14252        &mut u[4 * r..4 * r + width],
14253        p,
14254        bucket2,
14255        fastbits,
14256        shift,
14257        i4,
14258        k,
14259    );
14260    unbwt_decode_1(
14261        &mut u[5 * r..5 * r + width],
14262        p,
14263        bucket2,
14264        fastbits,
14265        shift,
14266        i5,
14267        k,
14268    );
14269}
14270
14271/// Internal helper: unbwt decode 7.
14272#[doc(hidden)]
14273pub fn unbwt_decode_7(
14274    u: &mut [u8],
14275    p: &[SaUint],
14276    bucket2: &[SaUint],
14277    fastbits: &[u16],
14278    shift: FastUint,
14279    r: FastUint,
14280    i0: &mut FastUint,
14281    i1: &mut FastUint,
14282    i2: &mut FastUint,
14283    i3: &mut FastUint,
14284    i4: &mut FastUint,
14285    i5: &mut FastUint,
14286    i6: &mut FastUint,
14287    k: FastUint,
14288) {
14289    let width = 2 * k;
14290    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14291    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14292    unbwt_decode_1(
14293        &mut u[2 * r..2 * r + width],
14294        p,
14295        bucket2,
14296        fastbits,
14297        shift,
14298        i2,
14299        k,
14300    );
14301    unbwt_decode_1(
14302        &mut u[3 * r..3 * r + width],
14303        p,
14304        bucket2,
14305        fastbits,
14306        shift,
14307        i3,
14308        k,
14309    );
14310    unbwt_decode_1(
14311        &mut u[4 * r..4 * r + width],
14312        p,
14313        bucket2,
14314        fastbits,
14315        shift,
14316        i4,
14317        k,
14318    );
14319    unbwt_decode_1(
14320        &mut u[5 * r..5 * r + width],
14321        p,
14322        bucket2,
14323        fastbits,
14324        shift,
14325        i5,
14326        k,
14327    );
14328    unbwt_decode_1(
14329        &mut u[6 * r..6 * r + width],
14330        p,
14331        bucket2,
14332        fastbits,
14333        shift,
14334        i6,
14335        k,
14336    );
14337}
14338
14339/// Internal helper: unbwt decode 8.
14340#[doc(hidden)]
14341pub fn unbwt_decode_8(
14342    u: &mut [u8],
14343    p: &[SaUint],
14344    bucket2: &[SaUint],
14345    fastbits: &[u16],
14346    shift: FastUint,
14347    r: FastUint,
14348    i0: &mut FastUint,
14349    i1: &mut FastUint,
14350    i2: &mut FastUint,
14351    i3: &mut FastUint,
14352    i4: &mut FastUint,
14353    i5: &mut FastUint,
14354    i6: &mut FastUint,
14355    i7: &mut FastUint,
14356    k: FastUint,
14357) {
14358    let width = 2 * k;
14359    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14360    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14361    unbwt_decode_1(
14362        &mut u[2 * r..2 * r + width],
14363        p,
14364        bucket2,
14365        fastbits,
14366        shift,
14367        i2,
14368        k,
14369    );
14370    unbwt_decode_1(
14371        &mut u[3 * r..3 * r + width],
14372        p,
14373        bucket2,
14374        fastbits,
14375        shift,
14376        i3,
14377        k,
14378    );
14379    unbwt_decode_1(
14380        &mut u[4 * r..4 * r + width],
14381        p,
14382        bucket2,
14383        fastbits,
14384        shift,
14385        i4,
14386        k,
14387    );
14388    unbwt_decode_1(
14389        &mut u[5 * r..5 * r + width],
14390        p,
14391        bucket2,
14392        fastbits,
14393        shift,
14394        i5,
14395        k,
14396    );
14397    unbwt_decode_1(
14398        &mut u[6 * r..6 * r + width],
14399        p,
14400        bucket2,
14401        fastbits,
14402        shift,
14403        i6,
14404        k,
14405    );
14406    unbwt_decode_1(
14407        &mut u[7 * r..7 * r + width],
14408        p,
14409        bucket2,
14410        fastbits,
14411        shift,
14412        i7,
14413        k,
14414    );
14415}
14416
14417/// Internal helper: unbwt decode.
14418#[doc(hidden)]
14419pub fn unbwt_decode(
14420    u: &mut [u8],
14421    p: &[SaUint],
14422    n: SaSint,
14423    r: SaSint,
14424    i: &[SaUint],
14425    bucket2: &[SaUint],
14426    fastbits: &[u16],
14427    mut blocks: FastSint,
14428    remainder: FastUint,
14429) {
14430    let mut shift = 0usize;
14431    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14432        > (1usize << UNBWT_FASTBITS)
14433    {
14434        shift += 1;
14435    }
14436    let mut offset = 0usize;
14437    let mut i_index = 0usize;
14438    let r_usize = usize::try_from(r).expect("r must be non-negative");
14439
14440    while blocks > 8 {
14441        let mut i0 = i[i_index] as FastUint;
14442        let mut i1 = i[i_index + 1] as FastUint;
14443        let mut i2 = i[i_index + 2] as FastUint;
14444        let mut i3 = i[i_index + 3] as FastUint;
14445        let mut i4 = i[i_index + 4] as FastUint;
14446        let mut i5 = i[i_index + 5] as FastUint;
14447        let mut i6 = i[i_index + 6] as FastUint;
14448        let mut i7 = i[i_index + 7] as FastUint;
14449        unbwt_decode_8(
14450            &mut u[offset..],
14451            p,
14452            bucket2,
14453            fastbits,
14454            shift,
14455            r_usize,
14456            &mut i0,
14457            &mut i1,
14458            &mut i2,
14459            &mut i3,
14460            &mut i4,
14461            &mut i5,
14462            &mut i6,
14463            &mut i7,
14464            r_usize >> 1,
14465        );
14466        i_index += 8;
14467        blocks -= 8;
14468        offset += 8 * r_usize;
14469    }
14470
14471    match blocks {
14472        1 => {
14473            let mut i0 = i[i_index] as FastUint;
14474            unbwt_decode_1(
14475                &mut u[offset..],
14476                p,
14477                bucket2,
14478                fastbits,
14479                shift,
14480                &mut i0,
14481                remainder >> 1,
14482            );
14483        }
14484        2 => {
14485            let mut i0 = i[i_index] as FastUint;
14486            let mut i1 = i[i_index + 1] as FastUint;
14487            unbwt_decode_2(
14488                &mut u[offset..],
14489                p,
14490                bucket2,
14491                fastbits,
14492                shift,
14493                r_usize,
14494                &mut i0,
14495                &mut i1,
14496                remainder >> 1,
14497            );
14498            unbwt_decode_1(
14499                &mut u[offset + 2 * (remainder >> 1)..],
14500                p,
14501                bucket2,
14502                fastbits,
14503                shift,
14504                &mut i0,
14505                (r_usize >> 1) - (remainder >> 1),
14506            );
14507        }
14508        3 => {
14509            let mut i0 = i[i_index] as FastUint;
14510            let mut i1 = i[i_index + 1] as FastUint;
14511            let mut i2 = i[i_index + 2] as FastUint;
14512            unbwt_decode_3(
14513                &mut u[offset..],
14514                p,
14515                bucket2,
14516                fastbits,
14517                shift,
14518                r_usize,
14519                &mut i0,
14520                &mut i1,
14521                &mut i2,
14522                remainder >> 1,
14523            );
14524            unbwt_decode_2(
14525                &mut u[offset + 2 * (remainder >> 1)..],
14526                p,
14527                bucket2,
14528                fastbits,
14529                shift,
14530                r_usize,
14531                &mut i0,
14532                &mut i1,
14533                (r_usize >> 1) - (remainder >> 1),
14534            );
14535        }
14536        4 => {
14537            let mut i0 = i[i_index] as FastUint;
14538            let mut i1 = i[i_index + 1] as FastUint;
14539            let mut i2 = i[i_index + 2] as FastUint;
14540            let mut i3 = i[i_index + 3] as FastUint;
14541            unbwt_decode_4(
14542                &mut u[offset..],
14543                p,
14544                bucket2,
14545                fastbits,
14546                shift,
14547                r_usize,
14548                &mut i0,
14549                &mut i1,
14550                &mut i2,
14551                &mut i3,
14552                remainder >> 1,
14553            );
14554            unbwt_decode_3(
14555                &mut u[offset + 2 * (remainder >> 1)..],
14556                p,
14557                bucket2,
14558                fastbits,
14559                shift,
14560                r_usize,
14561                &mut i0,
14562                &mut i1,
14563                &mut i2,
14564                (r_usize >> 1) - (remainder >> 1),
14565            );
14566        }
14567        5 => {
14568            let mut i0 = i[i_index] as FastUint;
14569            let mut i1 = i[i_index + 1] as FastUint;
14570            let mut i2 = i[i_index + 2] as FastUint;
14571            let mut i3 = i[i_index + 3] as FastUint;
14572            let mut i4 = i[i_index + 4] as FastUint;
14573            unbwt_decode_5(
14574                &mut u[offset..],
14575                p,
14576                bucket2,
14577                fastbits,
14578                shift,
14579                r_usize,
14580                &mut i0,
14581                &mut i1,
14582                &mut i2,
14583                &mut i3,
14584                &mut i4,
14585                remainder >> 1,
14586            );
14587            unbwt_decode_4(
14588                &mut u[offset + 2 * (remainder >> 1)..],
14589                p,
14590                bucket2,
14591                fastbits,
14592                shift,
14593                r_usize,
14594                &mut i0,
14595                &mut i1,
14596                &mut i2,
14597                &mut i3,
14598                (r_usize >> 1) - (remainder >> 1),
14599            );
14600        }
14601        6 => {
14602            let mut i0 = i[i_index] as FastUint;
14603            let mut i1 = i[i_index + 1] as FastUint;
14604            let mut i2 = i[i_index + 2] as FastUint;
14605            let mut i3 = i[i_index + 3] as FastUint;
14606            let mut i4 = i[i_index + 4] as FastUint;
14607            let mut i5 = i[i_index + 5] as FastUint;
14608            unbwt_decode_6(
14609                &mut u[offset..],
14610                p,
14611                bucket2,
14612                fastbits,
14613                shift,
14614                r_usize,
14615                &mut i0,
14616                &mut i1,
14617                &mut i2,
14618                &mut i3,
14619                &mut i4,
14620                &mut i5,
14621                remainder >> 1,
14622            );
14623            unbwt_decode_5(
14624                &mut u[offset + 2 * (remainder >> 1)..],
14625                p,
14626                bucket2,
14627                fastbits,
14628                shift,
14629                r_usize,
14630                &mut i0,
14631                &mut i1,
14632                &mut i2,
14633                &mut i3,
14634                &mut i4,
14635                (r_usize >> 1) - (remainder >> 1),
14636            );
14637        }
14638        7 => {
14639            let mut i0 = i[i_index] as FastUint;
14640            let mut i1 = i[i_index + 1] as FastUint;
14641            let mut i2 = i[i_index + 2] as FastUint;
14642            let mut i3 = i[i_index + 3] as FastUint;
14643            let mut i4 = i[i_index + 4] as FastUint;
14644            let mut i5 = i[i_index + 5] as FastUint;
14645            let mut i6 = i[i_index + 6] as FastUint;
14646            unbwt_decode_7(
14647                &mut u[offset..],
14648                p,
14649                bucket2,
14650                fastbits,
14651                shift,
14652                r_usize,
14653                &mut i0,
14654                &mut i1,
14655                &mut i2,
14656                &mut i3,
14657                &mut i4,
14658                &mut i5,
14659                &mut i6,
14660                remainder >> 1,
14661            );
14662            unbwt_decode_6(
14663                &mut u[offset + 2 * (remainder >> 1)..],
14664                p,
14665                bucket2,
14666                fastbits,
14667                shift,
14668                r_usize,
14669                &mut i0,
14670                &mut i1,
14671                &mut i2,
14672                &mut i3,
14673                &mut i4,
14674                &mut i5,
14675                (r_usize >> 1) - (remainder >> 1),
14676            );
14677        }
14678        8 => {
14679            let mut i0 = i[i_index] as FastUint;
14680            let mut i1 = i[i_index + 1] as FastUint;
14681            let mut i2 = i[i_index + 2] as FastUint;
14682            let mut i3 = i[i_index + 3] as FastUint;
14683            let mut i4 = i[i_index + 4] as FastUint;
14684            let mut i5 = i[i_index + 5] as FastUint;
14685            let mut i6 = i[i_index + 6] as FastUint;
14686            let mut i7 = i[i_index + 7] as FastUint;
14687            unbwt_decode_8(
14688                &mut u[offset..],
14689                p,
14690                bucket2,
14691                fastbits,
14692                shift,
14693                r_usize,
14694                &mut i0,
14695                &mut i1,
14696                &mut i2,
14697                &mut i3,
14698                &mut i4,
14699                &mut i5,
14700                &mut i6,
14701                &mut i7,
14702                remainder >> 1,
14703            );
14704            unbwt_decode_7(
14705                &mut u[offset + 2 * (remainder >> 1)..],
14706                p,
14707                bucket2,
14708                fastbits,
14709                shift,
14710                r_usize,
14711                &mut i0,
14712                &mut i1,
14713                &mut i2,
14714                &mut i3,
14715                &mut i4,
14716                &mut i5,
14717                &mut i6,
14718                (r_usize >> 1) - (remainder >> 1),
14719            );
14720        }
14721        _ => {}
14722    }
14723}
14724
14725/// Internal helper: unbwt decode (OpenMP variant).
14726#[doc(hidden)]
14727pub fn unbwt_decode_omp(
14728    t: &[u8],
14729    u: &mut [u8],
14730    p: &[SaUint],
14731    n: SaSint,
14732    r: SaSint,
14733    i: &[SaUint],
14734    bucket2: &[SaUint],
14735    fastbits: &[u16],
14736    threads: SaSint,
14737) {
14738    let lastc = t[0];
14739    let blocks = 1 + ((n as FastSint - 1) / r as FastSint);
14740    let remainder = usize::try_from(n).expect("n must be non-negative")
14741        - usize::try_from(r).expect("r must be non-negative")
14742            * (usize::try_from(blocks).expect("blocks") - 1);
14743    let max_threads = usize::try_from(blocks.min(threads.max(1) as FastSint))
14744        .expect("thread count must fit usize");
14745    let block_stride = usize::try_from(blocks).expect("blocks must be non-negative") / max_threads;
14746    let block_remainder =
14747        usize::try_from(blocks).expect("blocks must be non-negative") % max_threads;
14748    let r_usize = usize::try_from(r).expect("r must be non-negative");
14749
14750    let u_ptr = SyncMutPtr::new(u);
14751    run_rayon_with_threads(max_threads, || {
14752        (0..max_threads).into_par_iter().for_each(|thread| {
14753            let block_size = block_stride + usize::from(thread < block_remainder);
14754            let block_start = block_stride * thread + thread.min(block_remainder);
14755            let u = unsafe { u_ptr.as_slice() };
14756            unbwt_decode(
14757                &mut u[r_usize * block_start..],
14758                p,
14759                n,
14760                r,
14761                &i[block_start..],
14762                bucket2,
14763                fastbits,
14764                block_size as FastSint,
14765                if thread + 1 < max_threads {
14766                    r_usize
14767                } else {
14768                    remainder
14769                },
14770            );
14771        });
14772    });
14773    u[usize::try_from(n).expect("n must be non-negative") - 1] = lastc;
14774}
14775
14776/// Internal helper: unbwt core.
14777#[doc(hidden)]
14778pub fn unbwt_core(
14779    t: &[u8],
14780    u: &mut [u8],
14781    p: &mut [SaUint],
14782    n: SaSint,
14783    freq: Option<&[SaSint]>,
14784    r: SaSint,
14785    i: &[SaUint],
14786    bucket2: &mut [SaUint],
14787    fastbits: &mut [u16],
14788    buckets: Option<&mut [SaUint]>,
14789    threads: SaSint,
14790) -> SaSint {
14791    if threads > 1 && n >= 262_144 {
14792        unbwt_init_parallel(t, p, n, freq, i, bucket2, fastbits, buckets, threads);
14793    } else {
14794        unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
14795    }
14796
14797    unbwt_decode_omp(t, u, p, n, r, i, bucket2, fastbits, threads);
14798    0
14799}
14800
14801/// Internal helper: unbwt main.
14802#[doc(hidden)]
14803pub fn unbwt_main(
14804    t: &[u8],
14805    u: &mut [u8],
14806    p: &mut [SaUint],
14807    n: SaSint,
14808    freq: Option<&[SaSint]>,
14809    r: SaSint,
14810    i: &[SaUint],
14811    threads: SaSint,
14812) -> SaSint {
14813    let mut shift = 0usize;
14814    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14815        > (1usize << UNBWT_FASTBITS)
14816    {
14817        shift += 1;
14818    }
14819
14820    let mut bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
14821    let mut fastbits =
14822        vec![0u16; 1 + (usize::try_from(n).expect("n must be non-negative") >> shift)];
14823    let mut buckets = if threads > 1 && n >= 262_144 {
14824        Some(vec![
14825            0u32;
14826            usize::try_from(threads)
14827                .expect("threads must be non-negative")
14828                * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)
14829        ])
14830    } else {
14831        None
14832    };
14833
14834    unbwt_core(
14835        t,
14836        u,
14837        p,
14838        n,
14839        freq,
14840        r,
14841        i,
14842        &mut bucket2,
14843        &mut fastbits,
14844        buckets.as_deref_mut(),
14845        threads,
14846    )
14847}
14848
14849/// Internal helper: unbwt main ctx.
14850#[doc(hidden)]
14851pub fn unbwt_main_ctx(
14852    ctx: &mut UnbwtContext,
14853    t: &[u8],
14854    u: &mut [u8],
14855    p: &mut [SaUint],
14856    n: SaSint,
14857    freq: Option<&[SaSint]>,
14858    r: SaSint,
14859    i: &[SaUint],
14860) -> SaSint {
14861    if ctx.threads <= 0 {
14862        return -2;
14863    }
14864    let mut shift = 0usize;
14865    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14866        > (1usize << UNBWT_FASTBITS)
14867    {
14868        shift += 1;
14869    }
14870    let required_fastbits = 1 + (usize::try_from(n).expect("n must be non-negative") >> shift);
14871    if ctx.bucket2.len() < ALPHABET_SIZE * ALPHABET_SIZE
14872        || ctx.fastbits.len() < required_fastbits
14873        || (ctx.threads > 1 && ctx.buckets.is_none())
14874    {
14875        return -2;
14876    }
14877
14878    unbwt_core(
14879        t,
14880        u,
14881        p,
14882        n,
14883        freq,
14884        r,
14885        i,
14886        &mut ctx.bucket2,
14887        &mut ctx.fastbits,
14888        ctx.buckets.as_deref_mut(),
14889        ctx.threads as SaSint,
14890    )
14891}
14892
14893/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with primary index.
14894///
14895/// # Arguments
14896/// - `T`: [0..n-1] The input string.
14897/// - `U`: [0..n-1] The output string (can be T).
14898/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
14899/// - `n`: The length of the given string.
14900/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
14901/// - `i`: The primary index.
14902///
14903/// # Returns
14904/// 0 if no error occurred, -1 or -2 otherwise.
14905pub fn libsais_unbwt(
14906    t: &[u8],
14907    u: &mut [u8],
14908    a: &mut [SaSint],
14909    freq: Option<&[SaSint]>,
14910    i: SaSint,
14911) -> SaSint {
14912    libsais_unbwt_aux(
14913        t,
14914        u,
14915        a,
14916        freq,
14917        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
14918        &[i],
14919    )
14920}
14921
14922/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with primary index using libsais reverse BWT context.
14923///
14924/// # Arguments
14925/// - `ctx`: The libsais reverse BWT context.
14926/// - `T`: [0..n-1] The input string.
14927/// - `U`: [0..n-1] The output string (can be T).
14928/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
14929/// - `n`: The length of the given string.
14930/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
14931/// - `i`: The primary index.
14932///
14933/// # Returns
14934/// 0 if no error occurred, -1 or -2 otherwise.
14935pub fn libsais_unbwt_ctx(
14936    ctx: &mut UnbwtContext,
14937    t: &[u8],
14938    u: &mut [u8],
14939    a: &mut [SaSint],
14940    freq: Option<&[SaSint]>,
14941    i: SaSint,
14942) -> SaSint {
14943    libsais_unbwt_aux_ctx(
14944        ctx,
14945        t,
14946        u,
14947        a,
14948        freq,
14949        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
14950        &[i],
14951    )
14952}
14953
14954/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with auxiliary indexes.
14955///
14956/// # Arguments
14957/// - `T`: [0..n-1] The input string.
14958/// - `U`: [0..n-1] The output string (can be T).
14959/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
14960/// - `n`: The length of the given string.
14961/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
14962/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
14963/// - `I`: [0..(n-1)/r] The input auxiliary indexes.
14964///
14965/// # Returns
14966/// 0 if no error occurred, -1 or -2 otherwise.
14967pub fn libsais_unbwt_aux(
14968    t: &[u8],
14969    u: &mut [u8],
14970    a: &mut [SaSint],
14971    freq: Option<&[SaSint]>,
14972    r: SaSint,
14973    i: &[SaSint],
14974) -> SaSint {
14975    let t_len = t.len();
14976    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
14977    if u.len() < t_len
14978        || a.len() < t_len
14979        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
14980        || (r != n && (r < 2 || (r & (r - 1)) != 0))
14981    {
14982        return -1;
14983    }
14984    let sample_count = if n == 0 {
14985        1
14986    } else {
14987        ((n - 1) / r + 1) as usize
14988    };
14989    if i.len() < sample_count {
14990        return -1;
14991    }
14992
14993    if n <= 1 {
14994        if i[0] != n {
14995            return -1;
14996        }
14997        if n == 1 {
14998            u[0] = t[0];
14999        }
15000        return 0;
15001    }
15002
15003    for t in 0..sample_count {
15004        let sample = i[t];
15005        if sample <= 0 || sample > n {
15006            return -1;
15007        }
15008    }
15009
15010    let i_u32: Vec<SaUint> = i
15011        .iter()
15012        .take(sample_count)
15013        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
15014        .collect();
15015    let mut p = vec![0u32; t_len + 1];
15016    let result = unbwt_main(t, u, &mut p, n, freq, r, &i_u32, 1);
15017    for t in 0..t_len {
15018        a[t] = p[t] as SaSint;
15019    }
15020    result
15021}
15022
15023/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with auxiliary indexes using libsais reverse BWT context.
15024///
15025/// # Arguments
15026/// - `ctx`: The libsais reverse BWT context.
15027/// - `T`: [0..n-1] The input string.
15028/// - `U`: [0..n-1] The output string (can be T).
15029/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
15030/// - `n`: The length of the given string.
15031/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
15032/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
15033/// - `I`: [0..(n-1)/r] The input auxiliary indexes.
15034///
15035/// # Returns
15036/// 0 if no error occurred, -1 or -2 otherwise.
15037pub fn libsais_unbwt_aux_ctx(
15038    ctx: &mut UnbwtContext,
15039    t: &[u8],
15040    u: &mut [u8],
15041    a: &mut [SaSint],
15042    freq: Option<&[SaSint]>,
15043    r: SaSint,
15044    i: &[SaSint],
15045) -> SaSint {
15046    let t_len = t.len();
15047    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
15048    if u.len() < t_len
15049        || a.len() < t_len
15050        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
15051        || (r != n && (r < 2 || (r & (r - 1)) != 0))
15052    {
15053        return -1;
15054    }
15055    let sample_count = if n == 0 {
15056        1
15057    } else {
15058        ((n - 1) / r + 1) as usize
15059    };
15060    if i.len() < sample_count {
15061        return -1;
15062    }
15063
15064    if n <= 1 {
15065        if i[0] != n {
15066            return -1;
15067        }
15068        if n == 1 {
15069            u[0] = t[0];
15070        }
15071        return 0;
15072    }
15073
15074    for t in 0..sample_count {
15075        let sample = i[t];
15076        if sample <= 0 || sample > n {
15077            return -1;
15078        }
15079    }
15080
15081    let i_u32: Vec<SaUint> = i
15082        .iter()
15083        .take(sample_count)
15084        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
15085        .collect();
15086    let mut p = vec![0u32; t_len + 1];
15087    let result = unbwt_main_ctx(ctx, t, u, &mut p, n, freq, r, &i_u32);
15088    for t in 0..t_len {
15089        a[t] = p[t] as SaSint;
15090    }
15091    result
15092}
15093
15094/// Creates the libsais reverse BWT context that allows reusing allocated memory with each parallel libsais_unbwt_* operation using OpenMP.
15095/// In multi-threaded environments, use one context per thread for parallel executions.
15096///
15097/// # Arguments
15098/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
15099///
15100/// # Returns
15101/// the libsais context, NULL otherwise.
15102pub fn unbwt_create_ctx_omp(threads: SaSint) -> Option<UnbwtContext> {
15103    if threads < 0 {
15104        return None;
15105    }
15106    unbwt_create_ctx_main(normalize_omp_threads(threads))
15107}
15108
15109/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with primary index in parallel using OpenMP.
15110///
15111/// # Arguments
15112/// - `T`: [0..n-1] The input string.
15113/// - `U`: [0..n-1] The output string (can be T).
15114/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
15115/// - `n`: The length of the given string.
15116/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
15117/// - `i`: The primary index.
15118/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
15119///
15120/// # Returns
15121/// 0 if no error occurred, -1 or -2 otherwise.
15122pub fn libsais_unbwt_omp(
15123    t: &[u8],
15124    u: &mut [u8],
15125    a: &mut [SaSint],
15126    freq: Option<&[SaSint]>,
15127    i: SaSint,
15128    threads: SaSint,
15129) -> SaSint {
15130    libsais_unbwt_aux_omp(
15131        t,
15132        u,
15133        a,
15134        freq,
15135        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
15136        &[i],
15137        threads,
15138    )
15139}
15140
15141/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with auxiliary indexes in parallel using OpenMP.
15142///
15143/// # Arguments
15144/// - `T`: [0..n-1] The input string.
15145/// - `U`: [0..n-1] The output string (can be T).
15146/// - `A`: [0..n] The temporary array (NOTE, temporary array must be n + 1 size).
15147/// - `n`: The length of the given string.
15148/// - `freq`: [0..255] The input symbol frequency table (can be NULL).
15149/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
15150/// - `I`: [0..(n-1)/r] The input auxiliary indexes.
15151/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
15152///
15153/// # Returns
15154/// 0 if no error occurred, -1 or -2 otherwise.
15155pub fn libsais_unbwt_aux_omp(
15156    t: &[u8],
15157    u: &mut [u8],
15158    a: &mut [SaSint],
15159    freq: Option<&[SaSint]>,
15160    r: SaSint,
15161    i: &[SaSint],
15162    threads: SaSint,
15163) -> SaSint {
15164    let t_len = t.len();
15165    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
15166    if threads < 0
15167        || u.len() < t_len
15168        || a.len() < t_len
15169        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
15170        || (r != n && (r < 2 || (r & (r - 1)) != 0))
15171    {
15172        return -1;
15173    }
15174    let sample_count = if n == 0 {
15175        1
15176    } else {
15177        ((n - 1) / r + 1) as usize
15178    };
15179    if i.len() < sample_count {
15180        return -1;
15181    }
15182
15183    if n <= 1 {
15184        if i[0] != n {
15185            return -1;
15186        }
15187        if n == 1 {
15188            u[0] = t[0];
15189        }
15190        return 0;
15191    }
15192
15193    for sample in i.iter().take(sample_count) {
15194        let sample = *sample;
15195        if sample <= 0 || sample > n {
15196            return -1;
15197        }
15198    }
15199
15200    let threads = if threads > 0 { threads } else { 1 };
15201    let i_u32: Vec<SaUint> = i
15202        .iter()
15203        .take(sample_count)
15204        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
15205        .collect();
15206    let mut p = vec![0u32; t_len + 1];
15207    let result = unbwt_main(t, u, &mut p, n, freq, r, &i_u32, threads);
15208    for idx in 0..t_len {
15209        a[idx] = p[idx] as SaSint;
15210    }
15211    result
15212}
15213
15214/// Internal helper: bwt copy 8u.
15215#[doc(hidden)]
15216pub fn bwt_copy_8u(u: &mut [u8], a: &[SaSint], n: SaSint) {
15217    if n <= 0 {
15218        return;
15219    }
15220
15221    let n_usize = usize::try_from(n).expect("n must be non-negative");
15222    for i in 0..n_usize {
15223        u[i] = a[i] as u8;
15224    }
15225}
15226
15227/// Internal helper: bwt copy 8u (OpenMP variant).
15228#[doc(hidden)]
15229pub fn bwt_copy_8u_omp(u: &mut [u8], a: &[SaSint], n: SaSint, threads: SaSint) {
15230    if threads == 1 || n < 65_536 {
15231        bwt_copy_8u(u, a, n);
15232        return;
15233    }
15234
15235    let n_usize = usize::try_from(n).expect("n must be non-negative");
15236    assert!(u.len() >= n_usize);
15237    assert!(a.len() >= n_usize);
15238    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
15239    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
15240    let a_ptr = a.as_ptr() as usize;
15241    run_rayon_with_threads(threads_usize, || {
15242        u[..n_usize]
15243            .par_chunks_mut(chunk_size)
15244            .enumerate()
15245            .for_each(|(chunk_index, chunk)| {
15246                let start = chunk_index * chunk_size;
15247                let dst_ptr = chunk.as_mut_ptr();
15248                let src_ptr = unsafe { (a_ptr as *const SaSint).add(start) };
15249                for offset in 0..chunk.len() {
15250                    unsafe {
15251                        *dst_ptr.add(offset) = *src_ptr.add(offset) as u8;
15252                    }
15253                }
15254            });
15255    });
15256}
15257
15258/// Internal helper: accumulate counts s32 2.
15259#[doc(hidden)]
15260pub fn accumulate_counts_s32_2(bucket00: &mut [SaSint], bucket01: &[SaSint]) {
15261    assert_eq!(bucket00.len(), bucket01.len());
15262    for (dst, src) in bucket00.iter_mut().zip(bucket01.iter()) {
15263        *dst += *src;
15264    }
15265}
15266
15267/// Internal helper: accumulate counts s32 3.
15268#[doc(hidden)]
15269pub fn accumulate_counts_s32_3(bucket00: &mut [SaSint], bucket01: &[SaSint], bucket02: &[SaSint]) {
15270    assert_eq!(bucket00.len(), bucket01.len());
15271    assert_eq!(bucket00.len(), bucket02.len());
15272    for ((dst, src1), src2) in bucket00
15273        .iter_mut()
15274        .zip(bucket01.iter())
15275        .zip(bucket02.iter())
15276    {
15277        *dst += *src1 + *src2;
15278    }
15279}
15280
15281/// Internal helper: accumulate counts s32 4.
15282#[doc(hidden)]
15283pub fn accumulate_counts_s32_4(
15284    bucket00: &mut [SaSint],
15285    bucket01: &[SaSint],
15286    bucket02: &[SaSint],
15287    bucket03: &[SaSint],
15288) {
15289    assert_eq!(bucket00.len(), bucket01.len());
15290    assert_eq!(bucket00.len(), bucket02.len());
15291    assert_eq!(bucket00.len(), bucket03.len());
15292    for (((dst, src1), src2), src3) in bucket00
15293        .iter_mut()
15294        .zip(bucket01.iter())
15295        .zip(bucket02.iter())
15296        .zip(bucket03.iter())
15297    {
15298        *dst += *src1 + *src2 + *src3;
15299    }
15300}
15301
15302/// Internal helper: accumulate counts s32 5.
15303#[doc(hidden)]
15304pub fn accumulate_counts_s32_5(
15305    bucket00: &mut [SaSint],
15306    bucket01: &[SaSint],
15307    bucket02: &[SaSint],
15308    bucket03: &[SaSint],
15309    bucket04: &[SaSint],
15310) {
15311    assert_eq!(bucket00.len(), bucket01.len());
15312    assert_eq!(bucket00.len(), bucket02.len());
15313    assert_eq!(bucket00.len(), bucket03.len());
15314    assert_eq!(bucket00.len(), bucket04.len());
15315    for ((((dst, src1), src2), src3), src4) in bucket00
15316        .iter_mut()
15317        .zip(bucket01.iter())
15318        .zip(bucket02.iter())
15319        .zip(bucket03.iter())
15320        .zip(bucket04.iter())
15321    {
15322        *dst += *src1 + *src2 + *src3 + *src4;
15323    }
15324}
15325
15326/// Internal helper: accumulate counts s32 6.
15327#[doc(hidden)]
15328pub fn accumulate_counts_s32_6(
15329    bucket00: &mut [SaSint],
15330    bucket01: &[SaSint],
15331    bucket02: &[SaSint],
15332    bucket03: &[SaSint],
15333    bucket04: &[SaSint],
15334    bucket05: &[SaSint],
15335) {
15336    assert_eq!(bucket00.len(), bucket01.len());
15337    assert_eq!(bucket00.len(), bucket02.len());
15338    assert_eq!(bucket00.len(), bucket03.len());
15339    assert_eq!(bucket00.len(), bucket04.len());
15340    assert_eq!(bucket00.len(), bucket05.len());
15341    for (((((dst, src1), src2), src3), src4), src5) in bucket00
15342        .iter_mut()
15343        .zip(bucket01.iter())
15344        .zip(bucket02.iter())
15345        .zip(bucket03.iter())
15346        .zip(bucket04.iter())
15347        .zip(bucket05.iter())
15348    {
15349        *dst += *src1 + *src2 + *src3 + *src4 + *src5;
15350    }
15351}
15352
15353/// Internal helper: accumulate counts s32 7.
15354#[doc(hidden)]
15355pub fn accumulate_counts_s32_7(
15356    bucket00: &mut [SaSint],
15357    bucket01: &[SaSint],
15358    bucket02: &[SaSint],
15359    bucket03: &[SaSint],
15360    bucket04: &[SaSint],
15361    bucket05: &[SaSint],
15362    bucket06: &[SaSint],
15363) {
15364    assert_eq!(bucket00.len(), bucket01.len());
15365    assert_eq!(bucket00.len(), bucket02.len());
15366    assert_eq!(bucket00.len(), bucket03.len());
15367    assert_eq!(bucket00.len(), bucket04.len());
15368    assert_eq!(bucket00.len(), bucket05.len());
15369    assert_eq!(bucket00.len(), bucket06.len());
15370    for ((((((dst, src1), src2), src3), src4), src5), src6) in bucket00
15371        .iter_mut()
15372        .zip(bucket01.iter())
15373        .zip(bucket02.iter())
15374        .zip(bucket03.iter())
15375        .zip(bucket04.iter())
15376        .zip(bucket05.iter())
15377        .zip(bucket06.iter())
15378    {
15379        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6;
15380    }
15381}
15382
15383/// Internal helper: accumulate counts s32 8.
15384#[doc(hidden)]
15385pub fn accumulate_counts_s32_8(
15386    bucket00: &mut [SaSint],
15387    bucket01: &[SaSint],
15388    bucket02: &[SaSint],
15389    bucket03: &[SaSint],
15390    bucket04: &[SaSint],
15391    bucket05: &[SaSint],
15392    bucket06: &[SaSint],
15393    bucket07: &[SaSint],
15394) {
15395    assert_eq!(bucket00.len(), bucket01.len());
15396    assert_eq!(bucket00.len(), bucket02.len());
15397    assert_eq!(bucket00.len(), bucket03.len());
15398    assert_eq!(bucket00.len(), bucket04.len());
15399    assert_eq!(bucket00.len(), bucket05.len());
15400    assert_eq!(bucket00.len(), bucket06.len());
15401    assert_eq!(bucket00.len(), bucket07.len());
15402    for (((((((dst, src1), src2), src3), src4), src5), src6), src7) in bucket00
15403        .iter_mut()
15404        .zip(bucket01.iter())
15405        .zip(bucket02.iter())
15406        .zip(bucket03.iter())
15407        .zip(bucket04.iter())
15408        .zip(bucket05.iter())
15409        .zip(bucket06.iter())
15410        .zip(bucket07.iter())
15411    {
15412        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6 + *src7;
15413    }
15414}
15415
15416/// Internal helper: accumulate counts s32 9.
15417#[doc(hidden)]
15418pub fn accumulate_counts_s32_9(
15419    bucket00: &mut [SaSint],
15420    bucket01: &[SaSint],
15421    bucket02: &[SaSint],
15422    bucket03: &[SaSint],
15423    bucket04: &[SaSint],
15424    bucket05: &[SaSint],
15425    bucket06: &[SaSint],
15426    bucket07: &[SaSint],
15427    bucket08: &[SaSint],
15428) {
15429    assert_eq!(bucket00.len(), bucket01.len());
15430    assert_eq!(bucket00.len(), bucket02.len());
15431    assert_eq!(bucket00.len(), bucket03.len());
15432    assert_eq!(bucket00.len(), bucket04.len());
15433    assert_eq!(bucket00.len(), bucket05.len());
15434    assert_eq!(bucket00.len(), bucket06.len());
15435    assert_eq!(bucket00.len(), bucket07.len());
15436    assert_eq!(bucket00.len(), bucket08.len());
15437    for ((((((((dst, src1), src2), src3), src4), src5), src6), src7), src8) in bucket00
15438        .iter_mut()
15439        .zip(bucket01.iter())
15440        .zip(bucket02.iter())
15441        .zip(bucket03.iter())
15442        .zip(bucket04.iter())
15443        .zip(bucket05.iter())
15444        .zip(bucket06.iter())
15445        .zip(bucket07.iter())
15446        .zip(bucket08.iter())
15447    {
15448        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6 + *src7 + *src8;
15449    }
15450}
15451
15452/// Internal helper: accumulate counts s32.
15453#[doc(hidden)]
15454pub fn accumulate_counts_s32(
15455    buckets: &mut [SaSint],
15456    bucket_size: FastSint,
15457    bucket_stride: FastSint,
15458    mut num_buckets: FastSint,
15459) {
15460    if num_buckets <= 1 {
15461        return;
15462    }
15463
15464    let bucket_size = usize::try_from(bucket_size).expect("bucket_size must be non-negative");
15465    let bucket_stride = usize::try_from(bucket_stride).expect("bucket_stride must be non-negative");
15466    let num_buckets_usize = usize::try_from(num_buckets).expect("num_buckets must be non-negative");
15467    assert!(buckets.len() >= bucket_size + (num_buckets_usize - 1) * bucket_stride);
15468    let bucket00_start = (num_buckets_usize - 1) * bucket_stride;
15469
15470    while num_buckets >= 9 {
15471        let start = bucket00_start
15472            - usize::try_from(num_buckets - 9).expect("non-negative") * bucket_stride;
15473        accumulate_counts_at(buckets, start, bucket_size, bucket_stride, 9);
15474        num_buckets -= 8;
15475    }
15476
15477    match num_buckets {
15478        1 => {}
15479        2..=8 => accumulate_counts_at(
15480            buckets,
15481            bucket00_start,
15482            bucket_size,
15483            bucket_stride,
15484            usize::try_from(num_buckets).expect("non-negative"),
15485        ),
15486        _ => {}
15487    }
15488}
15489
15490fn block_slice<T>(slice: &[T], block_start: FastSint, block_size: FastSint) -> &[T] {
15491    let start = usize::try_from(block_start).expect("block_start must be non-negative");
15492    let len = usize::try_from(block_size).expect("block_size must be non-negative");
15493    &slice[start..start + len]
15494}
15495
15496#[allow(dead_code)]
15497struct SharedMutArray<'a> {
15498    ptr: *mut SaSint,
15499    len: usize,
15500    _marker: PhantomData<&'a mut [SaSint]>,
15501}
15502
15503#[allow(dead_code)]
15504impl<'a> SharedMutArray<'a> {
15505    fn new(slice: &'a mut [SaSint]) -> Self {
15506        Self {
15507            ptr: slice.as_mut_ptr(),
15508            len: slice.len(),
15509            _marker: PhantomData,
15510        }
15511    }
15512
15513    fn len(&self) -> usize {
15514        self.len
15515    }
15516
15517    fn slice_mut(&mut self, start: usize, len: usize) -> &mut [SaSint] {
15518        assert!(start <= self.len);
15519        assert!(len <= self.len - start);
15520        unsafe {
15521            // The recursive driver aliases multiple logical views into one SA backing store.
15522            // This helper centralizes that checked projection so the driver can be translated
15523            // without pretending those regions are independent Rust slices.
15524            std::slice::from_raw_parts_mut(self.ptr.add(start), len)
15525        }
15526    }
15527}
15528
15529fn accumulate_counts_at(
15530    buckets: &mut [SaSint],
15531    bucket00_start: usize,
15532    bucket_size: usize,
15533    bucket_stride: usize,
15534    count: usize,
15535) {
15536    assert!((2..=9).contains(&count));
15537    assert!(bucket00_start >= (count - 1) * bucket_stride);
15538
15539    let dst_end = bucket00_start + bucket_size;
15540    let mut sums = vec![0; bucket_size];
15541
15542    for i in 0..count {
15543        let start = bucket00_start - i * bucket_stride;
15544        let end = start + bucket_size;
15545        for (sum, value) in sums.iter_mut().zip(buckets[start..end].iter()) {
15546            *sum += *value;
15547        }
15548    }
15549
15550    buckets[bucket00_start..dst_end].copy_from_slice(&sums);
15551}
15552
15553/// Internal helper: thread state size.
15554#[doc(hidden)]
15555pub fn thread_state_size() -> usize {
15556    mem::size_of::<ThreadState>()
15557}
15558
15559#[cfg(all(test, feature = "upstream-c"))]
15560mod tests {
15561    use super::*;
15562
15563    unsafe extern "C" {
15564        fn probe_renumber_lms_suffixes_8u(
15565            sa: *mut SaSint,
15566            m: SaSint,
15567            name: SaSint,
15568            omp_block_start: FastSint,
15569            omp_block_size: FastSint,
15570        ) -> SaSint;
15571
15572        fn probe_gather_marked_lms_suffixes(
15573            sa: *mut SaSint,
15574            m: SaSint,
15575            l: FastSint,
15576            omp_block_start: FastSint,
15577            omp_block_size: FastSint,
15578        ) -> FastSint;
15579
15580        fn probe_renumber_distinct_lms_suffixes_32s_4k(
15581            sa: *mut SaSint,
15582            m: SaSint,
15583            name: SaSint,
15584            omp_block_start: FastSint,
15585            omp_block_size: FastSint,
15586        ) -> SaSint;
15587
15588        fn probe_renumber_unique_and_nonunique_lms_suffixes_32s(
15589            t: *mut SaSint,
15590            sa: *mut SaSint,
15591            m: SaSint,
15592            f: SaSint,
15593            omp_block_start: FastSint,
15594            omp_block_size: FastSint,
15595        ) -> SaSint;
15596
15597        fn probe_renumber_unique_and_nonunique_lms_suffixes_32s_omp(
15598            t: *mut SaSint,
15599            sa: *mut SaSint,
15600            m: SaSint,
15601            threads: SaSint,
15602        ) -> SaSint;
15603
15604        fn probe_renumber_and_gather_lms_suffixes_omp(
15605            sa: *mut SaSint,
15606            n: SaSint,
15607            m: SaSint,
15608            fs: SaSint,
15609            threads: SaSint,
15610        ) -> SaSint;
15611
15612        fn probe_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
15613            sa: *mut SaSint,
15614            n: SaSint,
15615            m: SaSint,
15616            threads: SaSint,
15617        ) -> SaSint;
15618
15619        fn probe_main_32s_entry(
15620            t: *mut SaSint,
15621            sa: *mut SaSint,
15622            n: SaSint,
15623            k: SaSint,
15624            fs: SaSint,
15625            threads: SaSint,
15626        ) -> SaSint;
15627
15628        fn probe_public_libsais_freq(
15629            t: *const u8,
15630            sa: *mut SaSint,
15631            n: SaSint,
15632            fs: SaSint,
15633            freq: *mut SaSint,
15634        ) -> SaSint;
15635
15636        fn probe_public_libsais_gsa_freq(
15637            t: *const u8,
15638            sa: *mut SaSint,
15639            n: SaSint,
15640            fs: SaSint,
15641            freq: *mut SaSint,
15642        ) -> SaSint;
15643
15644        fn probe_public_libsais_bwt_freq(
15645            t: *const u8,
15646            u: *mut u8,
15647            a: *mut SaSint,
15648            n: SaSint,
15649            fs: SaSint,
15650            freq: *mut SaSint,
15651        ) -> SaSint;
15652
15653        fn probe_public_libsais_bwt_aux_freq(
15654            t: *const u8,
15655            u: *mut u8,
15656            a: *mut SaSint,
15657            n: SaSint,
15658            fs: SaSint,
15659            freq: *mut SaSint,
15660            r: SaSint,
15661            i: *mut SaSint,
15662        ) -> SaSint;
15663
15664        fn probe_public_libsais_unbwt_freq(
15665            t: *const u8,
15666            u: *mut u8,
15667            a: *mut SaSint,
15668            n: SaSint,
15669            freq: *const SaSint,
15670            i: SaSint,
15671        ) -> SaSint;
15672
15673        fn probe_public_libsais_unbwt_aux_freq(
15674            t: *const u8,
15675            u: *mut u8,
15676            a: *mut SaSint,
15677            n: SaSint,
15678            freq: *const SaSint,
15679            r: SaSint,
15680            i: *const SaSint,
15681        ) -> SaSint;
15682    }
15683
15684    fn make_recursive_main_32s_text(repeats: usize) -> Vec<SaSint> {
15685        let motif = [9, 4, 9, 2, 9, 4, 9, 1];
15686        let mut t = Vec::with_capacity(repeats * motif.len() + 1);
15687        for _ in 0..repeats {
15688            t.extend_from_slice(&motif);
15689        }
15690        t.push(0);
15691        t
15692    }
15693
15694    fn make_large_main_32s_stress_text(len: usize, alphabet: SaSint) -> Vec<SaSint> {
15695        let mut state: u32 = 0x1357_9bdf;
15696        let mut t = Vec::with_capacity(len + 1);
15697
15698        for i in 0..len {
15699            state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15700            let mut value = ((state >> 16) % (alphabet as u32 - 1)) as SaSint + 1;
15701
15702            if i % 17 < 8 {
15703                value = ((i / 17) as SaSint % 11) + 1;
15704            }
15705            if i % 29 < 10 {
15706                value = (((i / 29) as SaSint * 3) % 19) + 1;
15707            }
15708            if i % 64 >= 48 {
15709                value = t[i - 48];
15710            }
15711
15712            t.push(value);
15713        }
15714
15715        t.push(0);
15716        t
15717    }
15718
15719    fn assert_main_32s_entry_matches_upstream_c(
15720        t: Vec<SaSint>,
15721        k: SaSint,
15722        fs: SaSint,
15723        compare_full_sa: bool,
15724    ) {
15725        let mut t = t;
15726        let n = t.len() as SaSint;
15727        let n_usize = t.len();
15728        let threads = 1;
15729        let extra = usize::try_from(fs).expect("fs must be non-negative");
15730        let mut sa = vec![0; t.len() + extra];
15731
15732        let initial_t = t.clone();
15733        let initial_sa = sa.clone();
15734
15735        let c_result =
15736            unsafe { probe_main_32s_entry(t.as_mut_ptr(), sa.as_mut_ptr(), n, k, fs, threads) };
15737        let c_t = t.clone();
15738        let c_sa = sa.clone();
15739
15740        t.copy_from_slice(&initial_t);
15741        sa.copy_from_slice(&initial_sa);
15742
15743        let mut thread_state = alloc_thread_state(threads).expect("thread state");
15744        let rust_result =
15745            libsais_main_32s_entry(&mut t, &mut sa, n, k, fs, threads, &mut thread_state);
15746
15747        assert_eq!(rust_result, c_result);
15748        assert_slice_eq_with_first_diff("T", &t, &c_t);
15749        if compare_full_sa {
15750            assert_slice_eq_with_first_diff("SA", &sa, &c_sa);
15751        } else {
15752            assert_slice_eq_with_first_diff("SA", &sa[..n_usize], &c_sa[..n_usize]);
15753        }
15754    }
15755
15756    fn assert_main_32s_entry_matches_upstream_c_for_branch(k: SaSint) {
15757        assert_main_32s_entry_matches_upstream_c(
15758            vec![17, 3, 17, 9, 5, 9, 2, 11, 2, 7, 1, 7, 0],
15759            k,
15760            0,
15761            true,
15762        );
15763    }
15764
15765    fn assert_slice_eq_with_first_diff(label: &str, left: &[SaSint], right: &[SaSint]) {
15766        assert_eq!(left.len(), right.len(), "{label} length mismatch");
15767        if let Some((idx, (l, r))) = left
15768            .iter()
15769            .zip(right.iter())
15770            .enumerate()
15771            .find(|(_, (l, r))| l != r)
15772        {
15773            panic!("{label} first diff at index {idx}: rust={l}, c={r}");
15774        }
15775    }
15776
15777    #[test]
15778    fn align_up_matches_power_of_two_alignment() {
15779        assert_eq!(align_up(0, 4096), 0);
15780        assert_eq!(align_up(1, 4096), 4096);
15781        assert_eq!(align_up(4095, 4096), 4096);
15782        assert_eq!(align_up(4096, 4096), 4096);
15783        assert_eq!(align_up(4097, 4096), 8192);
15784        assert_eq!(align_up(65, 64), 128);
15785    }
15786
15787    #[test]
15788    fn shared_mut_array_projects_mutable_spans_from_one_backing_buffer() {
15789        let mut backing = vec![1, 2, 3, 4, 5, 6];
15790        let len;
15791        {
15792            let mut shared = SharedMutArray::new(&mut backing);
15793            shared.slice_mut(1, 3).copy_from_slice(&[20, 30, 40]);
15794            shared.slice_mut(4, 2).copy_from_slice(&[50, 60]);
15795            len = shared.len();
15796        }
15797        assert_eq!(backing, vec![1, 20, 30, 40, 50, 60]);
15798        assert_eq!(len, 6);
15799    }
15800
15801    #[test]
15802    fn create_ctx_main_matches_single_thread_layout() {
15803        let ctx = create_ctx_main(1).expect("context");
15804        assert_eq!(ctx.buckets.len(), 8 * ALPHABET_SIZE);
15805        assert_eq!(ctx.threads, 1);
15806        assert!(ctx.thread_state.is_none());
15807    }
15808
15809    #[test]
15810    fn create_ctx_main_allocates_thread_state_for_multi_threaded_mode() {
15811        let ctx = create_ctx_main(3).expect("context");
15812        let states = ctx.thread_state.expect("thread state");
15813        assert_eq!(states.len(), 3);
15814        assert!(states
15815            .iter()
15816            .all(|state| state.buckets.len() == 4 * ALPHABET_SIZE));
15817        assert!(states
15818            .iter()
15819            .all(|state| state.cache.len() == LIBSAIS_PER_THREAD_CACHE_SIZE));
15820    }
15821
15822    #[test]
15823    fn create_ctx_wraps_single_thread_main_context() {
15824        let ctx = create_ctx().expect("context");
15825        assert_eq!(ctx.threads, 1);
15826        assert_eq!(ctx.buckets.len(), 8 * ALPHABET_SIZE);
15827        assert!(ctx.thread_state.is_none());
15828    }
15829
15830    #[test]
15831    fn free_ctx_accepts_context_value() {
15832        let ctx = create_ctx().expect("context");
15833        free_ctx(ctx);
15834    }
15835
15836    fn brute_force_suffix_array_u8(t: &[u8]) -> Vec<SaSint> {
15837        let mut sa: Vec<SaSint> = (0..t.len())
15838            .map(|index| SaSint::try_from(index).expect("index must fit SaSint"))
15839            .collect();
15840        sa.sort_by(|&lhs, &rhs| {
15841            t[usize::try_from(lhs).expect("non-negative")..]
15842                .cmp(&t[usize::try_from(rhs).expect("non-negative")..])
15843        });
15844        sa
15845    }
15846
15847    fn brute_force_plcp_u8(t: &[u8], sa: &[SaSint]) -> Vec<SaSint> {
15848        let mut rank = vec![0usize; t.len()];
15849        for (i, &suffix) in sa.iter().enumerate() {
15850            rank[usize::try_from(suffix).expect("suffix index must be non-negative")] = i;
15851        }
15852
15853        let mut plcp = vec![0; t.len()];
15854        for i in 0..t.len() {
15855            let r = rank[i];
15856            let prev = if r == 0 {
15857                t.len()
15858            } else {
15859                usize::try_from(sa[r - 1]).expect("suffix index must be non-negative")
15860            };
15861            if prev == t.len() {
15862                plcp[i] = 0;
15863                continue;
15864            }
15865
15866            let mut l = 0usize;
15867            while i + l < t.len() && prev + l < t.len() && t[i + l] == t[prev + l] {
15868                l += 1;
15869            }
15870            plcp[i] = l as SaSint;
15871        }
15872        plcp
15873    }
15874
15875    fn brute_force_lcp_from_sa_u8(t: &[u8], sa: &[SaSint]) -> Vec<SaSint> {
15876        let mut lcp = vec![0; sa.len()];
15877        for i in 0..sa.len() {
15878            let lhs = usize::try_from(sa[i]).expect("suffix index must be non-negative");
15879            let rhs = if i == 0 {
15880                sa.len()
15881            } else {
15882                usize::try_from(sa[i - 1]).expect("suffix index must be non-negative")
15883            };
15884            if rhs == sa.len() {
15885                lcp[i] = 0;
15886                continue;
15887            }
15888
15889            let mut l = 0usize;
15890            while lhs + l < t.len() && rhs + l < t.len() && t[lhs + l] == t[rhs + l] {
15891                l += 1;
15892            }
15893            lcp[i] = l as SaSint;
15894        }
15895        lcp
15896    }
15897
15898    #[test]
15899    fn libsais_matches_bruteforce_suffix_array_for_small_text() {
15900        let t = b"banana";
15901        let mut sa = vec![0; t.len()];
15902        let mut freq = vec![0; ALPHABET_SIZE];
15903
15904        let result = libsais(t, &mut sa, 0, Some(&mut freq));
15905
15906        assert_eq!(result, 0);
15907        assert_eq!(sa, brute_force_suffix_array_u8(t));
15908        assert_eq!(freq[b'a' as usize], 3);
15909        assert_eq!(freq[b'b' as usize], 1);
15910        assert_eq!(freq[b'n' as usize], 2);
15911    }
15912
15913    #[test]
15914    fn public_libsais_frequency_outputs_match_upstream_c() {
15915        let text = b"banana";
15916        let gsa_text = b"ban\0ana\0";
15917        let mut rust_sa = vec![0; text.len()];
15918        let mut c_sa = vec![0; text.len()];
15919        let mut rust_freq = vec![-1; ALPHABET_SIZE];
15920        let mut c_freq = vec![-1; ALPHABET_SIZE];
15921
15922        let rust_rc = libsais(text, &mut rust_sa, 0, Some(&mut rust_freq));
15923        let c_rc = unsafe {
15924            probe_public_libsais_freq(
15925                text.as_ptr(),
15926                c_sa.as_mut_ptr(),
15927                text.len() as SaSint,
15928                0,
15929                c_freq.as_mut_ptr(),
15930            )
15931        };
15932        assert_eq!(rust_rc, c_rc);
15933        assert_eq!(rust_sa, c_sa);
15934        assert_eq!(rust_freq, c_freq);
15935
15936        let mut rust_gsa = vec![0; gsa_text.len()];
15937        let mut c_gsa = vec![0; gsa_text.len()];
15938        rust_freq.fill(-1);
15939        c_freq.fill(-1);
15940        let rust_rc = libsais_gsa(gsa_text, &mut rust_gsa, 0, Some(&mut rust_freq));
15941        let c_rc = unsafe {
15942            probe_public_libsais_gsa_freq(
15943                gsa_text.as_ptr(),
15944                c_gsa.as_mut_ptr(),
15945                gsa_text.len() as SaSint,
15946                0,
15947                c_freq.as_mut_ptr(),
15948            )
15949        };
15950        assert_eq!(rust_rc, c_rc);
15951        assert_eq!(rust_gsa, c_gsa);
15952        assert_eq!(rust_freq, c_freq);
15953
15954        let mut rust_u = vec![0; text.len()];
15955        let mut rust_a = vec![0; text.len()];
15956        let mut c_u = vec![0; text.len()];
15957        let mut c_a = vec![0; text.len()];
15958        rust_freq.fill(-1);
15959        c_freq.fill(-1);
15960        let rust_rc = libsais_bwt(text, &mut rust_u, &mut rust_a, 0, Some(&mut rust_freq));
15961        let c_rc = unsafe {
15962            probe_public_libsais_bwt_freq(
15963                text.as_ptr(),
15964                c_u.as_mut_ptr(),
15965                c_a.as_mut_ptr(),
15966                text.len() as SaSint,
15967                0,
15968                c_freq.as_mut_ptr(),
15969            )
15970        };
15971        assert_eq!(rust_rc, c_rc);
15972        assert_eq!(rust_u, c_u);
15973        assert_eq!(rust_freq, c_freq);
15974
15975        let r = 4;
15976        let mut rust_i = vec![0; (text.len() - 1) / r as usize + 1];
15977        let mut c_i = vec![0; rust_i.len()];
15978        rust_freq.fill(-1);
15979        c_freq.fill(-1);
15980        let rust_rc = libsais_bwt_aux(
15981            text,
15982            &mut rust_u,
15983            &mut rust_a,
15984            0,
15985            Some(&mut rust_freq),
15986            r,
15987            &mut rust_i,
15988        );
15989        let c_rc = unsafe {
15990            probe_public_libsais_bwt_aux_freq(
15991                text.as_ptr(),
15992                c_u.as_mut_ptr(),
15993                c_a.as_mut_ptr(),
15994                text.len() as SaSint,
15995                0,
15996                c_freq.as_mut_ptr(),
15997                r,
15998                c_i.as_mut_ptr(),
15999            )
16000        };
16001        assert_eq!(rust_rc, c_rc);
16002        assert_eq!(rust_u, c_u);
16003        assert_eq!(rust_i, c_i);
16004        assert_eq!(rust_freq, c_freq);
16005    }
16006
16007    #[test]
16008    fn public_libsais_unbwt_with_frequency_matches_upstream_c() {
16009        let text = b"abracadabra";
16010        let mut freq = vec![0; ALPHABET_SIZE];
16011        let mut bwt = vec![0; text.len()];
16012        let mut work = vec![0; text.len()];
16013        let primary = libsais_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
16014        assert!(primary >= 0);
16015
16016        let mut rust_u = vec![0; text.len()];
16017        let mut rust_a = vec![0; text.len() + 1];
16018        let mut c_u = vec![0; text.len()];
16019        let mut c_a = vec![0; text.len() + 1];
16020        let rust_rc = libsais_unbwt(&bwt, &mut rust_u, &mut rust_a, Some(&freq), primary);
16021        let c_rc = unsafe {
16022            probe_public_libsais_unbwt_freq(
16023                bwt.as_ptr(),
16024                c_u.as_mut_ptr(),
16025                c_a.as_mut_ptr(),
16026                bwt.len() as SaSint,
16027                freq.as_ptr(),
16028                primary,
16029            )
16030        };
16031        assert_eq!(rust_rc, c_rc);
16032        assert_eq!(rust_u, c_u);
16033        assert_eq!(rust_u, text);
16034
16035        let r = 4;
16036        let mut aux = vec![0; (text.len() - 1) / r as usize + 1];
16037        let bwt_rc = libsais_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), r, &mut aux);
16038        assert_eq!(bwt_rc, 0);
16039
16040        rust_u.fill(0);
16041        rust_a.fill(0);
16042        c_u.fill(0);
16043        c_a.fill(0);
16044        let rust_rc = libsais_unbwt_aux(&bwt, &mut rust_u, &mut rust_a, Some(&freq), r, &aux);
16045        let c_rc = unsafe {
16046            probe_public_libsais_unbwt_aux_freq(
16047                bwt.as_ptr(),
16048                c_u.as_mut_ptr(),
16049                c_a.as_mut_ptr(),
16050                bwt.len() as SaSint,
16051                freq.as_ptr(),
16052                r,
16053                aux.as_ptr(),
16054            )
16055        };
16056        assert_eq!(rust_rc, c_rc);
16057        assert_eq!(rust_u, c_u);
16058        assert_eq!(rust_u, text);
16059    }
16060
16061    #[test]
16062    fn libsais_omp_frequency_wrappers_match_direct_calls() {
16063        let text = b"banana";
16064        let gsa_text = b"ban\0ana\0";
16065
16066        let mut direct_sa = vec![0; text.len()];
16067        let mut omp_sa = vec![0; text.len()];
16068        let mut direct_freq = vec![-1; ALPHABET_SIZE];
16069        let mut omp_freq = vec![-1; ALPHABET_SIZE];
16070        assert_eq!(libsais(text, &mut direct_sa, 0, Some(&mut direct_freq)), 0);
16071        assert_eq!(libsais_omp(text, &mut omp_sa, 0, Some(&mut omp_freq), 2), 0);
16072        assert_eq!(omp_sa, direct_sa);
16073        assert_eq!(omp_freq, direct_freq);
16074
16075        let mut direct_gsa = vec![0; gsa_text.len()];
16076        let mut omp_gsa = vec![0; gsa_text.len()];
16077        direct_freq.fill(-1);
16078        omp_freq.fill(-1);
16079        assert_eq!(
16080            libsais_gsa(gsa_text, &mut direct_gsa, 0, Some(&mut direct_freq)),
16081            0
16082        );
16083        assert_eq!(
16084            libsais_gsa_omp(gsa_text, &mut omp_gsa, 0, Some(&mut omp_freq), 2),
16085            0
16086        );
16087        assert_eq!(omp_gsa, direct_gsa);
16088        assert_eq!(omp_freq, direct_freq);
16089
16090        let mut direct_bwt = vec![0; text.len()];
16091        let mut direct_work = vec![0; text.len()];
16092        let mut omp_bwt = vec![0; text.len()];
16093        let mut omp_work = vec![0; text.len()];
16094        direct_freq.fill(-1);
16095        omp_freq.fill(-1);
16096        assert_eq!(
16097            libsais_bwt(
16098                text,
16099                &mut direct_bwt,
16100                &mut direct_work,
16101                0,
16102                Some(&mut direct_freq)
16103            ),
16104            libsais_bwt_omp(text, &mut omp_bwt, &mut omp_work, 0, Some(&mut omp_freq), 2)
16105        );
16106        assert_eq!(omp_bwt, direct_bwt);
16107        assert_eq!(omp_freq, direct_freq);
16108
16109        let mut direct_aux = vec![0; 2];
16110        let mut omp_aux = vec![0; 2];
16111        direct_freq.fill(-1);
16112        omp_freq.fill(-1);
16113        assert_eq!(
16114            libsais_bwt_aux(
16115                text,
16116                &mut direct_bwt,
16117                &mut direct_work,
16118                0,
16119                Some(&mut direct_freq),
16120                4,
16121                &mut direct_aux
16122            ),
16123            libsais_bwt_aux_omp(
16124                text,
16125                &mut omp_bwt,
16126                &mut omp_work,
16127                0,
16128                Some(&mut omp_freq),
16129                4,
16130                &mut omp_aux,
16131                2
16132            )
16133        );
16134        assert_eq!(omp_bwt, direct_bwt);
16135        assert_eq!(omp_aux, direct_aux);
16136        assert_eq!(omp_freq, direct_freq);
16137    }
16138
16139    #[test]
16140    #[ignore = "large real-data regression; requires local minibwa yeast fixture"]
16141    fn public_libsais_omp_handles_minibwa_yeast_two_strand_index_input() {
16142        let l2b_path =
16143            "/data/henriksson/github/claude/minibwa/.tmp/compare-yeast-now/ref.split.rust.l2b";
16144        let fasta_path =
16145            "/data/henriksson/github/claude/minibwa/.tmp/large-real/yeast/ref.sanitized.fa";
16146        let forward = if let Ok(bytes) = std::fs::read(l2b_path) {
16147            assert!(bytes.len() >= 64, "short l2b fixture: {l2b_path}");
16148            assert_eq!(&bytes[..4], b"L2B\x01", "bad l2b magic in {l2b_path}");
16149            let n_ctg = u64::from_le_bytes(bytes[8..16].try_into().unwrap()) as usize;
16150            let tot_len = u64::from_le_bytes(bytes[16..24].try_into().unwrap()) as usize;
16151            let n_ambi = u64::from_le_bytes(bytes[24..32].try_into().unwrap()) as usize;
16152            let n_mask = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
16153            let n_pac = u64::from_le_bytes(bytes[56..64].try_into().unwrap()) as usize;
16154            let pac_start = 64 + 8 * n_ctg + 16 * n_ambi + 16 * n_mask;
16155            assert!(
16156                bytes.len() >= pac_start + 8 * n_pac,
16157                "truncated l2b pac in {l2b_path}"
16158            );
16159            let mut pac = Vec::with_capacity(n_pac);
16160            for chunk in bytes[pac_start..pac_start + 8 * n_pac].chunks_exact(8) {
16161                pac.push(u64::from_le_bytes(chunk.try_into().unwrap()));
16162            }
16163            (0..tot_len)
16164                .map(|i| ((pac[i >> 5] >> ((i & 31) << 1)) & 3) as u8)
16165                .collect::<Vec<_>>()
16166        } else if let Ok(fasta) = std::fs::read_to_string(fasta_path) {
16167            let mut rng = 11u64;
16168            let mut forward = Vec::new();
16169            for line in fasta.lines() {
16170                if line.starts_with('>') {
16171                    continue;
16172                }
16173                forward.extend(line.bytes().map(|b| {
16174                    let mut c = match b {
16175                        b'A' | b'a' => 0,
16176                        b'C' | b'c' => 1,
16177                        b'G' | b'g' => 2,
16178                        b'T' | b't' | b'U' | b'u' => 3,
16179                        _ => {
16180                            rng = rng.wrapping_add(0x9e3779b97f4a7c15);
16181                            let mut z = rng;
16182                            z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
16183                            z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
16184                            4 | ((z ^ (z >> 31)) & 3) as u8
16185                        }
16186                    };
16187                    if b < b'A' || b > b'Z' {
16188                        c |= 1 << 3;
16189                    }
16190                    c & 3
16191                }));
16192            }
16193            forward
16194        } else {
16195            eprintln!("skipping missing fixtures: {l2b_path} and {fasta_path}");
16196            return;
16197        };
16198        assert!(
16199            forward.len() > 12_000_000,
16200            "fixture should exercise the minibwa yeast index workload"
16201        );
16202
16203        let mut text = Vec::with_capacity(forward.len() * 2);
16204        text.extend_from_slice(&forward);
16205        text.extend(forward.iter().rev().map(|&c| 3 - c));
16206
16207        const FS: SaSint = 10_000;
16208        let mut sa = vec![0; text.len() + FS as usize + 1];
16209        assert_eq!(libsais_omp(&text, &mut sa[1..], FS, None, 4), 0);
16210        if let Some((i, &value)) = sa[1..1 + text.len()]
16211            .iter()
16212            .enumerate()
16213            .find(|&(_, &value)| value < 0 || value as usize >= text.len())
16214        {
16215            panic!("invalid suffix-array entry at {i}: {value}");
16216        }
16217    }
16218
16219    #[test]
16220    #[ignore = "large real-data regression; requires local minibwa yeast fixture"]
16221    fn public_libsais_omp_matches_plain_on_minibwa_yeast_two_strand_index_input() {
16222        let l2b_path =
16223            "/data/henriksson/github/claude/minibwa/.tmp/compare-yeast-now/ref.split.rust.l2b";
16224        let fasta_path =
16225            "/data/henriksson/github/claude/minibwa/.tmp/large-real/yeast/ref.sanitized.fa";
16226        let forward = if let Ok(bytes) = std::fs::read(l2b_path) {
16227            assert!(bytes.len() >= 64, "short l2b fixture: {l2b_path}");
16228            assert_eq!(&bytes[..4], b"L2B\x01", "bad l2b magic in {l2b_path}");
16229            let n_ctg = u64::from_le_bytes(bytes[8..16].try_into().unwrap()) as usize;
16230            let tot_len = u64::from_le_bytes(bytes[16..24].try_into().unwrap()) as usize;
16231            let n_ambi = u64::from_le_bytes(bytes[24..32].try_into().unwrap()) as usize;
16232            let n_mask = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
16233            let n_pac = u64::from_le_bytes(bytes[56..64].try_into().unwrap()) as usize;
16234            let pac_start = 64 + 8 * n_ctg + 16 * n_ambi + 16 * n_mask;
16235            assert!(
16236                bytes.len() >= pac_start + 8 * n_pac,
16237                "truncated l2b pac in {l2b_path}"
16238            );
16239            let mut pac = Vec::with_capacity(n_pac);
16240            for chunk in bytes[pac_start..pac_start + 8 * n_pac].chunks_exact(8) {
16241                pac.push(u64::from_le_bytes(chunk.try_into().unwrap()));
16242            }
16243            (0..tot_len)
16244                .map(|i| ((pac[i >> 5] >> ((i & 31) << 1)) & 3) as u8)
16245                .collect::<Vec<_>>()
16246        } else if let Ok(fasta) = std::fs::read_to_string(fasta_path) {
16247            let mut rng = 11u64;
16248            let mut forward = Vec::new();
16249            for line in fasta.lines() {
16250                if line.starts_with('>') {
16251                    continue;
16252                }
16253                forward.extend(line.bytes().map(|b| {
16254                    let mut c = match b {
16255                        b'A' | b'a' => 0,
16256                        b'C' | b'c' => 1,
16257                        b'G' | b'g' => 2,
16258                        b'T' | b't' | b'U' | b'u' => 3,
16259                        _ => {
16260                            rng = rng.wrapping_add(0x9e3779b97f4a7c15);
16261                            let mut z = rng;
16262                            z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
16263                            z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
16264                            4 | ((z ^ (z >> 31)) & 3) as u8
16265                        }
16266                    };
16267                    if b < b'A' || b > b'Z' {
16268                        c |= 1 << 3;
16269                    }
16270                    c & 3
16271                }));
16272            }
16273            forward
16274        } else {
16275            eprintln!("skipping missing fixtures: {l2b_path} and {fasta_path}");
16276            return;
16277        };
16278        assert!(
16279            forward.len() > 12_000_000,
16280            "fixture should exercise the minibwa yeast index workload"
16281        );
16282
16283        let mut text = Vec::with_capacity(forward.len() * 2);
16284        text.extend_from_slice(&forward);
16285        text.extend(forward.iter().rev().map(|&c| 3 - c));
16286
16287        const FS: SaSint = 10_000;
16288        let mut plain_sa = vec![0; text.len() + FS as usize + 1];
16289        let mut omp_sa = vec![0; text.len() + FS as usize + 1];
16290        assert_eq!(libsais(&text, &mut plain_sa[1..], FS, None), 0);
16291        assert_eq!(libsais_omp(&text, &mut omp_sa[1..], FS, None, 4), 0);
16292        plain_sa[0] = text.len() as SaSint;
16293        omp_sa[0] = text.len() as SaSint;
16294        if let Some(i) = plain_sa[..=text.len()]
16295            .iter()
16296            .zip(&omp_sa[..=text.len()])
16297            .position(|(plain, omp)| plain != omp)
16298        {
16299            panic!(
16300                "first suffix-array diff at {i}: plain={} omp={}",
16301                plain_sa[i], omp_sa[i]
16302            );
16303        }
16304    }
16305
16306    #[test]
16307    fn libsais_unbwt_omp_frequency_wrappers_match_direct_calls() {
16308        let text = b"abracadabra";
16309        let mut freq = vec![0; ALPHABET_SIZE];
16310        let mut bwt = vec![0; text.len()];
16311        let mut work = vec![0; text.len()];
16312        let primary = libsais_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
16313        assert!(primary >= 0);
16314
16315        let mut direct = vec![0; text.len()];
16316        let mut direct_work = vec![0; text.len() + 1];
16317        let mut omp = vec![0; text.len()];
16318        let mut omp_work = vec![0; text.len() + 1];
16319        assert_eq!(
16320            libsais_unbwt(&bwt, &mut direct, &mut direct_work, Some(&freq), primary),
16321            libsais_unbwt_omp(&bwt, &mut omp, &mut omp_work, Some(&freq), primary, 2)
16322        );
16323        assert_eq!(omp, direct);
16324        assert_eq!(omp, text);
16325
16326        let mut aux = vec![0; (text.len() - 1) / 4 + 1];
16327        assert_eq!(
16328            libsais_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), 4, &mut aux),
16329            0
16330        );
16331        direct.fill(0);
16332        direct_work.fill(0);
16333        omp.fill(0);
16334        omp_work.fill(0);
16335        assert_eq!(
16336            libsais_unbwt_aux(&bwt, &mut direct, &mut direct_work, Some(&freq), 4, &aux),
16337            libsais_unbwt_aux_omp(&bwt, &mut omp, &mut omp_work, Some(&freq), 4, &aux, 2)
16338        );
16339        assert_eq!(omp, direct);
16340        assert_eq!(omp, text);
16341    }
16342
16343    #[test]
16344    fn libsais_ctx_matches_plain_entry_point_for_small_text() {
16345        let t = b"mississippi";
16346        let mut sa_plain = vec![0; t.len()];
16347        let mut sa_ctx = vec![0; t.len()];
16348        let plain = libsais(t, &mut sa_plain, 0, None);
16349
16350        let mut ctx = create_ctx().expect("context");
16351        let with_ctx = libsais_ctx(&mut ctx, t, &mut sa_ctx, 0, None);
16352
16353        assert_eq!(plain, 0);
16354        assert_eq!(with_ctx, 0);
16355        assert_eq!(sa_ctx, sa_plain);
16356    }
16357
16358    #[test]
16359    fn libsais_ctx_frequency_wrappers_match_direct_calls() {
16360        let text = b"banana";
16361        let gsa_text = b"ban\0ana\0";
16362        let mut ctx = create_ctx().expect("context");
16363
16364        let mut direct_sa = vec![0; text.len()];
16365        let mut ctx_sa = vec![0; text.len()];
16366        let mut direct_freq = vec![-1; ALPHABET_SIZE];
16367        let mut ctx_freq = vec![-1; ALPHABET_SIZE];
16368        assert_eq!(libsais(text, &mut direct_sa, 0, Some(&mut direct_freq)), 0);
16369        assert_eq!(
16370            libsais_ctx(&mut ctx, text, &mut ctx_sa, 0, Some(&mut ctx_freq)),
16371            0
16372        );
16373        assert_eq!(ctx_sa, direct_sa);
16374        assert_eq!(ctx_freq, direct_freq);
16375
16376        let mut direct_gsa = vec![0; gsa_text.len()];
16377        let mut ctx_gsa = vec![0; gsa_text.len()];
16378        direct_freq.fill(-1);
16379        ctx_freq.fill(-1);
16380        assert_eq!(
16381            libsais_gsa(gsa_text, &mut direct_gsa, 0, Some(&mut direct_freq)),
16382            0
16383        );
16384        assert_eq!(
16385            libsais_gsa_ctx(&mut ctx, gsa_text, &mut ctx_gsa, 0, Some(&mut ctx_freq)),
16386            0
16387        );
16388        assert_eq!(ctx_gsa, direct_gsa);
16389        assert_eq!(ctx_freq, direct_freq);
16390
16391        let mut direct_bwt = vec![0; text.len()];
16392        let mut direct_work = vec![0; text.len()];
16393        let mut ctx_bwt = vec![0; text.len()];
16394        let mut ctx_work = vec![0; text.len()];
16395        direct_freq.fill(-1);
16396        ctx_freq.fill(-1);
16397        assert_eq!(
16398            libsais_bwt(
16399                text,
16400                &mut direct_bwt,
16401                &mut direct_work,
16402                0,
16403                Some(&mut direct_freq)
16404            ),
16405            libsais_bwt_ctx(
16406                &mut ctx,
16407                text,
16408                &mut ctx_bwt,
16409                &mut ctx_work,
16410                0,
16411                Some(&mut ctx_freq)
16412            )
16413        );
16414        assert_eq!(ctx_bwt, direct_bwt);
16415        assert_eq!(ctx_freq, direct_freq);
16416
16417        let mut direct_aux = vec![0; 2];
16418        let mut ctx_aux = vec![0; 2];
16419        direct_freq.fill(-1);
16420        ctx_freq.fill(-1);
16421        assert_eq!(
16422            libsais_bwt_aux(
16423                text,
16424                &mut direct_bwt,
16425                &mut direct_work,
16426                0,
16427                Some(&mut direct_freq),
16428                4,
16429                &mut direct_aux
16430            ),
16431            libsais_bwt_aux_ctx(
16432                &mut ctx,
16433                text,
16434                &mut ctx_bwt,
16435                &mut ctx_work,
16436                0,
16437                Some(&mut ctx_freq),
16438                4,
16439                &mut ctx_aux
16440            )
16441        );
16442        assert_eq!(ctx_bwt, direct_bwt);
16443        assert_eq!(ctx_aux, direct_aux);
16444        assert_eq!(ctx_freq, direct_freq);
16445    }
16446
16447    #[test]
16448    fn libsais_unbwt_ctx_frequency_wrappers_match_direct_calls() {
16449        let text = b"abracadabra";
16450        let mut freq = vec![0; ALPHABET_SIZE];
16451        let mut bwt = vec![0; text.len()];
16452        let mut work = vec![0; text.len()];
16453        let primary = libsais_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
16454        assert!(primary >= 0);
16455
16456        let mut ctx = unbwt_create_ctx().expect("unbwt context");
16457        let mut direct = vec![0; text.len()];
16458        let mut direct_work = vec![0; text.len() + 1];
16459        let mut via_ctx = vec![0; text.len()];
16460        let mut ctx_work = vec![0; text.len() + 1];
16461        assert_eq!(
16462            libsais_unbwt(&bwt, &mut direct, &mut direct_work, Some(&freq), primary),
16463            libsais_unbwt_ctx(
16464                &mut ctx,
16465                &bwt,
16466                &mut via_ctx,
16467                &mut ctx_work,
16468                Some(&freq),
16469                primary
16470            )
16471        );
16472        assert_eq!(via_ctx, direct);
16473        assert_eq!(via_ctx, text);
16474
16475        let mut aux = vec![0; (text.len() - 1) / 4 + 1];
16476        assert_eq!(
16477            libsais_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), 4, &mut aux),
16478            0
16479        );
16480        direct.fill(0);
16481        direct_work.fill(0);
16482        via_ctx.fill(0);
16483        ctx_work.fill(0);
16484        assert_eq!(
16485            libsais_unbwt_aux(&bwt, &mut direct, &mut direct_work, Some(&freq), 4, &aux),
16486            libsais_unbwt_aux_ctx(
16487                &mut ctx,
16488                &bwt,
16489                &mut via_ctx,
16490                &mut ctx_work,
16491                Some(&freq),
16492                4,
16493                &aux
16494            )
16495        );
16496        assert_eq!(via_ctx, direct);
16497        assert_eq!(via_ctx, text);
16498    }
16499
16500    #[test]
16501    fn libsais_int_matches_bruteforce_suffix_array_for_small_integer_text() {
16502        let mut t = vec![2, 1, 3, 1, 0];
16503        let expected = {
16504            let mut sa: Vec<SaSint> = (0..t.len())
16505                .map(|index| SaSint::try_from(index).expect("index must fit SaSint"))
16506                .collect();
16507            sa.sort_by(|&lhs, &rhs| {
16508                t[usize::try_from(lhs).expect("non-negative")..]
16509                    .cmp(&t[usize::try_from(rhs).expect("non-negative")..])
16510            });
16511            sa
16512        };
16513        let mut sa = vec![0; t.len()];
16514
16515        let result = libsais_int(&mut t, &mut sa, 4, 0);
16516
16517        assert_eq!(result, 0);
16518        assert_eq!(sa, expected);
16519    }
16520
16521    #[test]
16522    fn libsais_plcp_matches_bruteforce_for_small_text() {
16523        let t = b"banana";
16524        let sa = brute_force_suffix_array_u8(t);
16525        let expected = brute_force_plcp_u8(t, &sa);
16526        let mut plcp = vec![0; t.len()];
16527
16528        let result = libsais_plcp(t, &sa, &mut plcp);
16529
16530        assert_eq!(result, 0);
16531        assert_eq!(plcp, expected);
16532    }
16533
16534    #[test]
16535    fn libsais_plcp_gsa_stops_at_separator() {
16536        let t = b"ab\0b\0";
16537        let sa = brute_force_suffix_array_u8(t);
16538        let mut plcp = vec![0; t.len()];
16539
16540        let result = libsais_plcp_gsa(t, &sa, &mut plcp);
16541
16542        assert_eq!(result, 0);
16543        assert_eq!(plcp[2], 0);
16544        assert_eq!(plcp[4], 0);
16545    }
16546
16547    #[test]
16548    fn libsais_lcp_matches_bruteforce_for_small_text() {
16549        let t = b"banana";
16550        let sa = brute_force_suffix_array_u8(t);
16551        let plcp = brute_force_plcp_u8(t, &sa);
16552        let expected = brute_force_lcp_from_sa_u8(t, &sa);
16553        let mut lcp = vec![0; t.len()];
16554
16555        let result = libsais_lcp(&plcp, &sa, &mut lcp);
16556
16557        assert_eq!(result, 0);
16558        assert_eq!(lcp, expected);
16559    }
16560
16561    #[test]
16562    fn libsais_ctx_rejects_invalid_public_arguments() {
16563        let text = b"banana";
16564        let mut ctx = create_ctx().expect("context");
16565        let mut short_sa = vec![0; text.len() - 1];
16566        let mut full_sa = vec![0; text.len()];
16567        let mut short_freq = vec![0; ALPHABET_SIZE - 1];
16568        let mut short_u = vec![0; text.len() - 1];
16569        let mut full_u = vec![0; text.len()];
16570        let mut short_a = vec![0; text.len() - 1];
16571        let mut full_a = vec![0; text.len()];
16572        let mut aux = vec![0; 2];
16573
16574        assert_eq!(libsais_ctx(&mut ctx, text, &mut short_sa, 0, None), -1);
16575        assert_eq!(
16576            libsais_ctx(&mut ctx, text, &mut full_sa, 0, Some(&mut short_freq)),
16577            -1
16578        );
16579        assert_eq!(
16580            libsais_gsa_ctx(&mut ctx, b"banana", &mut full_sa, 0, None),
16581            -1
16582        );
16583        assert_eq!(
16584            libsais_gsa_ctx(&mut ctx, b"banana\0", &mut short_sa, 0, None),
16585            -1
16586        );
16587        assert_eq!(
16588            libsais_bwt_ctx(&mut ctx, text, &mut short_u, &mut full_a, 0, None),
16589            -1
16590        );
16591        assert_eq!(
16592            libsais_bwt_ctx(&mut ctx, text, &mut full_u, &mut short_a, 0, None),
16593            -1
16594        );
16595        assert_eq!(
16596            libsais_bwt_ctx(
16597                &mut ctx,
16598                text,
16599                &mut full_u,
16600                &mut full_a,
16601                0,
16602                Some(&mut short_freq)
16603            ),
16604            -1
16605        );
16606        assert_eq!(
16607            libsais_bwt_aux_ctx(
16608                &mut ctx,
16609                text,
16610                &mut full_u,
16611                &mut full_a,
16612                0,
16613                None,
16614                0,
16615                &mut aux
16616            ),
16617            -1
16618        );
16619        assert_eq!(
16620            libsais_bwt_aux_ctx(
16621                &mut ctx,
16622                text,
16623                &mut full_u,
16624                &mut full_a,
16625                0,
16626                None,
16627                3,
16628                &mut aux
16629            ),
16630            -1
16631        );
16632        assert_eq!(
16633            libsais_bwt_aux_ctx(
16634                &mut ctx,
16635                text,
16636                &mut full_u,
16637                &mut full_a,
16638                0,
16639                None,
16640                4,
16641                &mut []
16642            ),
16643            -1
16644        );
16645
16646        let mut missing_thread_state_ctx = Context {
16647            buckets: vec![0; 8 * ALPHABET_SIZE],
16648            thread_state: None,
16649            threads: 2,
16650        };
16651        assert_eq!(
16652            libsais_ctx(&mut missing_thread_state_ctx, text, &mut full_sa, 0, None),
16653            -2
16654        );
16655
16656        let mut zero_thread_ctx = Context {
16657            buckets: vec![0; 8 * ALPHABET_SIZE],
16658            thread_state: None,
16659            threads: 0,
16660        };
16661        assert_eq!(
16662            libsais_ctx(&mut zero_thread_ctx, text, &mut full_sa, 0, None),
16663            -2
16664        );
16665
16666        let mut short_thread_state_ctx = create_ctx_main(2).expect("context");
16667        short_thread_state_ctx
16668            .thread_state
16669            .as_mut()
16670            .expect("thread state")
16671            .truncate(1);
16672        assert_eq!(
16673            libsais_ctx(&mut short_thread_state_ctx, text, &mut full_sa, 0, None),
16674            -2
16675        );
16676    }
16677
16678    #[test]
16679    fn libsais_unbwt_ctx_rejects_invalid_public_arguments() {
16680        let text = b"banana";
16681        let mut bwt = vec![0; text.len()];
16682        let mut work = vec![0; text.len()];
16683        let primary = libsais_bwt(text, &mut bwt, &mut work, 0, None);
16684        let mut ctx = unbwt_create_ctx().expect("context");
16685
16686        let mut short_u = vec![0; text.len() - 1];
16687        let mut full_u = vec![0; text.len()];
16688        let mut short_a = vec![0; text.len() - 1];
16689        let mut full_a = vec![0; text.len()];
16690        let short_freq = vec![0; ALPHABET_SIZE - 1];
16691        let good_aux = vec![primary, 4];
16692
16693        assert_eq!(
16694            libsais_unbwt_ctx(&mut ctx, &bwt, &mut short_u, &mut full_a, None, primary),
16695            -1
16696        );
16697        assert_eq!(
16698            libsais_unbwt_ctx(&mut ctx, &bwt, &mut full_u, &mut short_a, None, primary),
16699            -1
16700        );
16701        assert_eq!(
16702            libsais_unbwt_ctx(
16703                &mut ctx,
16704                &bwt,
16705                &mut full_u,
16706                &mut full_a,
16707                Some(&short_freq),
16708                primary
16709            ),
16710            -1
16711        );
16712        assert_eq!(
16713            libsais_unbwt_ctx(&mut ctx, &bwt, &mut full_u, &mut full_a, None, 0),
16714            -1
16715        );
16716        assert_eq!(
16717            libsais_unbwt_aux_ctx(&mut ctx, &bwt, &mut full_u, &mut full_a, None, 3, &good_aux),
16718            -1
16719        );
16720        assert_eq!(
16721            libsais_unbwt_aux_ctx(
16722                &mut ctx,
16723                &bwt,
16724                &mut full_u,
16725                &mut full_a,
16726                None,
16727                4,
16728                &[primary]
16729            ),
16730            -1
16731        );
16732
16733        let mut malformed_ctx = UnbwtContext {
16734            bucket2: Vec::new(),
16735            fastbits: Vec::new(),
16736            buckets: None,
16737            threads: 1,
16738        };
16739        assert_eq!(
16740            libsais_unbwt_ctx(
16741                &mut malformed_ctx,
16742                &bwt,
16743                &mut full_u,
16744                &mut full_a,
16745                None,
16746                primary
16747            ),
16748            -2
16749        );
16750
16751        let mut missing_parallel_buckets_ctx = UnbwtContext {
16752            bucket2: vec![0; ALPHABET_SIZE * ALPHABET_SIZE],
16753            fastbits: vec![0; 1 + (1 << UNBWT_FASTBITS)],
16754            buckets: None,
16755            threads: 2,
16756        };
16757        assert_eq!(
16758            libsais_unbwt_ctx(
16759                &mut missing_parallel_buckets_ctx,
16760                &bwt,
16761                &mut full_u,
16762                &mut full_a,
16763                None,
16764                primary
16765            ),
16766            -2
16767        );
16768    }
16769
16770    #[test]
16771    fn unbwt_create_ctx_main_allocates_expected_buffers() {
16772        let ctx = unbwt_create_ctx_main(3).expect("context");
16773        assert_eq!(ctx.bucket2.len(), ALPHABET_SIZE * ALPHABET_SIZE);
16774        assert_eq!(ctx.fastbits.len(), 1 + (1 << UNBWT_FASTBITS));
16775        assert_eq!(
16776            ctx.buckets.as_ref().expect("parallel buckets").len(),
16777            3 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)
16778        );
16779        assert_eq!(ctx.threads, 3);
16780    }
16781
16782    #[test]
16783    fn unbwt_compute_histogram_counts_bytes() {
16784        let t = b"banana";
16785        let mut count = vec![0u32; ALPHABET_SIZE];
16786        unbwt_compute_histogram(t, t.len() as FastSint, &mut count);
16787        assert_eq!(count[b'a' as usize], 3);
16788        assert_eq!(count[b'b' as usize], 1);
16789        assert_eq!(count[b'n' as usize], 2);
16790    }
16791
16792    #[test]
16793    fn unbwt_transpose_bucket2_swaps_matrix_entries() {
16794        let mut bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16795        bucket2[(2 << 8) + 1] = 7;
16796        bucket2[(1 << 8) + 2] = 9;
16797        unbwt_transpose_bucket2(&mut bucket2);
16798        assert_eq!(bucket2[(1 << 8) + 2], 7);
16799        assert_eq!(bucket2[(2 << 8) + 1], 9);
16800    }
16801
16802    #[test]
16803    fn unbwt_init_single_builds_monotone_fastbits_and_writes_psi() {
16804        let t = b"annb\x00aa";
16805        let mut p = vec![0u32; t.len() + 1];
16806        let mut bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16807        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16808        let i = vec![4u32];
16809
16810        unbwt_init_single(
16811            t,
16812            &mut p,
16813            t.len() as SaSint,
16814            None,
16815            &i,
16816            &mut bucket2,
16817            &mut fastbits,
16818        );
16819
16820        assert!(fastbits
16821            .iter()
16822            .all(|&value| usize::from(value) < ALPHABET_SIZE * ALPHABET_SIZE));
16823        assert!(fastbits.iter().any(|&value| value != 0));
16824        assert!(p.iter().any(|&value| value != 0));
16825    }
16826
16827    #[test]
16828    fn unbwt_init_parallel_currently_matches_single_initializer() {
16829        let t = b"annb\x00aa";
16830        let mut p_single = vec![0u32; t.len() + 1];
16831        let mut p_parallel = vec![0u32; t.len() + 1];
16832        let mut bucket2_single = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16833        let mut bucket2_parallel = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16834        let mut fastbits_single = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16835        let mut fastbits_parallel = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16836        let i = vec![4u32];
16837        let mut scratch = vec![0u32; 2 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)];
16838
16839        unbwt_init_single(
16840            t,
16841            &mut p_single,
16842            t.len() as SaSint,
16843            None,
16844            &i,
16845            &mut bucket2_single,
16846            &mut fastbits_single,
16847        );
16848        unbwt_init_parallel(
16849            t,
16850            &mut p_parallel,
16851            t.len() as SaSint,
16852            None,
16853            &i,
16854            &mut bucket2_parallel,
16855            &mut fastbits_parallel,
16856            Some(&mut scratch),
16857            2,
16858        );
16859
16860        assert_eq!(p_parallel, p_single);
16861        assert_eq!(bucket2_parallel, bucket2_single);
16862        assert_eq!(fastbits_parallel, fastbits_single);
16863    }
16864
16865    #[test]
16866    fn unbwt_init_parallel_uses_block_partition_for_large_inputs() {
16867        let n = 70_003usize;
16868        let t: Vec<u8> = (0..n)
16869            .map(|i| i.wrapping_mul(37).wrapping_add(i >> 3) as u8)
16870            .collect();
16871        let i = [12_345u32];
16872
16873        let mut single_p = vec![0u32; n + 1];
16874        let mut threaded_p = vec![0u32; n + 1];
16875        let mut single_bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16876        let mut threaded_bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16877        let mut single_fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16878        let mut threaded_fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16879        let mut buckets = vec![0u32; 4 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)];
16880
16881        unbwt_init_single(
16882            &t,
16883            &mut single_p,
16884            n as SaSint,
16885            None,
16886            &i,
16887            &mut single_bucket2,
16888            &mut single_fastbits,
16889        );
16890        unbwt_init_parallel(
16891            &t,
16892            &mut threaded_p,
16893            n as SaSint,
16894            None,
16895            &i,
16896            &mut threaded_bucket2,
16897            &mut threaded_fastbits,
16898            Some(&mut buckets),
16899            4,
16900        );
16901
16902        assert_eq!(threaded_p, single_p);
16903        assert_eq!(threaded_bucket2, single_bucket2);
16904        assert_eq!(threaded_fastbits, single_fastbits);
16905    }
16906
16907    #[test]
16908    fn unbwt_decode_1_writes_big_endian_symbol_words() {
16909        let mut u = vec![0u8; 4];
16910        let p = vec![1u32, 0u32];
16911        let mut bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16912        bucket2[0x1234] = 0;
16913        bucket2[0x1235] = 2;
16914        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16915        fastbits[0] = 0x1234;
16916        let mut i0 = 0usize;
16917
16918        unbwt_decode_1(&mut u, &p, &bucket2, &fastbits, 0, &mut i0, 2);
16919
16920        assert_eq!(u, vec![0x12, 0x35, 0x12, 0x35]);
16921        assert_eq!(i0, 0);
16922    }
16923
16924    #[test]
16925    fn unbwt_decode_dispatches_two_block_tail_shape() {
16926        let mut u = vec![0u8; 8];
16927        let p = vec![1u32, 0u32];
16928        let mut bucket2 = vec![0u32; ALPHABET_SIZE * ALPHABET_SIZE];
16929        bucket2[0x1234] = 0;
16930        bucket2[0x1235] = 2;
16931        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16932        fastbits[0] = 0x1234;
16933        let i = vec![0u32, 0u32];
16934
16935        unbwt_decode(&mut u, &p, 4, 2, &i, &bucket2, &fastbits, 2, 2);
16936
16937        assert_eq!(u, vec![0x12, 0x35, 0x12, 0x35, 0x00, 0x00, 0x00, 0x00]);
16938    }
16939
16940    #[test]
16941    fn libsais_unbwt_aux_rejects_invalid_sampling_range() {
16942        let t = b"abc";
16943        let mut u = vec![0u8; t.len()];
16944        let mut a = vec![0i32; t.len()];
16945
16946        let result = libsais_unbwt_aux(t, &mut u, &mut a, None, 2, &[0, 4]);
16947
16948        assert_eq!(result, -1);
16949
16950        assert_eq!(libsais_unbwt_aux(t, &mut u, &mut a, None, 0, &[1]), -1);
16951
16952        let mut ctx = unbwt_create_ctx().expect("context");
16953        assert_eq!(
16954            libsais_unbwt_aux_ctx(&mut ctx, t, &mut u, &mut a, None, 0, &[1]),
16955            -1
16956        );
16957        assert_eq!(
16958            libsais_unbwt_aux_omp(t, &mut u, &mut a, None, 0, &[1], 2),
16959            -1
16960        );
16961    }
16962
16963    #[test]
16964    fn libsais_bwt_and_unbwt_round_trip_small_text() {
16965        let t = b"banana";
16966        let mut bwt = vec![0u8; t.len()];
16967        let mut a = vec![0i32; t.len()];
16968
16969        let primary = libsais_bwt(t, &mut bwt, &mut a, 0, None);
16970        assert!(primary > 0);
16971
16972        let mut restored = vec![0u8; t.len()];
16973        let result = libsais_unbwt(&bwt, &mut restored, &mut a, None, primary);
16974
16975        assert_eq!(result, 0);
16976        assert_eq!(restored, t);
16977    }
16978
16979    #[test]
16980    fn libsais_bwt_aux_and_unbwt_aux_round_trip_small_text() {
16981        let t = b"mississippi";
16982        let mut bwt = vec![0u8; t.len()];
16983        let mut a = vec![0i32; t.len()];
16984        let mut samples = vec![0i32; 4];
16985
16986        let result = libsais_bwt_aux(t, &mut bwt, &mut a, 0, None, 4, &mut samples);
16987        assert_eq!(result, 0);
16988
16989        let mut restored = vec![0u8; t.len()];
16990        let result = libsais_unbwt_aux(&bwt, &mut restored, &mut a, None, 4, &samples);
16991
16992        assert_eq!(result, 0);
16993        assert_eq!(restored, t);
16994    }
16995
16996    #[test]
16997    fn libsais_bwt_aux_and_unbwt_aux_omp_round_trip_small_text() {
16998        let t = b"mississippi";
16999        let mut bwt = vec![0u8; t.len()];
17000        let mut a = vec![0i32; t.len()];
17001        let mut samples = vec![0i32; 4];
17002
17003        let result = libsais_bwt_aux(t, &mut bwt, &mut a, 0, None, 4, &mut samples);
17004        assert_eq!(result, 0);
17005
17006        let mut restored = vec![0u8; t.len()];
17007        let result = libsais_unbwt_aux_omp(&bwt, &mut restored, &mut a, None, 4, &samples, 2);
17008
17009        assert_eq!(result, 0);
17010        assert_eq!(restored, t);
17011    }
17012
17013    #[test]
17014    fn real_world_round_trip_on_upstream_readme() {
17015        let t = include_bytes!("../libsais/README.md");
17016        let mut bwt = vec![0u8; t.len()];
17017        let mut a = vec![0i32; t.len()];
17018
17019        let primary = libsais_bwt(t, &mut bwt, &mut a, 0, None);
17020        assert!(primary > 0);
17021
17022        let mut restored = vec![0u8; t.len()];
17023        let result = libsais_unbwt(&bwt, &mut restored, &mut a, None, primary);
17024
17025        assert_eq!(result, 0);
17026        assert_eq!(restored, t);
17027    }
17028
17029    #[test]
17030    fn real_world_aux_omp_round_trip_on_upstream_c_source() {
17031        let t = include_bytes!("../libsais/src/libsais.c");
17032        let mut bwt = vec![0u8; t.len()];
17033        let mut a = vec![0i32; t.len()];
17034        let r = 128i32;
17035        let mut samples = vec![0i32; (t.len() - 1) / usize::try_from(r).expect("fits") + 1];
17036
17037        let result = libsais_bwt_aux(t, &mut bwt, &mut a, 0, None, r, &mut samples);
17038        assert_eq!(result, 0);
17039
17040        let mut restored = vec![0u8; t.len()];
17041        let result = libsais_unbwt_aux_omp(&bwt, &mut restored, &mut a, None, r, &samples, 2);
17042
17043        assert_eq!(result, 0);
17044        assert_eq!(restored, t);
17045    }
17046
17047    #[test]
17048    fn libsais_bwt_aux_rejects_undersized_sampling_array() {
17049        let t = b"upstream source text";
17050        let mut bwt = vec![0u8; t.len()];
17051        let mut a = vec![0i32; t.len()];
17052        let mut samples = vec![0i32; 1];
17053
17054        let result = libsais_bwt_aux(t, &mut bwt, &mut a, 0, None, 2, &mut samples);
17055
17056        assert_eq!(result, -1);
17057
17058        let result = libsais_bwt_aux(t, &mut bwt, &mut a, 0, None, 0, &mut samples);
17059
17060        assert_eq!(result, -1);
17061    }
17062
17063    #[test]
17064    fn libsais_bwt_aux_omp_rejects_invalid_sampling_rate_without_panicking() {
17065        let t = b"upstream source text";
17066        let mut bwt = vec![0u8; t.len()];
17067        let mut a = vec![0i32; t.len()];
17068        let mut samples = vec![0i32; 4];
17069
17070        let result = libsais_bwt_aux_omp(t, &mut bwt, &mut a, 0, None, 0, &mut samples, 2);
17071
17072        assert_eq!(result, -1);
17073    }
17074
17075    #[test]
17076    fn count_helpers_match_c_predicates() {
17077        let sa = [1, -1, 0, -3, 4, 0, -9];
17078        assert_eq!(
17079            count_negative_marked_suffixes(&sa, 0, sa.len() as FastSint),
17080            3
17081        );
17082        assert_eq!(count_zero_marked_suffixes(&sa, 0, sa.len() as FastSint), 2);
17083        assert_eq!(count_negative_marked_suffixes(&sa, 2, 3), 1);
17084        assert_eq!(count_zero_marked_suffixes(&sa, 2, 3), 1);
17085    }
17086
17087    #[test]
17088    fn flip_suffix_markers_omp_toggles_saint_min_bits() {
17089        let mut sa = vec![1, -2, 3, -4];
17090        flip_suffix_markers_omp(&mut sa, 4, 1);
17091        assert_eq!(
17092            sa,
17093            vec![1 ^ SAINT_MIN, -2 ^ SAINT_MIN, 3 ^ SAINT_MIN, -4 ^ SAINT_MIN]
17094        );
17095    }
17096
17097    #[test]
17098    fn flip_suffix_markers_omp_uses_block_partition_for_large_inputs() {
17099        let n = 65_600usize;
17100        let mut single: Vec<SaSint> = (0..n).map(|i| (i as SaSint) ^ SAINT_MIN).collect();
17101        let mut threaded = single.clone();
17102
17103        flip_suffix_markers_omp(&mut single, n as SaSint, 1);
17104        flip_suffix_markers_omp(&mut threaded, n as SaSint, 4);
17105
17106        assert_eq!(threaded, single);
17107    }
17108
17109    #[test]
17110    fn place_cached_suffixes_writes_indices_to_symbol_slots() {
17111        let mut sa = vec![0; 8];
17112        let cache = vec![
17113            ThreadCache {
17114                symbol: 2,
17115                index: 10,
17116            },
17117            ThreadCache {
17118                symbol: 5,
17119                index: 20,
17120            },
17121            ThreadCache {
17122                symbol: 1,
17123                index: 30,
17124            },
17125        ];
17126
17127        place_cached_suffixes(&mut sa, &cache, 0, cache.len() as FastSint);
17128
17129        assert_eq!(sa[2], 10);
17130        assert_eq!(sa[5], 20);
17131        assert_eq!(sa[1], 30);
17132    }
17133
17134    #[test]
17135    fn compact_and_place_cached_suffixes_discards_negative_symbols() {
17136        let mut sa = vec![0; 8];
17137        let mut cache = vec![
17138            ThreadCache {
17139                symbol: 2,
17140                index: 10,
17141            },
17142            ThreadCache {
17143                symbol: -1,
17144                index: 99,
17145            },
17146            ThreadCache {
17147                symbol: 5,
17148                index: 20,
17149            },
17150            ThreadCache {
17151                symbol: -4,
17152                index: 77,
17153            },
17154            ThreadCache {
17155                symbol: 1,
17156                index: 30,
17157            },
17158        ];
17159        let cache_len = cache.len() as FastSint;
17160
17161        compact_and_place_cached_suffixes(&mut sa, &mut cache, 0, cache_len);
17162
17163        assert_eq!(sa[2], 10);
17164        assert_eq!(sa[5], 20);
17165        assert_eq!(sa[1], 30);
17166        assert_eq!(
17167            cache[0],
17168            ThreadCache {
17169                symbol: 2,
17170                index: 10
17171            }
17172        );
17173        assert_eq!(
17174            cache[1],
17175            ThreadCache {
17176                symbol: 5,
17177                index: 20
17178            }
17179        );
17180        assert_eq!(
17181            cache[2],
17182            ThreadCache {
17183                symbol: 1,
17184                index: 30
17185            }
17186        );
17187    }
17188
17189    #[test]
17190    fn gather_lms_suffixes_32s_collects_expected_suffix_starts() {
17191        let t = vec![2, 1, 3, 1, 0];
17192        let mut sa = vec![0; t.len()];
17193        let m = gather_lms_suffixes_32s(&t, &mut sa, t.len() as SaSint);
17194        assert!(m >= 0);
17195        assert!(sa
17196            .iter()
17197            .all(|&value| value >= 0 && value <= t.len() as SaSint));
17198        assert!(sa[t.len() - 1] >= 1 && sa[t.len() - 1] <= t.len() as SaSint - 1);
17199    }
17200
17201    #[test]
17202    fn gather_compacted_lms_suffixes_32s_skips_negative_marked_symbols() {
17203        let t = vec![2, -1, 3, 1, 0];
17204        let mut sa = vec![0; t.len()];
17205        let m = gather_compacted_lms_suffixes_32s(&t, &mut sa, t.len() as SaSint);
17206        assert!(m >= 0);
17207        assert!(sa
17208            .iter()
17209            .all(|&value| value >= 0 && value <= t.len() as SaSint));
17210    }
17211
17212    #[test]
17213    fn count_lms_suffixes_32s_2k_counts_two_bucket_categories() {
17214        let t = vec![2, 1, 3, 1, 0];
17215        let mut buckets = vec![0; 2 * 4];
17216        count_lms_suffixes_32s_2k(&t, t.len() as SaSint, 4, &mut buckets);
17217        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17218    }
17219
17220    #[test]
17221    fn count_lms_suffixes_32s_4k_counts_four_bucket_categories() {
17222        let t = vec![2, 1, 3, 1, 0];
17223        let mut buckets = vec![0; 4 * 4];
17224        count_lms_suffixes_32s_4k(&t, t.len() as SaSint, 4, &mut buckets);
17225        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17226    }
17227
17228    #[test]
17229    fn count_compacted_lms_suffixes_32s_2k_masks_saint_bits() {
17230        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
17231        let mut buckets = vec![0; 2 * 4];
17232        count_compacted_lms_suffixes_32s_2k(&t, t.len() as SaSint, 4, &mut buckets);
17233        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17234    }
17235
17236    #[test]
17237    fn count_and_gather_lms_suffixes_8u_updates_sa_and_buckets() {
17238        let t = vec![2_u8, 1, 3, 1, 0];
17239        let mut sa = vec![0; t.len()];
17240        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
17241        let m = count_and_gather_lms_suffixes_8u(
17242            &t,
17243            &mut sa,
17244            t.len() as SaSint,
17245            &mut buckets,
17246            0,
17247            t.len() as FastSint,
17248        );
17249        assert_eq!(m, 1);
17250        assert_eq!(sa[t.len() - 1], 1);
17251        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17252    }
17253
17254    #[test]
17255    fn get_bucket_stride_prefers_aligned_sizes_when_space_allows() {
17256        assert_eq!(get_bucket_stride(8192, 1000, 2), 1024);
17257        assert_eq!(get_bucket_stride(256, 17, 2), 32);
17258        assert_eq!(get_bucket_stride(8, 17, 2), 17);
17259    }
17260
17261    #[test]
17262    fn count_suffixes_32s_counts_symbol_histogram() {
17263        let t = vec![2, 1, 2, 3, 1, 0, 2];
17264        let mut buckets = vec![0; 4];
17265        count_suffixes_32s(&t, t.len() as SaSint, 4, &mut buckets);
17266        assert_eq!(buckets, vec![1, 2, 3, 1]);
17267    }
17268
17269    #[test]
17270    fn initialize_buckets_start_and_end_8u_sets_ranges_and_freq() {
17271        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
17272        buckets[buckets_index4(0, 0)] = 1;
17273        buckets[buckets_index4(1, 1)] = 2;
17274        buckets[buckets_index4(2, 3)] = 3;
17275        let mut freq = vec![0; ALPHABET_SIZE];
17276        let k = initialize_buckets_start_and_end_8u(&mut buckets, Some(&mut freq));
17277        assert_eq!(k, 3);
17278        assert_eq!(freq[0], 1);
17279        assert_eq!(freq[1], 2);
17280        assert_eq!(freq[2], 3);
17281        assert_eq!(buckets[6 * ALPHABET_SIZE], 0);
17282        assert_eq!(buckets[7 * ALPHABET_SIZE], 1);
17283        assert_eq!(buckets[6 * ALPHABET_SIZE + 1], 1);
17284        assert_eq!(buckets[7 * ALPHABET_SIZE + 1], 3);
17285    }
17286
17287    #[test]
17288    fn initialize_buckets_start_and_end_32s_6k_sets_prefix_ranges() {
17289        let k = 3;
17290        let mut buckets = vec![0; 6 * k];
17291        buckets[buckets_index4(0, 0)] = 1;
17292        buckets[buckets_index4(0, 1)] = 2;
17293        buckets[buckets_index4(1, 2)] = 3;
17294        buckets[buckets_index4(2, 3)] = 4;
17295        initialize_buckets_start_and_end_32s_6k(k as SaSint, &mut buckets);
17296        assert_eq!(&buckets[4 * k..5 * k], &[0, 3, 6]);
17297        assert_eq!(&buckets[5 * k..6 * k], &[3, 6, 10]);
17298    }
17299
17300    #[test]
17301    fn initialize_buckets_start_and_end_32s_4k_sets_prefix_ranges() {
17302        let k = 3;
17303        let mut buckets = vec![0; 4 * k];
17304        buckets[buckets_index2(0, 0)] = 1;
17305        buckets[buckets_index2(0, 1)] = 2;
17306        buckets[buckets_index2(1, 0)] = 3;
17307        buckets[buckets_index2(2, 1)] = 4;
17308        initialize_buckets_start_and_end_32s_4k(k as SaSint, &mut buckets);
17309        assert_eq!(&buckets[2 * k..3 * k], &[0, 3, 6]);
17310        assert_eq!(&buckets[3 * k..4 * k], &[3, 6, 10]);
17311    }
17312
17313    #[test]
17314    fn initialize_buckets_end_32s_2k_rewrites_first_lanes_to_end_positions() {
17315        let k = 3;
17316        let mut buckets = vec![1, 2, 3, 4, 5, 6];
17317        initialize_buckets_end_32s_2k(k as SaSint, &mut buckets);
17318        assert_eq!(buckets[0], 3);
17319        assert_eq!(buckets[2], 10);
17320        assert_eq!(buckets[4], 21);
17321    }
17322
17323    #[test]
17324    fn initialize_buckets_start_and_end_32s_2k_copies_start_positions() {
17325        let k = 3;
17326        let mut buckets = vec![3, 2, 10, 4, 21, 6];
17327        initialize_buckets_start_and_end_32s_2k(k as SaSint, &mut buckets);
17328        assert_eq!(&buckets[..k], &[3, 10, 21]);
17329        assert_eq!(&buckets[k..2 * k], &[0, 3, 10]);
17330    }
17331
17332    #[test]
17333    fn initialize_buckets_start_32s_1k_builds_prefix_starts() {
17334        let mut buckets = vec![1, 2, 3];
17335        initialize_buckets_start_32s_1k(3, &mut buckets);
17336        assert_eq!(buckets, vec![0, 1, 3]);
17337    }
17338
17339    #[test]
17340    fn initialize_buckets_end_32s_1k_builds_prefix_ends() {
17341        let mut buckets = vec![1, 2, 3];
17342        initialize_buckets_end_32s_1k(3, &mut buckets);
17343        assert_eq!(buckets, vec![1, 3, 6]);
17344    }
17345
17346    #[test]
17347    fn initialize_buckets_for_lms_suffixes_radix_sort_8u_returns_total_lms_slots() {
17348        let t = vec![2_u8, 1, 3, 1, 0];
17349        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17350        buckets[buckets_index4(0, 1)] = 1;
17351        buckets[buckets_index4(1, 3)] = 2;
17352        let sum = initialize_buckets_for_lms_suffixes_radix_sort_8u(&t, &mut buckets, 4);
17353        assert!(sum >= 0);
17354    }
17355
17356    #[test]
17357    fn initialize_buckets_for_lms_suffixes_radix_sort_32s_2k_rewrites_two_lane_prefixes() {
17358        let t = vec![2, 1, 3, 1, 0];
17359        let mut buckets = vec![0; 2 * 4];
17360        initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(&t, 4, &mut buckets, 4);
17361        assert!(buckets.iter().any(|&v| v != 0));
17362    }
17363
17364    #[test]
17365    fn initialize_buckets_for_lms_suffixes_radix_sort_32s_6k_returns_total_lms_slots() {
17366        let t = vec![2, 1, 3, 1, 0];
17367        let mut buckets = vec![0; 6 * 4];
17368        buckets[buckets_index4(0, 1)] = 1;
17369        buckets[buckets_index4(1, 3)] = 2;
17370        let sum = initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(&t, 4, &mut buckets, 4);
17371        assert!(sum >= 0);
17372    }
17373
17374    #[test]
17375    fn initialize_buckets_for_radix_and_partial_sorting_32s_4k_sets_start_end_views() {
17376        let t = vec![2, 1, 3, 1, 0];
17377        let k = 4usize;
17378        let mut buckets = vec![0; 4 * k];
17379        buckets[buckets_index2(0, 0)] = 1;
17380        buckets[buckets_index2(0, 1)] = 2;
17381        buckets[buckets_index2(1, 0)] = 3;
17382        initialize_buckets_for_radix_and_partial_sorting_32s_4k(&t, k as SaSint, &mut buckets, 4);
17383        assert_eq!(buckets[2 * k], 0);
17384        assert!(buckets[3 * k] >= buckets[2 * k]);
17385    }
17386
17387    #[test]
17388    fn radix_sort_lms_suffixes_8u_places_suffixes_by_bucket() {
17389        let t = vec![1_u8, 0, 1, 0];
17390        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
17391        let mut induction_bucket = vec![0; 2 * ALPHABET_SIZE];
17392        induction_bucket[buckets_index2(0, 0)] = 2;
17393        induction_bucket[buckets_index2(1, 0)] = 4;
17394        radix_sort_lms_suffixes_8u(&t, &mut sa, &mut induction_bucket, 4, 4);
17395        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
17396    }
17397
17398    #[test]
17399    fn radix_sort_lms_suffixes_8u_omp_wraps_sequential_version() {
17400        let t = vec![9_u8, 1, 0, 1, 0];
17401        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
17402        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17403        buckets[4 * ALPHABET_SIZE + buckets_index2(0, 0)] = 2;
17404        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 4;
17405        let mut thread_state = alloc_thread_state(2).unwrap();
17406        radix_sort_lms_suffixes_8u_omp(&t, &mut sa, 9, 5, 0, &mut buckets, 2, &mut thread_state);
17407        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
17408    }
17409
17410    #[test]
17411    fn radix_sort_lms_suffixes_8u_omp_uses_thread_state_for_large_inputs() {
17412        let m = 65_600usize;
17413        let n = 2 * m + 16;
17414        let start = n - m + 1;
17415        let t: Vec<u8> = (0..n).map(|i| (i % 4) as u8).collect();
17416        let suffixes: Vec<SaSint> = (0..m - 1).map(|i| i as SaSint).collect();
17417
17418        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17419        for &suffix in &suffixes {
17420            buckets[4 * ALPHABET_SIZE + buckets_index2(t[suffix as usize] as usize, 0)] += 1;
17421        }
17422        let mut sum = 0;
17423        for symbol in 0..ALPHABET_SIZE {
17424            let bucket = 4 * ALPHABET_SIZE + buckets_index2(symbol, 0);
17425            sum += buckets[bucket];
17426            buckets[bucket] = sum;
17427        }
17428
17429        let mut sa_single = vec![0; n];
17430        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
17431        let mut sa_threaded = sa_single.clone();
17432        let mut buckets_single = buckets.clone();
17433        let mut buckets_threaded = buckets;
17434        let mut thread_state = alloc_thread_state(4).unwrap();
17435        thread_state[3].m = m as FastSint;
17436
17437        radix_sort_lms_suffixes_8u_omp(
17438            &t,
17439            &mut sa_single,
17440            n as SaSint,
17441            m as SaSint,
17442            0,
17443            &mut buckets_single,
17444            1,
17445            &mut [],
17446        );
17447        radix_sort_lms_suffixes_8u_omp(
17448            &t,
17449            &mut sa_threaded,
17450            n as SaSint,
17451            m as SaSint,
17452            0,
17453            &mut buckets_threaded,
17454            4,
17455            &mut thread_state,
17456        );
17457
17458        assert_eq!(sa_threaded, sa_single);
17459    }
17460
17461    #[test]
17462    fn radix_sort_lms_suffixes_32s_6k_places_suffixes_by_bucket() {
17463        let t = vec![1, 0, 1, 0];
17464        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
17465        let mut induction_bucket = vec![2, 4];
17466        radix_sort_lms_suffixes_32s_6k(&t, &mut sa, &mut induction_bucket, 4, 4);
17467        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
17468    }
17469
17470    #[test]
17471    fn radix_sort_lms_suffixes_32s_2k_places_suffixes_by_bucket() {
17472        let t = vec![1, 0, 1, 0];
17473        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
17474        let mut induction_bucket = vec![2, 0, 4, 0];
17475        radix_sort_lms_suffixes_32s_2k(&t, &mut sa, &mut induction_bucket, 4, 4);
17476        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
17477    }
17478
17479    #[test]
17480    fn radix_sort_lms_suffixes_32s_6k_omp_wraps_sequential_version() {
17481        let t = vec![9, 1, 0, 1, 0];
17482        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
17483        let mut induction_bucket = vec![2, 4];
17484        let mut thread_state = alloc_thread_state(2).unwrap();
17485        radix_sort_lms_suffixes_32s_6k_omp(
17486            &t,
17487            &mut sa,
17488            9,
17489            5,
17490            &mut induction_bucket,
17491            2,
17492            &mut thread_state,
17493        );
17494        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
17495    }
17496
17497    #[test]
17498    fn radix_sort_lms_suffixes_32s_2k_omp_wraps_sequential_version() {
17499        let t = vec![9, 1, 0, 1, 0];
17500        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
17501        let mut induction_bucket = vec![2, 0, 4, 0];
17502        let mut thread_state = alloc_thread_state(2).unwrap();
17503        radix_sort_lms_suffixes_32s_2k_omp(
17504            &t,
17505            &mut sa,
17506            9,
17507            5,
17508            &mut induction_bucket,
17509            2,
17510            &mut thread_state,
17511        );
17512        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
17513    }
17514
17515    #[test]
17516    fn radix_sort_lms_suffixes_32s_block_omp_runs_cache_pipeline() {
17517        let t = vec![9, 1, 0, 1, 0];
17518        let mut sa_6k = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
17519        let mut bucket_6k = vec![2, 4];
17520        let mut cache = vec![ThreadCache::default(); 9];
17521        radix_sort_lms_suffixes_32s_6k_block_omp(
17522            &t,
17523            &mut sa_6k,
17524            &mut bucket_6k,
17525            &mut cache,
17526            5,
17527            4,
17528            2,
17529        );
17530        assert_eq!(&sa_6k[..4], &[2, 4, 1, 3]);
17531
17532        let mut sa_2k = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
17533        let mut bucket_2k = vec![2, 0, 4, 0];
17534        cache.fill(ThreadCache::default());
17535        radix_sort_lms_suffixes_32s_2k_block_omp(
17536            &t,
17537            &mut sa_2k,
17538            &mut bucket_2k,
17539            &mut cache,
17540            5,
17541            4,
17542            2,
17543        );
17544        assert_eq!(&sa_2k[..4], &[2, 4, 1, 3]);
17545    }
17546
17547    #[test]
17548    fn radix_sort_lms_suffixes_32s_omp_uses_block_pipeline_for_large_inputs() {
17549        let m = 65_600usize;
17550        let n = 2 * m + 16;
17551        let start = n - m + 1;
17552        let t: Vec<SaSint> = (0..n).map(|i| (i % 4) as SaSint).collect();
17553        let suffixes: Vec<SaSint> = (0..m - 1).map(|i| i as SaSint).collect();
17554
17555        let mut bucket_ends = vec![0; 4];
17556        for &suffix in &suffixes {
17557            bucket_ends[t[suffix as usize] as usize] += 1;
17558        }
17559        let mut sum = 0;
17560        for bucket in &mut bucket_ends {
17561            sum += *bucket;
17562            *bucket = sum;
17563        }
17564
17565        let mut sa_single = vec![0; n];
17566        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
17567        let mut sa_threaded = sa_single.clone();
17568        let mut bucket_single = bucket_ends.clone();
17569        let mut bucket_threaded = bucket_ends.clone();
17570        let mut thread_state = alloc_thread_state(4).unwrap();
17571
17572        radix_sort_lms_suffixes_32s_6k_omp(
17573            &t,
17574            &mut sa_single,
17575            n as SaSint,
17576            m as SaSint,
17577            &mut bucket_single,
17578            1,
17579            &mut [],
17580        );
17581        radix_sort_lms_suffixes_32s_6k_omp(
17582            &t,
17583            &mut sa_threaded,
17584            n as SaSint,
17585            m as SaSint,
17586            &mut bucket_threaded,
17587            4,
17588            &mut thread_state,
17589        );
17590        assert_eq!(sa_threaded, sa_single);
17591        assert_eq!(bucket_threaded, bucket_single);
17592
17593        let mut bucket_2k = vec![0; 8];
17594        for (symbol, &end) in bucket_ends.iter().enumerate() {
17595            bucket_2k[buckets_index2(symbol, 0)] = end;
17596        }
17597        let mut sa_single = vec![0; n];
17598        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
17599        let mut sa_threaded = sa_single.clone();
17600        let mut bucket_single = bucket_2k.clone();
17601        let mut bucket_threaded = bucket_2k;
17602
17603        radix_sort_lms_suffixes_32s_2k_omp(
17604            &t,
17605            &mut sa_single,
17606            n as SaSint,
17607            m as SaSint,
17608            &mut bucket_single,
17609            1,
17610            &mut [],
17611        );
17612        radix_sort_lms_suffixes_32s_2k_omp(
17613            &t,
17614            &mut sa_threaded,
17615            n as SaSint,
17616            m as SaSint,
17617            &mut bucket_threaded,
17618            4,
17619            &mut thread_state,
17620        );
17621        assert_eq!(sa_threaded, sa_single);
17622        assert_eq!(bucket_threaded, bucket_single);
17623    }
17624
17625    #[test]
17626    fn radix_sort_lms_suffixes_32s_1k_collects_lms_suffixes() {
17627        let t = vec![2, 1, 3, 1, 0];
17628        let mut sa = vec![0; t.len()];
17629        let mut buckets = vec![0, 2, 4, 5];
17630        let m = radix_sort_lms_suffixes_32s_1k(&t, &mut sa, t.len() as SaSint, &mut buckets);
17631        assert!(m >= 0);
17632    }
17633
17634    #[test]
17635    fn radix_sort_set_markers_32s_6k_marks_target_suffixes() {
17636        let mut sa = vec![0; 6];
17637        let induction_bucket = vec![1, 3, 5];
17638        radix_sort_set_markers_32s_6k(&mut sa, &induction_bucket, 0, 3);
17639        assert_eq!(sa[1], SAINT_MIN);
17640        assert_eq!(sa[3], SAINT_MIN);
17641        assert_eq!(sa[5], SAINT_MIN);
17642    }
17643
17644    #[test]
17645    fn radix_sort_set_markers_32s_4k_marks_target_suffixes() {
17646        let mut sa = vec![0; 6];
17647        let induction_bucket = vec![1, 0, 3, 0, 5, 0];
17648        radix_sort_set_markers_32s_4k(&mut sa, &induction_bucket, 0, 3);
17649        assert_eq!(sa[1], SUFFIX_GROUP_MARKER);
17650        assert_eq!(sa[3], SUFFIX_GROUP_MARKER);
17651        assert_eq!(sa[5], SUFFIX_GROUP_MARKER);
17652    }
17653
17654    #[test]
17655    fn radix_sort_set_markers_32s_6k_omp_wraps_sequential_version() {
17656        let mut sa = vec![0; 6];
17657        let induction_bucket = vec![1, 3, 5];
17658        radix_sort_set_markers_32s_6k_omp(&mut sa, 4, &induction_bucket, 2);
17659        assert_eq!(sa[1], SAINT_MIN);
17660        assert_eq!(sa[3], SAINT_MIN);
17661        assert_eq!(sa[5], SAINT_MIN);
17662    }
17663
17664    #[test]
17665    fn radix_sort_set_markers_32s_4k_omp_wraps_sequential_version() {
17666        let mut sa = vec![0; 6];
17667        let induction_bucket = vec![1, 0, 3, 0, 5, 0];
17668        radix_sort_set_markers_32s_4k_omp(&mut sa, 4, &induction_bucket, 2);
17669        assert_eq!(sa[1], SUFFIX_GROUP_MARKER);
17670        assert_eq!(sa[3], SUFFIX_GROUP_MARKER);
17671        assert_eq!(sa[5], SUFFIX_GROUP_MARKER);
17672    }
17673
17674    #[test]
17675    fn radix_sort_set_markers_32s_omp_partitions_large_inputs() {
17676        let k = 65_600usize;
17677        let induction_bucket_6k: Vec<SaSint> = (0..k).map(|i| i as SaSint).collect();
17678        let mut sa_single = vec![0; k];
17679        let mut sa_threaded = vec![0; k];
17680        radix_sort_set_markers_32s_6k_omp(&mut sa_single, k as SaSint, &induction_bucket_6k, 1);
17681        radix_sort_set_markers_32s_6k_omp(&mut sa_threaded, k as SaSint, &induction_bucket_6k, 4);
17682        assert_eq!(sa_threaded, sa_single);
17683
17684        let mut induction_bucket_4k = vec![0; 2 * k];
17685        for i in 0..k {
17686            induction_bucket_4k[buckets_index2(i, 0)] = i as SaSint;
17687        }
17688        let mut sa_single = vec![0; k];
17689        let mut sa_threaded = vec![0; k];
17690        radix_sort_set_markers_32s_4k_omp(&mut sa_single, k as SaSint, &induction_bucket_4k, 1);
17691        radix_sort_set_markers_32s_4k_omp(&mut sa_threaded, k as SaSint, &induction_bucket_4k, 4);
17692        assert_eq!(sa_threaded, sa_single);
17693    }
17694
17695    #[test]
17696    fn initialize_buckets_for_partial_sorting_8u_sets_start_and_distinct_views() {
17697        let t = vec![2_u8, 1, 3, 1, 0];
17698        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17699        buckets[buckets_index4(0, 0)] = 1;
17700        buckets[buckets_index4(0, 2)] = 2;
17701        initialize_buckets_for_partial_sorting_8u(&t, &mut buckets, 4, 3);
17702        assert!(buckets[0] >= 4);
17703        assert!(buckets[1] >= 0);
17704        assert!(buckets[4 * ALPHABET_SIZE] >= 4);
17705    }
17706
17707    #[test]
17708    fn initialize_buckets_for_partial_sorting_32s_6k_rewrites_bucket_views() {
17709        let t = vec![2, 1, 3, 1, 0];
17710        let k = 4usize;
17711        let mut buckets = vec![0; 6 * k];
17712        buckets[buckets_index4(0, 0)] = 1;
17713        buckets[buckets_index4(0, 1)] = 2;
17714        buckets[buckets_index4(1, 2)] = 3;
17715        initialize_buckets_for_partial_sorting_32s_6k(&t, k as SaSint, &mut buckets, 4, 3);
17716        assert!(buckets[0] >= 4);
17717        assert!(buckets[4 * k] >= 4);
17718    }
17719
17720    #[test]
17721    fn partial_sorting_scan_left_to_right_8u_emits_induced_suffixes() {
17722        let t = vec![2_u8, 1, 3, 1, 0];
17723        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
17724        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17725        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 2;
17726        let d = partial_sorting_scan_left_to_right_8u(&t, &mut sa, &mut buckets, 0, 0, 2);
17727        assert!(d >= 0);
17728        assert!(sa.iter().any(|&v| v != 0));
17729    }
17730
17731    #[test]
17732    fn partial_sorting_scan_left_to_right_8u_omp_wraps_sequential_version() {
17733        let t = vec![2_u8, 1, 3, 1, 0];
17734        let mut sa = vec![0; 8];
17735        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17736        buckets[4 * ALPHABET_SIZE + buckets_index2(0, 0)] = 1;
17737        let mut thread_state = alloc_thread_state(2).unwrap();
17738        let d = partial_sorting_scan_left_to_right_8u_omp(
17739            &t,
17740            &mut sa,
17741            5,
17742            4,
17743            &mut buckets,
17744            0,
17745            0,
17746            2,
17747            &mut thread_state,
17748        );
17749        assert!(d >= 1);
17750    }
17751
17752    #[test]
17753    fn partial_sorting_scan_left_to_right_32s_6k_emits_induced_suffixes() {
17754        let t = vec![2, 1, 3, 1, 0];
17755        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
17756        let mut buckets = vec![0; 4 * 4];
17757        buckets[buckets_index4(1, 0)] = 2;
17758        let d = partial_sorting_scan_left_to_right_32s_6k(&t, &mut sa, &mut buckets, 0, 0, 2);
17759        assert!(d >= 0);
17760        assert!(sa.iter().any(|&v| v != 0));
17761    }
17762
17763    #[test]
17764    fn partial_sorting_scan_left_to_right_32s_4k_emits_induced_suffixes() {
17765        let t = vec![2, 1, 3, 1, 0];
17766        let k = 4usize;
17767        let mut sa = vec![2 | SUFFIX_GROUP_MARKER, 4, 0, 0, 0, 0];
17768        let mut buckets = vec![0; 4 * k];
17769        buckets[2 * k + 1] = 2;
17770        let d = partial_sorting_scan_left_to_right_32s_4k(
17771            &t,
17772            &mut sa,
17773            k as SaSint,
17774            &mut buckets,
17775            0,
17776            0,
17777            2,
17778        );
17779        assert!(d >= 0);
17780        assert!(sa.iter().any(|&v| v != 0));
17781    }
17782
17783    #[test]
17784    fn partial_sorting_scan_left_to_right_32s_1k_emits_induced_suffixes() {
17785        let t = vec![2, 1, 3, 1, 0];
17786        let mut sa = vec![2, 4, 0, 0, 0, 0];
17787        let mut buckets = vec![0; 4];
17788        buckets[1] = 2;
17789        partial_sorting_scan_left_to_right_32s_1k(&t, &mut sa, &mut buckets, 0, 2);
17790        assert!(sa.iter().any(|&v| v != 0));
17791    }
17792
17793    #[test]
17794    fn partial_sorting_scan_left_to_right_32s_6k_omp_wraps_sequential_version() {
17795        let t = vec![2, 1, 3, 1, 0];
17796        let mut sa = vec![0; 8];
17797        let mut buckets = vec![0; 4 * 4];
17798        let mut thread_state = alloc_thread_state(2).unwrap();
17799        let d = partial_sorting_scan_left_to_right_32s_6k_omp(
17800            &t,
17801            &mut sa,
17802            5,
17803            &mut buckets,
17804            0,
17805            0,
17806            2,
17807            &mut thread_state,
17808        );
17809        assert!(d >= 1);
17810    }
17811
17812    #[test]
17813    fn partial_sorting_scan_left_to_right_32s_4k_omp_wraps_sequential_version() {
17814        let t = vec![2, 1, 3, 1, 0];
17815        let k = 4usize;
17816        let mut sa = vec![0; 8];
17817        let mut buckets = vec![0; 4 * k];
17818        let mut thread_state = alloc_thread_state(2).unwrap();
17819        let d = partial_sorting_scan_left_to_right_32s_4k_omp(
17820            &t,
17821            &mut sa,
17822            5,
17823            k as SaSint,
17824            &mut buckets,
17825            0,
17826            2,
17827            &mut thread_state,
17828        );
17829        assert!(d >= 1);
17830    }
17831
17832    #[test]
17833    fn partial_sorting_scan_left_to_right_32s_1k_omp_wraps_sequential_version() {
17834        let t = vec![2, 1, 3, 1, 0];
17835        let mut sa = vec![0; 8];
17836        let mut buckets = vec![0; 4];
17837        let mut thread_state = alloc_thread_state(2).unwrap();
17838        partial_sorting_scan_left_to_right_32s_1k_omp(
17839            &t,
17840            &mut sa,
17841            5,
17842            &mut buckets,
17843            2,
17844            &mut thread_state,
17845        );
17846        assert!(sa.iter().any(|&v| v != 0));
17847    }
17848
17849    #[test]
17850    fn partial_sorting_scan_left_to_right_32s_6k_block_gather_records_bucket_symbols() {
17851        let t = vec![3, 1, 2, 0];
17852        let mut sa = vec![2 | SAINT_MIN, 0, 0, 0];
17853        let mut cache = vec![ThreadCache::default(); 1];
17854
17855        partial_sorting_scan_left_to_right_32s_6k_block_gather(&t, &mut sa, &mut cache, 0, 1);
17856
17857        assert_eq!(cache[0].index, 2 | SAINT_MIN);
17858        assert_eq!(cache[0].symbol, buckets_index4(1, 1) as SaSint);
17859    }
17860
17861    #[test]
17862    fn partial_sorting_scan_left_to_right_32s_1k_block_gather_zeroes_positive_entries() {
17863        let t = vec![3, 1, 2, 0];
17864        let mut sa = vec![2, 0, 0, 0];
17865        let mut cache = vec![ThreadCache::default(); 1];
17866
17867        partial_sorting_scan_left_to_right_32s_1k_block_gather(&t, &mut sa, &mut cache, 0, 1);
17868
17869        assert_eq!(cache[0].symbol, 1);
17870        assert_eq!(cache[0].index, 1);
17871        assert_eq!(sa[0], 0);
17872    }
17873
17874    #[test]
17875    fn partial_sorting_scan_left_to_right_32s_1k_block_omp_uses_relative_cache() {
17876        let block_start = 20_000usize;
17877        let block_size = 16_384usize;
17878        let n = block_start + block_size + 8;
17879        let t = vec![1; n];
17880        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
17881
17882        let mut sa_single = vec![0; n];
17883        sa_single[block_start..block_start + block_size].copy_from_slice(&suffixes);
17884        let mut sa_threaded = sa_single.clone();
17885        let mut bucket_single = vec![0, 0];
17886        let mut bucket_threaded = bucket_single.clone();
17887        let mut cache = vec![ThreadCache::default(); 4 * LIBSAIS_PER_THREAD_CACHE_SIZE];
17888
17889        partial_sorting_scan_left_to_right_32s_1k(
17890            &t,
17891            &mut sa_single,
17892            &mut bucket_single,
17893            block_start as FastSint,
17894            block_size as FastSint,
17895        );
17896        partial_sorting_scan_left_to_right_32s_1k_block_omp(
17897            &t,
17898            &mut sa_threaded,
17899            &mut bucket_threaded,
17900            &mut cache,
17901            block_start as FastSint,
17902            block_size as FastSint,
17903            4,
17904        );
17905
17906        assert_eq!(sa_threaded, sa_single);
17907        assert_eq!(bucket_threaded, bucket_single);
17908    }
17909
17910    #[test]
17911    fn partial_sorting_scan_left_to_right_8u_block_prepare_records_cache_and_counts() {
17912        let t = vec![2_u8, 1, 3, 1, 0];
17913        let sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
17914        let k = 4;
17915        let mut buckets = vec![0; 4 * k];
17916        let mut cache = vec![ThreadCache::default(); 8];
17917        let mut state = ThreadState::new();
17918        let (position, count) = partial_sorting_scan_left_to_right_8u_block_prepare(
17919            &t,
17920            &sa,
17921            k as SaSint,
17922            &mut buckets,
17923            &mut cache,
17924            0,
17925            2,
17926        );
17927        state.position = position;
17928        state.count = count;
17929        assert!(state.count >= 1);
17930        assert!(cache
17931            .iter()
17932            .take(state.count as usize)
17933            .any(|entry| entry.symbol >= 0));
17934    }
17935
17936    #[test]
17937    fn partial_sorting_scan_left_to_right_8u_block_place_writes_induced_values() {
17938        let mut sa = vec![0; 8];
17939        let mut buckets = vec![0; 8];
17940        buckets[0] = 0;
17941        buckets[1] = 1;
17942        let cache = vec![
17943            ThreadCache {
17944                index: 3 | SAINT_MIN,
17945                symbol: 0,
17946            },
17947            ThreadCache {
17948                index: 5,
17949                symbol: 1,
17950            },
17951        ];
17952        partial_sorting_scan_left_to_right_8u_block_place(&mut sa, &mut buckets, 2, &cache, 2, 0);
17953        assert!(sa[0] != 0 || sa[1] != 0);
17954    }
17955
17956    #[test]
17957    fn partial_sorting_scan_left_to_right_8u_block_omp_wraps_sequential_version() {
17958        let t = vec![2_u8, 1, 3, 1, 0];
17959        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
17960        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17961        let mut thread_state = alloc_thread_state(2).unwrap();
17962        let d = partial_sorting_scan_left_to_right_8u_block_omp(
17963            &t,
17964            &mut sa,
17965            4,
17966            &mut buckets,
17967            0,
17968            0,
17969            2,
17970            2,
17971            &mut thread_state,
17972        );
17973        assert!(d >= 0);
17974    }
17975
17976    #[test]
17977    fn partial_sorting_shift_markers_8u_omp_toggles_segment_markers() {
17978        let mut sa = vec![1 | SAINT_MIN, 2 | SAINT_MIN, 3, 4 | SAINT_MIN, 5];
17979        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17980        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 5;
17981        buckets[buckets_index2(0, 0)] = 0;
17982        let len = sa.len() as SaSint;
17983        partial_sorting_shift_markers_8u_omp(&mut sa, len, &buckets, 1);
17984        assert!(sa.iter().any(|&v| (v & SAINT_MIN) == 0));
17985    }
17986
17987    #[test]
17988    fn partial_sorting_shift_markers_32s_6k_omp_toggles_segment_markers() {
17989        let mut sa = vec![1 | SAINT_MIN, 2 | SAINT_MIN, 3, 4 | SAINT_MIN, 5];
17990        let k = 3usize;
17991        let mut buckets = vec![0; 6 * k];
17992        buckets[buckets_index4(1, 0)] = 5;
17993        buckets[4 * k + buckets_index2(0, 0)] = 0;
17994        partial_sorting_shift_markers_32s_6k_omp(&mut sa, k as SaSint, &buckets, 1);
17995        assert!(sa.iter().any(|&v| (v & SAINT_MIN) == 0));
17996    }
17997
17998    #[test]
17999    fn partial_sorting_shift_markers_32s_4k_toggles_group_markers() {
18000        let mut sa = vec![
18001            1 | SUFFIX_GROUP_MARKER,
18002            2 | SUFFIX_GROUP_MARKER,
18003            3,
18004            4 | SUFFIX_GROUP_MARKER,
18005        ];
18006        let len = sa.len() as SaSint;
18007        partial_sorting_shift_markers_32s_4k(&mut sa, len);
18008        assert!(sa.iter().any(|&v| (v & SUFFIX_GROUP_MARKER) == 0));
18009    }
18010
18011    #[test]
18012    fn partial_sorting_shift_buckets_32s_6k_moves_temp_bucket_view_into_main_slots() {
18013        let k = 3usize;
18014        let mut buckets = vec![0; 6 * k];
18015        buckets[4 * k + 0] = 10;
18016        buckets[4 * k + 1] = 11;
18017        buckets[4 * k + 2] = 12;
18018        buckets[4 * k + 3] = 13;
18019        partial_sorting_shift_buckets_32s_6k(k as SaSint, &mut buckets);
18020        assert_eq!(buckets[0], 10);
18021        assert_eq!(buckets[1], 11);
18022        assert_eq!(buckets[4], 12);
18023        assert_eq!(buckets[5], 13);
18024    }
18025
18026    #[test]
18027    fn partial_sorting_scan_right_to_left_8u_emits_induced_suffixes() {
18028        let t = vec![0_u8, 1, 2, 1, 0];
18029        let mut sa = vec![0, 0, 4 | SAINT_MIN];
18030        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
18031        buckets[buckets_index2(1, 1)] = 2;
18032
18033        let d = partial_sorting_scan_right_to_left_8u(&t, &mut sa, &mut buckets, 0, 2, 1);
18034
18035        assert_eq!(d, 1);
18036        assert_eq!(sa[1], 3 | SAINT_MIN);
18037        assert_eq!(buckets[buckets_index2(1, 1)], 1);
18038        assert_eq!(buckets[2 * ALPHABET_SIZE + buckets_index2(1, 1)], 1);
18039    }
18040
18041    #[test]
18042    fn partial_gsa_scan_right_to_left_8u_skips_separator_bucket() {
18043        let t = vec![1_u8, 0, 0];
18044        let mut sa = vec![0, 2 | SAINT_MIN];
18045        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
18046        buckets[buckets_index2(0, 1)] = 2;
18047
18048        let d = partial_gsa_scan_right_to_left_8u(&t, &mut sa, &mut buckets, 0, 1, 1);
18049
18050        assert_eq!(d, 1);
18051        assert_eq!(sa, vec![0, 2 | SAINT_MIN]);
18052        assert_eq!(buckets[buckets_index2(0, 1)], 2);
18053    }
18054
18055    #[test]
18056    fn partial_sorting_scan_right_to_left_32s_6k_emits_induced_suffixes() {
18057        let t = vec![0, 1, 2, 1, 0];
18058        let mut sa = vec![0, 0, 4 | SAINT_MIN];
18059        let mut buckets = vec![0; 4 * 3];
18060        buckets[buckets_index4(1, 1)] = 2;
18061
18062        let d = partial_sorting_scan_right_to_left_32s_6k(&t, &mut sa, &mut buckets, 0, 2, 1);
18063
18064        assert_eq!(d, 1);
18065        assert_eq!(sa[1], 3 | SAINT_MIN);
18066        assert_eq!(buckets[buckets_index4(1, 1)], 1);
18067        assert_eq!(buckets[buckets_index4(1, 1) + 2], 1);
18068    }
18069
18070    #[test]
18071    fn partial_sorting_scan_right_to_left_32s_1k_omp_wraps_sequential_version() {
18072        let t = vec![0, 1, 2, 1, 0];
18073        let mut sa = vec![0, 0, 4];
18074        let mut buckets = vec![0; 3];
18075        buckets[1] = 2;
18076        let mut thread_state = alloc_thread_state(2).unwrap();
18077
18078        partial_sorting_scan_right_to_left_32s_1k_omp(
18079            &t,
18080            &mut sa,
18081            3,
18082            &mut buckets,
18083            2,
18084            &mut thread_state,
18085        );
18086
18087        assert_eq!(sa[1], 3 | SAINT_MIN);
18088        assert_eq!(buckets[1], 1);
18089    }
18090
18091    #[test]
18092    fn partial_sorting_scan_right_to_left_32s_6k_block_gather_records_symbols() {
18093        let t = vec![0, 1, 2, 1, 0];
18094        let sa = vec![0, 4 | SAINT_MIN, 0];
18095        let mut cache = vec![ThreadCache::default(); sa.len()];
18096
18097        partial_sorting_scan_right_to_left_32s_6k_block_gather(&t, &sa, &mut cache, 1, 1);
18098
18099        assert_eq!(cache[0].index, 4 | SAINT_MIN);
18100        assert_eq!(cache[0].symbol, buckets_index4(1, 1) as SaSint);
18101    }
18102
18103    #[test]
18104    fn partial_sorting_scan_right_to_left_32s_4k_block_gather_zeroes_positive_entries() {
18105        let t = vec![0, 1, 2, 1, 0];
18106        let mut sa = vec![0, 4 | SUFFIX_GROUP_MARKER, 0];
18107        let mut cache = vec![ThreadCache::default(); sa.len()];
18108
18109        partial_sorting_scan_right_to_left_32s_4k_block_gather(&t, &mut sa, &mut cache, 1, 1);
18110
18111        assert_eq!(sa[1], 0);
18112        assert_eq!(cache[0].index, 4 | SUFFIX_GROUP_MARKER);
18113        assert_eq!(cache[0].symbol, buckets_index2(1, 1) as SaSint);
18114    }
18115
18116    #[test]
18117    fn partial_sorting_scan_right_to_left_32s_1k_block_gather_stores_preinduced_entries() {
18118        let t = vec![0, 1, 2, 1, 0];
18119        let mut sa = vec![0, 4, 0];
18120        let mut cache = vec![ThreadCache::default(); sa.len()];
18121
18122        partial_sorting_scan_right_to_left_32s_1k_block_gather(&t, &mut sa, &mut cache, 1, 1);
18123
18124        assert_eq!(sa[1], 0);
18125        assert_eq!(cache[0].index, 3 | SAINT_MIN);
18126        assert_eq!(cache[0].symbol, 1);
18127    }
18128
18129    #[test]
18130    fn partial_sorting_scan_right_to_left_32s_6k_block_sort_updates_bucket_and_marker_state() {
18131        let t = vec![0, 1, 2, 1, 0];
18132        let mut cache = vec![ThreadCache::default(); 3];
18133        cache[0].index = 4 | SAINT_MIN;
18134        cache[0].symbol = buckets_index4(1, 1) as SaSint;
18135        let mut buckets = vec![0; 4 * 3];
18136        buckets[buckets_index4(1, 1)] = 2;
18137
18138        let d = partial_sorting_scan_right_to_left_32s_6k_block_sort(
18139            &t,
18140            &mut buckets,
18141            0,
18142            &mut cache,
18143            1,
18144            1,
18145        );
18146
18147        assert_eq!(d, 1);
18148        assert_eq!(cache[0].index, 3 | SAINT_MIN);
18149        assert_eq!(buckets[buckets_index4(1, 1)], 1);
18150        assert_eq!(buckets[buckets_index4(1, 1) + 2], 1);
18151    }
18152
18153    #[test]
18154    fn partial_sorting_scan_right_to_left_32s_1k_block_omp_places_cached_suffixes() {
18155        let t = vec![0, 1, 2, 1, 0];
18156        let mut sa = vec![0, 4, 0];
18157        let mut buckets = vec![0; 3];
18158        buckets[1] = 2;
18159        let mut cache = vec![ThreadCache::default(); sa.len()];
18160
18161        partial_sorting_scan_right_to_left_32s_1k_block_omp(
18162            &t,
18163            &mut sa,
18164            &mut buckets,
18165            &mut cache,
18166            1,
18167            1,
18168            2,
18169        );
18170
18171        assert_eq!(sa[1], 3 | SAINT_MIN);
18172        assert_eq!(buckets[1], 1);
18173    }
18174
18175    #[test]
18176    fn partial_sorting_scan_right_to_left_32s_1k_block_omp_uses_relative_cache() {
18177        let block_start = 20_000usize;
18178        let block_size = 16_384usize;
18179        let n = block_start + block_size + 8;
18180        let t = vec![1; n];
18181        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18182
18183        let mut sa_single = vec![0; n];
18184        sa_single[block_start..block_start + block_size].copy_from_slice(&suffixes);
18185        let mut sa_threaded = sa_single.clone();
18186        let mut bucket_single = vec![0, block_size as SaSint];
18187        let mut bucket_threaded = bucket_single.clone();
18188        let mut cache = vec![ThreadCache::default(); 4 * LIBSAIS_PER_THREAD_CACHE_SIZE];
18189
18190        partial_sorting_scan_right_to_left_32s_1k(
18191            &t,
18192            &mut sa_single,
18193            &mut bucket_single,
18194            block_start as FastSint,
18195            block_size as FastSint,
18196        );
18197        partial_sorting_scan_right_to_left_32s_1k_block_omp(
18198            &t,
18199            &mut sa_threaded,
18200            &mut bucket_threaded,
18201            &mut cache,
18202            block_start as FastSint,
18203            block_size as FastSint,
18204            4,
18205        );
18206
18207        assert_eq!(sa_threaded, sa_single);
18208        assert_eq!(bucket_threaded, bucket_single);
18209    }
18210
18211    #[test]
18212    fn partial_sorting_gather_lms_suffixes_32s_4k_compacts_negative_marked_entries() {
18213        let mut sa = vec![1 | SUFFIX_GROUP_MARKER, -3, 5 | SUFFIX_GROUP_MARKER, -7];
18214        let n = sa.len() as FastSint;
18215
18216        let l = partial_sorting_gather_lms_suffixes_32s_4k(&mut sa, 0, n);
18217
18218        assert_eq!(l, 2);
18219        assert_eq!(sa[0], -1073741827);
18220        assert_eq!(sa[1], -1073741831);
18221    }
18222
18223    #[test]
18224    fn partial_sorting_gather_lms_suffixes_32s_1k_compacts_negative_marked_entries() {
18225        let mut sa = vec![1, -3, 5, -7];
18226        let n = sa.len() as FastSint;
18227
18228        let l = partial_sorting_gather_lms_suffixes_32s_1k(&mut sa, 0, n);
18229
18230        assert_eq!(l, 2);
18231        assert_eq!(sa[0], SAINT_MAX - 2);
18232        assert_eq!(sa[1], SAINT_MAX - 6);
18233    }
18234
18235    #[test]
18236    fn partial_sorting_gather_lms_suffixes_32s_4k_omp_wraps_sequential_version() {
18237        let mut sa = vec![1 | SUFFIX_GROUP_MARKER, -3, 5 | SUFFIX_GROUP_MARKER, -7];
18238        let mut thread_state = alloc_thread_state(2).unwrap();
18239
18240        partial_sorting_gather_lms_suffixes_32s_4k_omp(&mut sa, 4, 2, &mut thread_state);
18241
18242        assert_eq!(sa[0], -1073741827);
18243        assert_eq!(sa[1], -1073741831);
18244    }
18245
18246    #[test]
18247    fn partial_sorting_gather_lms_suffixes_32s_1k_omp_wraps_sequential_version() {
18248        let mut sa = vec![1, -3, 5, -7];
18249        let mut thread_state = alloc_thread_state(2).unwrap();
18250
18251        partial_sorting_gather_lms_suffixes_32s_1k_omp(&mut sa, 4, 2, &mut thread_state);
18252
18253        assert_eq!(sa[0], SAINT_MAX - 2);
18254        assert_eq!(sa[1], SAINT_MAX - 6);
18255    }
18256
18257    #[test]
18258    fn partial_sorting_gather_lms_suffixes_32s_omp_uses_block_partition() {
18259        let n = 65_600usize;
18260        let input_4k: Vec<SaSint> = (0..n)
18261            .map(|i| {
18262                let value = (i as SaSint) | SUFFIX_GROUP_MARKER;
18263                if i % 5 == 0 {
18264                    value | SAINT_MIN
18265                } else {
18266                    value
18267                }
18268            })
18269            .collect();
18270        let count_4k = input_4k.iter().filter(|&&value| value < 0).count();
18271
18272        let mut single = input_4k.clone();
18273        let mut threaded = input_4k;
18274        let mut thread_state = alloc_thread_state(4).unwrap();
18275        partial_sorting_gather_lms_suffixes_32s_4k_omp(&mut single, n as SaSint, 1, &mut []);
18276        partial_sorting_gather_lms_suffixes_32s_4k_omp(
18277            &mut threaded,
18278            n as SaSint,
18279            4,
18280            &mut thread_state,
18281        );
18282        assert_eq!(&threaded[..count_4k], &single[..count_4k]);
18283
18284        let input_1k: Vec<SaSint> = (0..n)
18285            .map(|i| {
18286                let value = i as SaSint;
18287                if i % 7 == 0 {
18288                    value | SAINT_MIN
18289                } else {
18290                    value
18291                }
18292            })
18293            .collect();
18294        let count_1k = input_1k.iter().filter(|&&value| value < 0).count();
18295
18296        let mut single = input_1k.clone();
18297        let mut threaded = input_1k;
18298        partial_sorting_gather_lms_suffixes_32s_1k_omp(&mut single, n as SaSint, 1, &mut []);
18299        partial_sorting_gather_lms_suffixes_32s_1k_omp(
18300            &mut threaded,
18301            n as SaSint,
18302            4,
18303            &mut thread_state,
18304        );
18305        assert_eq!(&threaded[..count_1k], &single[..count_1k]);
18306    }
18307
18308    #[test]
18309    fn renumber_lms_suffixes_8u_writes_names_into_second_half() {
18310        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
18311
18312        let name = renumber_lms_suffixes_8u(&mut sa, 2, 0, 0, 2);
18313
18314        assert_eq!(name, 1);
18315        assert_eq!(sa[2], SAINT_MIN);
18316        assert_eq!(sa[3], SAINT_MIN | 1);
18317    }
18318
18319    #[test]
18320    fn renumber_lms_suffixes_8u_matches_upstream_c_helper() {
18321        let mut sa_rust = vec![1 | SAINT_MIN, 3, 0, 0];
18322        let mut sa_c = sa_rust.clone();
18323
18324        let rust_name = renumber_lms_suffixes_8u(&mut sa_rust, 2, 0, 0, 2);
18325        let c_name = unsafe { probe_renumber_lms_suffixes_8u(sa_c.as_mut_ptr(), 2, 0, 0, 2) };
18326
18327        assert_eq!(rust_name, c_name);
18328        assert_eq!(sa_rust, sa_c);
18329    }
18330
18331    #[test]
18332    fn gather_marked_lms_suffixes_moves_negative_marked_entries_to_tail() {
18333        let mut sa = vec![0, 0, 1 | SAINT_MIN, 3];
18334
18335        let l = gather_marked_lms_suffixes(&mut sa, 2, 4, 0, 2);
18336
18337        assert_eq!(l, 3);
18338        assert_eq!(sa[3], 1);
18339    }
18340
18341    #[test]
18342    fn gather_marked_lms_suffixes_matches_upstream_c_helper() {
18343        let mut sa_rust = vec![0, 0, 1 | SAINT_MIN, 3];
18344        let mut sa_c = sa_rust.clone();
18345
18346        let rust_l = gather_marked_lms_suffixes(&mut sa_rust, 2, 4, 0, 2);
18347        let c_l = unsafe { probe_gather_marked_lms_suffixes(sa_c.as_mut_ptr(), 2, 4, 0, 2) };
18348
18349        assert_eq!(rust_l, c_l);
18350        assert_eq!(sa_rust, sa_c);
18351    }
18352
18353    #[test]
18354    fn renumber_lms_suffixes_8u_omp_wraps_sequential_version() {
18355        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
18356        let mut thread_state = alloc_thread_state(2).unwrap();
18357
18358        let name = renumber_lms_suffixes_8u_omp(&mut sa, 2, 2, &mut thread_state);
18359
18360        assert_eq!(name, 1);
18361        assert_eq!(sa[2], SAINT_MIN);
18362    }
18363
18364    #[test]
18365    fn renumber_lms_suffixes_8u_omp_uses_block_partition_for_large_inputs() {
18366        let m = 65_600usize;
18367        let mut input = vec![0; 2 * m];
18368        for (i, slot) in input[..m].iter_mut().enumerate() {
18369            let suffix = (2 * i + 1) as SaSint;
18370            *slot = if i % 5 == 0 {
18371                suffix | SAINT_MIN
18372            } else {
18373                suffix
18374            };
18375        }
18376
18377        let mut single = input.clone();
18378        let mut threaded = input;
18379        let mut thread_state = alloc_thread_state(4).unwrap();
18380        let single_name = renumber_lms_suffixes_8u(&mut single, m as SaSint, 0, 0, m as FastSint);
18381        let threaded_name =
18382            renumber_lms_suffixes_8u_omp(&mut threaded, m as SaSint, 4, &mut thread_state);
18383
18384        assert_eq!(threaded_name, single_name);
18385        assert_eq!(threaded, single);
18386    }
18387
18388    #[test]
18389    fn gather_marked_lms_suffixes_omp_uses_block_partition_for_large_inputs() {
18390        let n = 131_200usize;
18391        let half_n = n >> 1;
18392        let mut input = vec![-77; n];
18393        for (i, slot) in input[..half_n].iter_mut().enumerate() {
18394            let suffix = (3 * i + 1) as SaSint;
18395            *slot = if i % 7 == 0 {
18396                suffix | SAINT_MIN
18397            } else {
18398                suffix
18399            };
18400        }
18401        let marked_count = input[..half_n].iter().filter(|&&value| value < 0).count();
18402
18403        let mut single = input.clone();
18404        let mut threaded = input;
18405        let mut thread_state = alloc_thread_state(4).unwrap();
18406        let _ = gather_marked_lms_suffixes(&mut single, 0, n as FastSint, 0, half_n as FastSint);
18407        gather_marked_lms_suffixes_omp(&mut threaded, n as SaSint, 0, 0, 4, &mut thread_state);
18408
18409        assert_eq!(&threaded[n - marked_count..], &single[n - marked_count..]);
18410    }
18411
18412    #[test]
18413    fn renumber_and_gather_lms_suffixes_omp_uses_large_input_paths() {
18414        let m = 65_600usize;
18415        let n = 2 * m;
18416        let mut input = vec![0; n];
18417        for (i, slot) in input[..m].iter_mut().enumerate() {
18418            let suffix = (2 * i + 1) as SaSint;
18419            *slot = if i % 5 == 0 {
18420                suffix | SAINT_MIN
18421            } else {
18422                suffix
18423            };
18424        }
18425
18426        let mut single = input.clone();
18427        let mut threaded = input;
18428        let mut single_state = alloc_thread_state(1).unwrap();
18429        let mut threaded_state = alloc_thread_state(4).unwrap();
18430        let single_name = renumber_and_gather_lms_suffixes_omp(
18431            &mut single,
18432            n as SaSint,
18433            m as SaSint,
18434            0,
18435            1,
18436            &mut single_state,
18437        );
18438        let threaded_name = renumber_and_gather_lms_suffixes_omp(
18439            &mut threaded,
18440            n as SaSint,
18441            m as SaSint,
18442            0,
18443            4,
18444            &mut threaded_state,
18445        );
18446
18447        assert_eq!(threaded_name, single_name);
18448        assert_eq!(threaded, single);
18449    }
18450
18451    #[test]
18452    fn renumber_and_gather_lms_suffixes_omp_gathers_when_names_are_not_distinct() {
18453        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
18454        let mut thread_state = alloc_thread_state(2).unwrap();
18455
18456        let name = renumber_and_gather_lms_suffixes_omp(&mut sa, 4, 2, 0, 2, &mut thread_state);
18457
18458        assert_eq!(name, 1);
18459        assert_eq!(sa[3], 1);
18460    }
18461
18462    #[test]
18463    fn renumber_and_gather_lms_suffixes_omp_matches_upstream_c_helper() {
18464        let mut sa_rust = vec![1 | SAINT_MIN, 3, 0, 0];
18465        let mut sa_c = sa_rust.clone();
18466        let mut thread_state = alloc_thread_state(2).unwrap();
18467
18468        let rust_name =
18469            renumber_and_gather_lms_suffixes_omp(&mut sa_rust, 4, 2, 0, 2, &mut thread_state);
18470        let c_name =
18471            unsafe { probe_renumber_and_gather_lms_suffixes_omp(sa_c.as_mut_ptr(), 4, 2, 0, 2) };
18472
18473        assert_eq!(rust_name, c_name);
18474        assert_eq!(sa_rust, sa_c);
18475    }
18476
18477    #[test]
18478    fn renumber_distinct_lms_suffixes_32s_4k_masks_sources_and_writes_second_half() {
18479        let mut sa = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18480
18481        let name = renumber_distinct_lms_suffixes_32s_4k(&mut sa, 2, 1, 0, 2);
18482
18483        assert_eq!(name, 3);
18484        assert_eq!(sa[0], 1);
18485        assert_eq!(sa[1], 3);
18486        assert_eq!(sa[2], 1);
18487        assert_eq!(sa[3], 2 | SAINT_MIN);
18488    }
18489
18490    #[test]
18491    fn renumber_distinct_lms_suffixes_32s_4k_matches_upstream_c_helper() {
18492        let mut sa_rust = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18493        let mut sa_c = sa_rust.clone();
18494
18495        let rust_name = renumber_distinct_lms_suffixes_32s_4k(&mut sa_rust, 2, 1, 0, 2);
18496        let c_name =
18497            unsafe { probe_renumber_distinct_lms_suffixes_32s_4k(sa_c.as_mut_ptr(), 2, 1, 0, 2) };
18498
18499        assert_eq!(rust_name, c_name);
18500        assert_eq!(sa_rust, sa_c);
18501    }
18502
18503    #[test]
18504    fn mark_distinct_lms_suffixes_32s_propagates_previous_nonzero_marker() {
18505        let mut sa = vec![0, 0, SAINT_MIN | 5, 0, SAINT_MIN | 7];
18506
18507        mark_distinct_lms_suffixes_32s(&mut sa, 2, 0, 3);
18508
18509        assert_eq!(sa[2], 5);
18510        assert_eq!(sa[3], 0);
18511        assert_eq!(sa[4], SAINT_MIN | 7);
18512    }
18513
18514    #[test]
18515    fn clamp_lms_suffixes_length_32s_keeps_only_negative_lengths() {
18516        let mut sa = vec![0, 0, SAINT_MIN | 5, 7, SAINT_MIN | 3];
18517
18518        clamp_lms_suffixes_length_32s(&mut sa, 2, 0, 3);
18519
18520        assert_eq!(sa[2], 5);
18521        assert_eq!(sa[3], 0);
18522        assert_eq!(sa[4], 3);
18523    }
18524
18525    #[test]
18526    fn renumber_and_mark_distinct_lms_suffixes_32s_4k_omp_marks_second_half_when_names_repeat() {
18527        let mut sa = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18528        let mut thread_state = alloc_thread_state(2).unwrap();
18529
18530        let name =
18531            renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(&mut sa, 4, 2, 2, &mut thread_state);
18532
18533        assert_eq!(name, 2);
18534        assert_eq!(sa[2], 1);
18535        assert_eq!(sa[3], SAINT_MIN | 2);
18536    }
18537
18538    #[test]
18539    fn renumber_and_mark_distinct_lms_suffixes_32s_4k_omp_matches_upstream_c_helper() {
18540        let mut sa_rust = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18541        let mut sa_c = sa_rust.clone();
18542        let mut thread_state = alloc_thread_state(2).unwrap();
18543
18544        let rust_name = renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
18545            &mut sa_rust,
18546            4,
18547            2,
18548            2,
18549            &mut thread_state,
18550        );
18551        let c_name = unsafe {
18552            probe_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(sa_c.as_mut_ptr(), 4, 2, 2)
18553        };
18554
18555        assert_eq!(rust_name, c_name);
18556        assert_eq!(sa_rust, sa_c);
18557    }
18558
18559    #[test]
18560    fn reconstruct_lms_suffixes_maps_indices_from_tail_interval() {
18561        let mut sa = vec![0, 1, 2, 7, 11, 13];
18562
18563        reconstruct_lms_suffixes(&mut sa, 6, 3, 0, 3);
18564
18565        assert_eq!(&sa[..3], &[7, 11, 13]);
18566    }
18567
18568    #[test]
18569    fn reconstruct_lms_suffixes_omp_wraps_sequential_version() {
18570        let mut sa = vec![0, 1, 2, 7, 11, 13];
18571
18572        reconstruct_lms_suffixes_omp(&mut sa, 6, 3, 2);
18573
18574        assert_eq!(&sa[..3], &[7, 11, 13]);
18575    }
18576
18577    #[test]
18578    fn reconstruct_lms_suffixes_omp_uses_block_partition_for_large_inputs() {
18579        let m = 65_600usize;
18580        let n = 2 * m;
18581        let mut input = vec![0; n];
18582        for (i, slot) in input[..m].iter_mut().enumerate() {
18583            *slot = (m - 1 - i) as SaSint;
18584        }
18585        for (i, slot) in input[m..].iter_mut().enumerate() {
18586            *slot = (i * 17 + 3) as SaSint;
18587        }
18588
18589        let mut single = input.clone();
18590        let mut threaded = input;
18591        reconstruct_lms_suffixes(&mut single, n as SaSint, m as SaSint, 0, m as FastSint);
18592        reconstruct_lms_suffixes_omp(&mut threaded, n as SaSint, m as SaSint, 4);
18593
18594        assert_eq!(threaded, single);
18595    }
18596
18597    #[test]
18598    fn renumber_and_mark_distinct_lms_suffixes_32s_1k_omp_handles_single_lms_suffix() {
18599        let t = vec![2, 1, 0];
18600        let mut sa = vec![0; t.len()];
18601
18602        let name = renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(&t, &mut sa, 3, 1, 1);
18603
18604        assert_eq!(name, 1);
18605        assert_eq!(sa[1], SAINT_MIN | 1);
18606    }
18607
18608    #[test]
18609    fn libsais_main_32s_entry_matches_upstream_c_on_6k_branch() {
18610        assert_main_32s_entry_matches_upstream_c_for_branch(300);
18611    }
18612
18613    #[test]
18614    fn libsais_main_32s_entry_matches_upstream_c_on_4k_branch() {
18615        assert_main_32s_entry_matches_upstream_c_for_branch(400);
18616    }
18617
18618    #[test]
18619    fn libsais_main_32s_entry_matches_upstream_c_on_2k_branch() {
18620        assert_main_32s_entry_matches_upstream_c_for_branch(700);
18621    }
18622
18623    #[test]
18624    fn libsais_main_32s_entry_matches_upstream_c_on_1k_branch() {
18625        assert_main_32s_entry_matches_upstream_c_for_branch(1501);
18626    }
18627
18628    #[test]
18629    fn libsais_main_32s_entry_matches_upstream_c_on_recursive_repetitive_6k_case() {
18630        assert_main_32s_entry_matches_upstream_c(make_recursive_main_32s_text(24), 300, 0, true);
18631    }
18632
18633    #[test]
18634    fn libsais_main_32s_entry_matches_upstream_c_on_recursive_repetitive_1k_case() {
18635        assert_main_32s_entry_matches_upstream_c(make_recursive_main_32s_text(24), 1501, 0, true);
18636    }
18637
18638    #[test]
18639    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_6k_case() {
18640        assert_main_32s_entry_matches_upstream_c(
18641            make_large_main_32s_stress_text(1024, 300),
18642            300,
18643            0,
18644            true,
18645        );
18646    }
18647
18648    #[test]
18649    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_6k_case_with_fs() {
18650        assert_main_32s_entry_matches_upstream_c(
18651            make_large_main_32s_stress_text(1024, 300),
18652            300,
18653            2048,
18654            false,
18655        );
18656    }
18657
18658    #[test]
18659    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_4k_case() {
18660        assert_main_32s_entry_matches_upstream_c(
18661            make_large_main_32s_stress_text(1024, 400),
18662            400,
18663            0,
18664            true,
18665        );
18666    }
18667
18668    #[test]
18669    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_4k_case_with_fs() {
18670        assert_main_32s_entry_matches_upstream_c(
18671            make_large_main_32s_stress_text(1024, 400),
18672            400,
18673            2048,
18674            false,
18675        );
18676    }
18677
18678    #[test]
18679    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_2k_case() {
18680        assert_main_32s_entry_matches_upstream_c(
18681            make_large_main_32s_stress_text(1024, 700),
18682            700,
18683            0,
18684            true,
18685        );
18686    }
18687
18688    #[test]
18689    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_2k_case_with_fs() {
18690        assert_main_32s_entry_matches_upstream_c(
18691            make_large_main_32s_stress_text(1024, 700),
18692            700,
18693            2048,
18694            false,
18695        );
18696    }
18697
18698    #[test]
18699    fn libsais_main_32s_entry_matches_upstream_c_on_large_generated_1k_case_with_fs() {
18700        assert_main_32s_entry_matches_upstream_c(
18701            make_large_main_32s_stress_text(1024, 1501),
18702            1501,
18703            2048,
18704            false,
18705        );
18706    }
18707
18708    #[test]
18709    fn place_lms_suffixes_interval_32s_4k_moves_suffixes_into_bucket_intervals() {
18710        let mut sa = vec![10, 11, 12, 13, 14];
18711        let k = 3usize;
18712        let mut buckets = vec![0; 4 * k];
18713        buckets[buckets_index2(0, 1)] = 0;
18714        buckets[buckets_index2(1, 1)] = 2;
18715        buckets[buckets_index2(2, 1)] = 3;
18716        buckets[3 * k] = 2;
18717        buckets[3 * k + 1] = 5;
18718
18719        place_lms_suffixes_interval_32s_4k(&mut sa, 5, k as SaSint, 5, &buckets);
18720
18721        assert_eq!(sa, vec![0, 0, 0, 0, 14]);
18722    }
18723
18724    #[test]
18725    fn place_lms_suffixes_interval_32s_2k_moves_suffixes_into_bucket_intervals() {
18726        let mut sa = vec![10, 11, 12, 13, 14];
18727        let mut buckets = vec![0; 2 * 3];
18728        buckets[buckets_index2(0, 0)] = 2;
18729        buckets[buckets_index2(0, 1)] = 0;
18730        buckets[buckets_index2(1, 0)] = 5;
18731        buckets[buckets_index2(1, 1)] = 2;
18732        buckets[buckets_index2(2, 0)] = 5;
18733        buckets[buckets_index2(2, 1)] = 3;
18734
18735        place_lms_suffixes_interval_32s_2k(&mut sa, 5, 3, 5, &buckets);
18736
18737        assert_eq!(sa, vec![0, 0, 0, 0, 14]);
18738    }
18739
18740    #[test]
18741    fn place_lms_suffixes_interval_32s_1k_places_suffixes_by_symbol_bucket() {
18742        let t = vec![0, 1, 1, 2, 2];
18743        let mut sa = vec![1, 2, 3, 4, 99];
18744        let buckets = vec![0, 2, 5];
18745
18746        place_lms_suffixes_interval_32s_1k(&t, &mut sa, 3, 4, &buckets);
18747
18748        assert_eq!(sa, vec![1, 2, 0, 3, 4]);
18749    }
18750
18751    #[test]
18752    fn final_bwt_scan_left_to_right_8u_rewrites_sa_and_induces_suffixes() {
18753        let t = vec![0_u8, 1, 2, 1, 0];
18754        let mut sa = vec![1, 0, 0];
18755        let mut induction_bucket = vec![0, 1, 3];
18756
18757        final_bwt_scan_left_to_right_8u(&t, &mut sa, &mut induction_bucket, 0, 1);
18758
18759        assert_eq!(sa[0], 0);
18760        assert_eq!(induction_bucket[0], 1);
18761    }
18762
18763    #[test]
18764    fn final_bwt_aux_scan_left_to_right_8u_updates_sampling_array() {
18765        let t = vec![0_u8, 1, 2, 1, 0];
18766        let mut sa = vec![1, 0, 0];
18767        let mut induction_bucket = vec![0, 1, 3];
18768        let mut i_out = vec![0; 2];
18769
18770        final_bwt_aux_scan_left_to_right_8u(
18771            &t,
18772            &mut sa,
18773            0,
18774            &mut i_out,
18775            &mut induction_bucket,
18776            0,
18777            1,
18778        );
18779
18780        assert_eq!(i_out[0], 1);
18781    }
18782
18783    #[test]
18784    fn final_sorting_scan_left_to_right_8u_clears_marker_and_places_suffix() {
18785        let t = vec![0_u8, 1, 2, 1, 0];
18786        let mut sa = vec![1, 0, 0];
18787        let mut induction_bucket = vec![0, 1, 3];
18788
18789        final_sorting_scan_left_to_right_8u(&t, &mut sa, &mut induction_bucket, 0, 1);
18790
18791        assert_eq!(sa[0], 0);
18792        assert_eq!(induction_bucket[0], 1);
18793    }
18794
18795    #[test]
18796    fn final_sorting_scan_left_to_right_32s_clears_marker_and_places_suffix() {
18797        let t = vec![0, 1, 2, 1, 0];
18798        let mut sa = vec![1, 0, 0];
18799        let mut induction_bucket = vec![0, 1, 3];
18800
18801        final_sorting_scan_left_to_right_32s(&t, &mut sa, &mut induction_bucket, 0, 1);
18802
18803        assert_eq!(sa[0], 0);
18804        assert_eq!(induction_bucket[0], 1);
18805    }
18806
18807    #[test]
18808    fn final_bwt_scan_left_to_right_8u_block_prepare_records_cache_and_counts() {
18809        let t = vec![0_u8, 1, 2, 1, 0];
18810        let mut sa = vec![1, 2, 0];
18811        let mut buckets = vec![99; ALPHABET_SIZE];
18812        let mut cache = vec![ThreadCache::default(); 4];
18813
18814        let count = final_bwt_scan_left_to_right_8u_block_prepare(
18815            &t,
18816            &mut sa,
18817            ALPHABET_SIZE as SaSint,
18818            &mut buckets,
18819            &mut cache,
18820            0,
18821            2,
18822        );
18823
18824        assert_eq!(count, 2);
18825        assert_eq!(sa[0] & SAINT_MAX, 0);
18826        assert_eq!(sa[1], 1 | SAINT_MIN);
18827        assert_eq!(buckets[0], 1);
18828        assert_eq!(buckets[1], 1);
18829        assert_eq!(cache[0].symbol, 0);
18830        assert_eq!(cache[0].index & SAINT_MAX, 0);
18831        assert_eq!(cache[1].symbol, 1);
18832        assert_eq!(cache[1].index & SAINT_MAX, 1);
18833    }
18834
18835    #[test]
18836    fn final_sorting_scan_left_to_right_32s_block_omp_places_cached_suffixes() {
18837        let t = vec![0, 1, 2, 1, 0];
18838        let mut sa = vec![1, 2, 0, 0];
18839        let mut induction_bucket = vec![0, 1, 3];
18840        let mut cache = vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE];
18841
18842        final_sorting_scan_left_to_right_32s_block_omp(
18843            &t,
18844            &mut sa,
18845            &mut induction_bucket,
18846            &mut cache,
18847            0,
18848            2,
18849            2,
18850        );
18851
18852        assert_eq!(sa[0] & SAINT_MAX, 0);
18853        assert_eq!(sa[1] & SAINT_MAX, 1);
18854        assert_eq!(induction_bucket[0], 1);
18855        assert_eq!(induction_bucket[1], 2);
18856    }
18857
18858    #[test]
18859    fn final_sorting_scan_left_to_right_8u_omp_wraps_sequential_behavior() {
18860        let t = vec![0_u8, 1, 2, 1, 0];
18861        let mut sa = vec![0; t.len()];
18862        let mut induction_bucket = vec![0, 1, 3];
18863        let mut expected_sa = sa.clone();
18864        let mut expected_bucket = induction_bucket.clone();
18865
18866        final_sorting_scan_left_to_right_8u_omp(
18867            &t,
18868            &mut expected_sa,
18869            t.len() as FastSint,
18870            ALPHABET_SIZE as SaSint,
18871            &mut expected_bucket,
18872            1,
18873            &mut [],
18874        );
18875
18876        let mut thread_state = alloc_thread_state(2).unwrap();
18877
18878        final_sorting_scan_left_to_right_8u_omp(
18879            &t,
18880            &mut sa,
18881            t.len() as FastSint,
18882            ALPHABET_SIZE as SaSint,
18883            &mut induction_bucket,
18884            2,
18885            &mut thread_state,
18886        );
18887
18888        assert_eq!(sa, expected_sa);
18889        assert_eq!(induction_bucket, expected_bucket);
18890    }
18891
18892    #[test]
18893    fn final_sorting_scan_left_to_right_8u_block_omp_uses_thread_buckets() {
18894        let block_start = 20_000usize;
18895        let block_size = 16_384usize;
18896        let n = block_start + block_size + 8;
18897        let t = vec![1_u8; n];
18898        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18899
18900        let mut expected_sa = vec![0; n];
18901        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18902        let mut threaded_sa = expected_sa.clone();
18903        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18904        let mut threaded_bucket = expected_bucket.clone();
18905        let mut thread_state = alloc_thread_state(4).unwrap();
18906
18907        final_sorting_scan_left_to_right_8u(
18908            &t,
18909            &mut expected_sa,
18910            &mut expected_bucket,
18911            block_start as FastSint,
18912            block_size as FastSint,
18913        );
18914        final_sorting_scan_left_to_right_8u_block_omp(
18915            &t,
18916            &mut threaded_sa,
18917            ALPHABET_SIZE as SaSint,
18918            &mut threaded_bucket,
18919            block_start as FastSint,
18920            block_size as FastSint,
18921            4,
18922            &mut thread_state,
18923        );
18924
18925        assert_eq!(threaded_sa, expected_sa);
18926        assert_eq!(threaded_bucket, expected_bucket);
18927    }
18928
18929    #[test]
18930    fn final_bwt_left_to_right_8u_block_omp_uses_thread_buckets() {
18931        let block_start = 20_000usize;
18932        let block_size = 16_384usize;
18933        let n = block_start + block_size + 8;
18934        let t = vec![1_u8; n];
18935        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18936
18937        let mut expected_sa = vec![0; n];
18938        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18939        let mut threaded_sa = expected_sa.clone();
18940        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18941        let mut threaded_bucket = expected_bucket.clone();
18942        let mut thread_state = alloc_thread_state(4).unwrap();
18943
18944        final_bwt_scan_left_to_right_8u(
18945            &t,
18946            &mut expected_sa,
18947            &mut expected_bucket,
18948            block_start as FastSint,
18949            block_size as FastSint,
18950        );
18951        final_bwt_scan_left_to_right_8u_block_omp(
18952            &t,
18953            &mut threaded_sa,
18954            ALPHABET_SIZE as SaSint,
18955            &mut threaded_bucket,
18956            block_start as FastSint,
18957            block_size as FastSint,
18958            4,
18959            &mut thread_state,
18960        );
18961
18962        assert_eq!(threaded_sa, expected_sa);
18963        assert_eq!(threaded_bucket, expected_bucket);
18964    }
18965
18966    #[test]
18967    fn final_bwt_aux_left_to_right_8u_block_omp_uses_thread_buckets() {
18968        let block_start = 20_000usize;
18969        let block_size = 16_384usize;
18970        let n = block_start + block_size + 8;
18971        let t = vec![1_u8; n];
18972        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18973
18974        let mut expected_sa = vec![0; n];
18975        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18976        let mut threaded_sa = expected_sa.clone();
18977        let mut expected_i = vec![0; n];
18978        let mut threaded_i = vec![0; n];
18979        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18980        let mut threaded_bucket = expected_bucket.clone();
18981        let mut thread_state = alloc_thread_state(4).unwrap();
18982
18983        final_bwt_aux_scan_left_to_right_8u(
18984            &t,
18985            &mut expected_sa,
18986            0,
18987            &mut expected_i,
18988            &mut expected_bucket,
18989            block_start as FastSint,
18990            block_size as FastSint,
18991        );
18992        final_bwt_aux_scan_left_to_right_8u_block_omp(
18993            &t,
18994            &mut threaded_sa,
18995            ALPHABET_SIZE as SaSint,
18996            0,
18997            &mut threaded_i,
18998            &mut threaded_bucket,
18999            block_start as FastSint,
19000            block_size as FastSint,
19001            4,
19002            &mut thread_state,
19003        );
19004
19005        assert_eq!(threaded_sa, expected_sa);
19006        assert_eq!(threaded_i, expected_i);
19007        assert_eq!(threaded_bucket, expected_bucket);
19008    }
19009
19010    #[test]
19011    fn final_bwt_scan_right_to_left_8u_returns_zero_index_and_induces_suffixes() {
19012        let t = vec![0_u8, 1, 2, 1, 0];
19013        let mut sa = vec![0, 2, 0];
19014        let mut induction_bucket = vec![1, 2, 3];
19015
19016        let index = final_bwt_scan_right_to_left_8u(&t, &mut sa, &mut induction_bucket, 0, 2);
19017
19018        assert_eq!(index, 0);
19019        assert_eq!(sa[1], 1);
19020        assert_eq!(induction_bucket[1], 1);
19021    }
19022
19023    #[test]
19024    fn final_sorting_scan_right_to_left_32s_block_omp_runs_block_pipeline() {
19025        let t = vec![0, 1, 2, 1, 0];
19026        let mut sa = vec![0, 2, 0, 0];
19027        let mut induction_bucket = vec![1, 2, 3];
19028        let mut expected_sa = sa.clone();
19029        let mut expected_bucket = induction_bucket.clone();
19030        let mut cache = vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE];
19031
19032        final_sorting_scan_right_to_left_32s(&t, &mut expected_sa, &mut expected_bucket, 0, 2);
19033        final_sorting_scan_right_to_left_32s_block_omp(
19034            &t,
19035            &mut sa,
19036            &mut induction_bucket,
19037            &mut cache,
19038            0,
19039            2,
19040            2,
19041        );
19042
19043        assert_eq!(sa, expected_sa);
19044        assert_eq!(induction_bucket, expected_bucket);
19045    }
19046
19047    #[test]
19048    fn final_sorting_scan_right_to_left_8u_block_omp_uses_thread_buckets() {
19049        let block_start = 20_000usize;
19050        let block_size = 16_384usize;
19051        let n = block_start + block_size + 8;
19052        let t = vec![1_u8; n];
19053        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19054
19055        let mut expected_sa = vec![0; n];
19056        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19057        let mut threaded_sa = expected_sa.clone();
19058        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19059        expected_bucket[1] = n as SaSint;
19060        let mut threaded_bucket = expected_bucket.clone();
19061        let mut thread_state = alloc_thread_state(4).unwrap();
19062
19063        final_sorting_scan_right_to_left_8u(
19064            &t,
19065            &mut expected_sa,
19066            &mut expected_bucket,
19067            block_start as FastSint,
19068            block_size as FastSint,
19069        );
19070        final_sorting_scan_right_to_left_8u_block_omp(
19071            &t,
19072            &mut threaded_sa,
19073            ALPHABET_SIZE as SaSint,
19074            &mut threaded_bucket,
19075            block_start as FastSint,
19076            block_size as FastSint,
19077            4,
19078            &mut thread_state,
19079        );
19080
19081        assert_eq!(threaded_sa, expected_sa);
19082        assert_eq!(threaded_bucket, expected_bucket);
19083    }
19084
19085    #[test]
19086    fn final_bwt_right_to_left_8u_block_omp_uses_thread_buckets() {
19087        let block_start = 20_000usize;
19088        let block_size = 16_384usize;
19089        let n = block_start + block_size + 8;
19090        let t = vec![1_u8; n];
19091        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19092
19093        let mut expected_sa = vec![0; n];
19094        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19095        let mut threaded_sa = expected_sa.clone();
19096        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19097        expected_bucket[1] = n as SaSint;
19098        let mut threaded_bucket = expected_bucket.clone();
19099        let mut thread_state = alloc_thread_state(4).unwrap();
19100
19101        final_bwt_scan_right_to_left_8u(
19102            &t,
19103            &mut expected_sa,
19104            &mut expected_bucket,
19105            block_start as FastSint,
19106            block_size as FastSint,
19107        );
19108        final_bwt_scan_right_to_left_8u_block_omp(
19109            &t,
19110            &mut threaded_sa,
19111            ALPHABET_SIZE as SaSint,
19112            &mut threaded_bucket,
19113            block_start as FastSint,
19114            block_size as FastSint,
19115            4,
19116            &mut thread_state,
19117        );
19118
19119        assert_eq!(threaded_sa, expected_sa);
19120        assert_eq!(threaded_bucket, expected_bucket);
19121    }
19122
19123    #[test]
19124    fn final_bwt_aux_right_to_left_8u_block_omp_uses_thread_buckets() {
19125        let block_start = 20_000usize;
19126        let block_size = 16_384usize;
19127        let n = block_start + block_size + 8;
19128        let t = vec![1_u8; n];
19129        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19130
19131        let mut expected_sa = vec![0; n];
19132        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19133        let mut threaded_sa = expected_sa.clone();
19134        let mut expected_i = vec![0; n];
19135        let mut threaded_i = vec![0; n];
19136        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19137        expected_bucket[1] = n as SaSint;
19138        let mut threaded_bucket = expected_bucket.clone();
19139        let mut thread_state = alloc_thread_state(4).unwrap();
19140
19141        final_bwt_aux_scan_right_to_left_8u(
19142            &t,
19143            &mut expected_sa,
19144            0,
19145            &mut expected_i,
19146            &mut expected_bucket,
19147            block_start as FastSint,
19148            block_size as FastSint,
19149        );
19150        final_bwt_aux_scan_right_to_left_8u_block_omp(
19151            &t,
19152            &mut threaded_sa,
19153            ALPHABET_SIZE as SaSint,
19154            0,
19155            &mut threaded_i,
19156            &mut threaded_bucket,
19157            block_start as FastSint,
19158            block_size as FastSint,
19159            4,
19160            &mut thread_state,
19161        );
19162
19163        assert_eq!(threaded_sa, expected_sa);
19164        assert_eq!(threaded_i, expected_i);
19165        assert_eq!(threaded_bucket, expected_bucket);
19166    }
19167
19168    #[test]
19169    fn final_gsa_right_to_left_8u_block_omp_uses_thread_buckets() {
19170        let block_start = 20_000usize;
19171        let block_size = 16_384usize;
19172        let n = block_start + block_size + 8;
19173        let t = vec![1_u8; n];
19174        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19175
19176        let mut expected_sa = vec![0; n];
19177        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19178        let mut threaded_sa = expected_sa.clone();
19179        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19180        expected_bucket[1] = n as SaSint;
19181        let mut threaded_bucket = expected_bucket.clone();
19182        let mut thread_state = alloc_thread_state(4).unwrap();
19183
19184        final_gsa_scan_right_to_left_8u(
19185            &t,
19186            &mut expected_sa,
19187            &mut expected_bucket,
19188            block_start as FastSint,
19189            block_size as FastSint,
19190        );
19191        final_gsa_scan_right_to_left_8u_block_omp(
19192            &t,
19193            &mut threaded_sa,
19194            ALPHABET_SIZE as SaSint,
19195            &mut threaded_bucket,
19196            block_start as FastSint,
19197            block_size as FastSint,
19198            4,
19199            &mut thread_state,
19200        );
19201
19202        assert_eq!(threaded_sa, expected_sa);
19203        assert_eq!(threaded_bucket, expected_bucket);
19204    }
19205
19206    #[test]
19207    fn final_sorting_scan_right_to_left_8u_omp_matches_sequential_path() {
19208        let t = vec![0_u8, 1, 2, 1, 0];
19209        let mut sa = vec![0, 2, 0, 0];
19210        let mut induction_bucket = vec![1, 2, 3];
19211        let mut expected_sa = sa.clone();
19212        let mut expected_bucket = induction_bucket.clone();
19213
19214        final_sorting_scan_right_to_left_8u_omp(
19215            &t,
19216            &mut expected_sa,
19217            0,
19218            2,
19219            ALPHABET_SIZE as SaSint,
19220            &mut expected_bucket,
19221            1,
19222            &mut [],
19223        );
19224
19225        let mut thread_state = alloc_thread_state(2).unwrap();
19226        final_sorting_scan_right_to_left_8u_omp(
19227            &t,
19228            &mut sa,
19229            0,
19230            2,
19231            ALPHABET_SIZE as SaSint,
19232            &mut induction_bucket,
19233            2,
19234            &mut thread_state,
19235        );
19236
19237        assert_eq!(sa, expected_sa);
19238        assert_eq!(induction_bucket, expected_bucket);
19239    }
19240
19241    #[test]
19242    fn clear_lms_suffixes_omp_zeroes_requested_bucket_ranges() {
19243        let mut sa = vec![5, 4, 3, 2, 1, 9];
19244        let n = sa.len() as SaSint;
19245        let bucket_start = vec![1, 4, 5];
19246        let bucket_end = vec![3, 5, 5];
19247
19248        clear_lms_suffixes_omp(&mut sa, n, 3, &bucket_start, &bucket_end, 2);
19249
19250        assert_eq!(sa, vec![5, 0, 0, 2, 0, 9]);
19251    }
19252
19253    #[test]
19254    fn induce_final_order_8u_omp_non_bwt_matches_direct_final_scans() {
19255        let t = vec![0_u8, 1, 2, 1, 0];
19256        let mut sa = vec![0, 2, 0, 0, 0];
19257        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
19258        buckets[6 * ALPHABET_SIZE..6 * ALPHABET_SIZE + 3].copy_from_slice(&[0, 1, 3]);
19259        buckets[7 * ALPHABET_SIZE..7 * ALPHABET_SIZE + 3].copy_from_slice(&[2, 4, 5]);
19260
19261        let mut expected_sa = sa.clone();
19262        let mut expected_left = vec![0, 1, 3];
19263        let mut expected_right = vec![2, 4, 5];
19264        final_sorting_scan_left_to_right_8u_omp(
19265            &t,
19266            &mut expected_sa,
19267            t.len() as FastSint,
19268            ALPHABET_SIZE as SaSint,
19269            &mut expected_left,
19270            1,
19271            &mut [],
19272        );
19273        final_sorting_scan_right_to_left_8u_omp(
19274            &t,
19275            &mut expected_sa,
19276            0,
19277            t.len() as FastSint,
19278            ALPHABET_SIZE as SaSint,
19279            &mut expected_right,
19280            1,
19281            &mut [],
19282        );
19283
19284        let mut thread_state = alloc_thread_state(2).unwrap();
19285        let result = induce_final_order_8u_omp(
19286            &t,
19287            &mut sa,
19288            t.len() as SaSint,
19289            ALPHABET_SIZE as SaSint,
19290            LIBSAIS_FLAGS_NONE,
19291            0,
19292            None,
19293            &mut buckets,
19294            2,
19295            &mut thread_state,
19296        );
19297
19298        assert_eq!(result, 0);
19299        assert_eq!(sa, expected_sa);
19300        assert_eq!(
19301            &buckets[6 * ALPHABET_SIZE..6 * ALPHABET_SIZE + 3],
19302            expected_left.as_slice()
19303        );
19304        assert_eq!(
19305            &buckets[7 * ALPHABET_SIZE..7 * ALPHABET_SIZE + 3],
19306            expected_right.as_slice()
19307        );
19308    }
19309
19310    #[test]
19311    fn renumber_unique_and_nonunique_lms_suffixes_32s_marks_new_unique_names() {
19312        let mut t = vec![0, 0, 0, 0];
19313        let mut sa = vec![0, 2, -1, 5];
19314
19315        let f = renumber_unique_and_nonunique_lms_suffixes_32s(&mut t, &mut sa, 2, 0, 0, 2);
19316
19317        assert_eq!(f, 1);
19318        assert_eq!(t[0], SAINT_MIN);
19319        assert_eq!(sa[2], SAINT_MIN);
19320        assert_eq!(sa[3], 4);
19321    }
19322
19323    #[test]
19324    fn renumber_unique_and_nonunique_lms_suffixes_32s_matches_upstream_c_helper() {
19325        let mut t_rust = vec![0, 0, 0, 0];
19326        let mut sa_rust = vec![0, 2, -1, 5];
19327        let mut t_c = t_rust.clone();
19328        let mut sa_c = sa_rust.clone();
19329
19330        let rust_f =
19331            renumber_unique_and_nonunique_lms_suffixes_32s(&mut t_rust, &mut sa_rust, 2, 0, 0, 2);
19332        let c_f = unsafe {
19333            probe_renumber_unique_and_nonunique_lms_suffixes_32s(
19334                t_c.as_mut_ptr(),
19335                sa_c.as_mut_ptr(),
19336                2,
19337                0,
19338                0,
19339                2,
19340            )
19341        };
19342
19343        assert_eq!(rust_f, c_f);
19344        assert_eq!(t_rust, t_c);
19345        assert_eq!(sa_rust, sa_c);
19346    }
19347
19348    #[test]
19349    fn renumber_unique_and_nonunique_lms_suffixes_32s_omp_matches_upstream_c_helper() {
19350        let mut t_rust = vec![0, 0, 0, 0];
19351        let mut sa_rust = vec![0, 2, -1, 5];
19352        let mut t_c = t_rust.clone();
19353        let mut sa_c = sa_rust.clone();
19354        let mut thread_state = alloc_thread_state(1).unwrap();
19355
19356        let rust_f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(
19357            &mut t_rust,
19358            &mut sa_rust,
19359            2,
19360            1,
19361            &mut thread_state,
19362        );
19363        let c_f = unsafe {
19364            probe_renumber_unique_and_nonunique_lms_suffixes_32s_omp(
19365                t_c.as_mut_ptr(),
19366                sa_c.as_mut_ptr(),
19367                2,
19368                1,
19369            )
19370        };
19371
19372        assert_eq!(rust_f, c_f);
19373        assert_eq!(t_rust, t_c);
19374        assert_eq!(sa_rust, sa_c);
19375    }
19376
19377    #[test]
19378    fn renumber_unique_and_nonunique_lms_suffixes_32s_omp_uses_block_partition() {
19379        let m = 65_600usize;
19380        let n = 2 * m;
19381        let t = vec![0; n];
19382        let mut sa = vec![0; n];
19383        for i in 0..m {
19384            sa[i] = (2 * i) as SaSint;
19385            sa[m + i] = if i % 5 == 0 {
19386                -((i as SaSint) + 1)
19387            } else {
19388                i as SaSint + 7
19389            };
19390        }
19391
19392        let mut single_t = t.clone();
19393        let mut single_sa = sa.clone();
19394        let mut threaded_t = t;
19395        let mut threaded_sa = sa;
19396        let mut thread_state = alloc_thread_state(4).unwrap();
19397        let single_f = renumber_unique_and_nonunique_lms_suffixes_32s(
19398            &mut single_t,
19399            &mut single_sa,
19400            m as SaSint,
19401            0,
19402            0,
19403            m as FastSint,
19404        );
19405        let threaded_f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(
19406            &mut threaded_t,
19407            &mut threaded_sa,
19408            m as SaSint,
19409            4,
19410            &mut thread_state,
19411        );
19412
19413        assert_eq!(threaded_f, single_f);
19414        assert_eq!(threaded_t, single_t);
19415        assert_eq!(threaded_sa, single_sa);
19416    }
19417
19418    #[test]
19419    fn compact_unique_and_nonunique_lms_suffixes_32s_splits_unique_and_nonunique_ranges() {
19420        let mut sa = vec![0, 0, 0, 0, SAINT_MIN, 4];
19421        let mut l = 2;
19422        let mut r = 6;
19423
19424        compact_unique_and_nonunique_lms_suffixes_32s(&mut sa, 2, &mut l, &mut r, 0, 2);
19425
19426        assert_eq!(l, 2);
19427        assert_eq!(r, 6);
19428        assert_eq!(sa[2], 0);
19429        assert_eq!(sa[3] & SAINT_MAX, 0);
19430    }
19431
19432    #[test]
19433    fn compact_lms_suffixes_32s_omp_runs_renumber_then_compaction() {
19434        let mut t = vec![0, 0, 0, 0];
19435        let mut sa = vec![0, 2, -1, 5, 77, 88];
19436        let mut thread_state = alloc_thread_state(2).unwrap();
19437
19438        let f = compact_lms_suffixes_32s_omp(&mut t, &mut sa, 4, 2, 2, 2, &mut thread_state);
19439
19440        assert_eq!(f, 1);
19441        assert_eq!(sa[2] & SAINT_MAX, 0);
19442        assert_eq!(sa[5], 3);
19443    }
19444
19445    #[test]
19446    fn compact_unique_and_nonunique_lms_suffixes_32s_omp_uses_block_partition() {
19447        let n = 131_200usize;
19448        let m = 65_600usize;
19449        let fs = m + 32;
19450        let half_n = n >> 1;
19451        let f = m / 5;
19452        let mut sa = vec![0; n + fs];
19453        for i in 0..half_n {
19454            sa[m + i] = if i % 5 == 0 {
19455                SAINT_MIN | i as SaSint
19456            } else {
19457                i as SaSint + 1
19458            };
19459        }
19460        for i in 0..f {
19461            sa[m - f + i] = (10_000 + i) as SaSint;
19462        }
19463
19464        let mut single = sa.clone();
19465        let mut threaded = sa;
19466        let mut single_state = alloc_thread_state(1).unwrap();
19467        let mut threaded_state = alloc_thread_state(4).unwrap();
19468        compact_unique_and_nonunique_lms_suffixes_32s_omp(
19469            &mut single,
19470            n as SaSint,
19471            m as SaSint,
19472            fs as SaSint,
19473            f as SaSint,
19474            1,
19475            &mut single_state,
19476        );
19477        compact_unique_and_nonunique_lms_suffixes_32s_omp(
19478            &mut threaded,
19479            n as SaSint,
19480            m as SaSint,
19481            fs as SaSint,
19482            f as SaSint,
19483            4,
19484            &mut threaded_state,
19485        );
19486
19487        let unique_dst = n + fs - m;
19488        assert_eq!(
19489            &threaded[unique_dst..unique_dst + f],
19490            &single[unique_dst..unique_dst + f]
19491        );
19492    }
19493
19494    #[test]
19495    fn compact_lms_suffixes_32s_omp_uses_large_input_paths() {
19496        let n = 131_200usize;
19497        let m = 65_600usize;
19498        let fs = m + 32;
19499        let t = vec![0; n];
19500        let mut sa = vec![0; n + fs];
19501        for i in 0..m {
19502            sa[i] = (2 * i) as SaSint;
19503            sa[m + i] = if i % 5 == 0 {
19504                -((i as SaSint) + 1)
19505            } else {
19506                i as SaSint + 7
19507            };
19508        }
19509
19510        let mut single_t = t.clone();
19511        let mut single_sa = sa.clone();
19512        let mut threaded_t = t;
19513        let mut threaded_sa = sa;
19514        let mut single_state = alloc_thread_state(1).unwrap();
19515        let mut threaded_state = alloc_thread_state(4).unwrap();
19516        let single_f = compact_lms_suffixes_32s_omp(
19517            &mut single_t,
19518            &mut single_sa,
19519            n as SaSint,
19520            m as SaSint,
19521            fs as SaSint,
19522            1,
19523            &mut single_state,
19524        );
19525        let threaded_f = compact_lms_suffixes_32s_omp(
19526            &mut threaded_t,
19527            &mut threaded_sa,
19528            n as SaSint,
19529            m as SaSint,
19530            fs as SaSint,
19531            4,
19532            &mut threaded_state,
19533        );
19534
19535        assert_eq!(threaded_f, single_f);
19536        assert_eq!(threaded_t, single_t);
19537        let unique_dst = n + fs - m;
19538        let unique_len = usize::try_from(threaded_f).expect("f must be non-negative");
19539        assert_eq!(
19540            &threaded_sa[unique_dst..unique_dst + unique_len],
19541            &single_sa[unique_dst..unique_dst + unique_len]
19542        );
19543    }
19544
19545    #[test]
19546    fn merge_unique_lms_suffixes_32s_noops_for_empty_block() {
19547        let mut t = vec![1, SAINT_MIN, 2, SAINT_MIN];
19548        let mut sa = vec![0, 0, 1, 3];
19549        let before_t = t.clone();
19550        let before_sa = sa.clone();
19551
19552        merge_unique_lms_suffixes_32s(&mut t, &mut sa, 4, 1, 0, 0, 0);
19553
19554        assert_eq!(t, before_t);
19555        assert_eq!(sa, before_sa);
19556    }
19557
19558    #[test]
19559    fn merge_nonunique_lms_suffixes_32s_noops_for_empty_block() {
19560        let mut sa = vec![0, 7, 0, 13, 11];
19561        let before = sa.clone();
19562
19563        merge_nonunique_lms_suffixes_32s(&mut sa, 4, 1, 0, 0, 0);
19564
19565        assert_eq!(sa, before);
19566    }
19567
19568    #[test]
19569    fn merge_compacted_lms_suffixes_32s_omp_preserves_input_text_and_fills_zero_slots() {
19570        let mut t = vec![1, 2, 3, 4];
19571        let mut sa = vec![0, 1, 2, 3, 4, 5];
19572        let before_t = t.clone();
19573        let mut thread_state = alloc_thread_state(2).unwrap();
19574
19575        merge_compacted_lms_suffixes_32s_omp(&mut t, &mut sa, 4, 1, 1, 2, &mut thread_state);
19576
19577        assert_eq!(t, before_t);
19578        assert_eq!(sa[0], 3);
19579        assert_eq!(sa[1], 1);
19580    }
19581
19582    #[test]
19583    fn merge_unique_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
19584        let n = 65_600usize;
19585        let m = 1_024usize;
19586        let mut t = vec![1; n];
19587        for i in (0..n).step_by(257) {
19588            t[i] = SAINT_MIN | ((i % 251) as SaSint);
19589        }
19590        let f = t.iter().filter(|&&value| value < 0).count();
19591        let mut sa = vec![-1; n];
19592        let src = n - m - 1;
19593        for i in 0..f {
19594            sa[src + i] = i as SaSint;
19595        }
19596
19597        let mut single_t = t.clone();
19598        let mut single_sa = sa.clone();
19599        let mut threaded_t = t;
19600        let mut threaded_sa = sa;
19601        let mut thread_state = alloc_thread_state(4).unwrap();
19602        merge_unique_lms_suffixes_32s_omp(
19603            &mut single_t,
19604            &mut single_sa,
19605            n as SaSint,
19606            m as SaSint,
19607            1,
19608            &mut [],
19609        );
19610        merge_unique_lms_suffixes_32s_omp(
19611            &mut threaded_t,
19612            &mut threaded_sa,
19613            n as SaSint,
19614            m as SaSint,
19615            4,
19616            &mut thread_state,
19617        );
19618
19619        assert_eq!(threaded_t, single_t);
19620        assert_eq!(threaded_sa, single_sa);
19621    }
19622
19623    #[test]
19624    fn merge_nonunique_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
19625        let n = 131_200usize;
19626        let m = 65_600usize;
19627        let f = 7usize;
19628        let mut sa = vec![1; n];
19629        let zero_count = (0..m).filter(|i| i % 17 == 0).count();
19630        for i in (0..m).step_by(17) {
19631            sa[i] = 0;
19632        }
19633        let src = n - m - 1 + f;
19634        for i in 0..zero_count {
19635            sa[src + i] = 10_000 + i as SaSint;
19636        }
19637
19638        let mut single = sa.clone();
19639        let mut threaded = sa;
19640        let mut thread_state = alloc_thread_state(4).unwrap();
19641        merge_nonunique_lms_suffixes_32s_omp(
19642            &mut single,
19643            n as SaSint,
19644            m as SaSint,
19645            f as SaSint,
19646            1,
19647            &mut [],
19648        );
19649        merge_nonunique_lms_suffixes_32s_omp(
19650            &mut threaded,
19651            n as SaSint,
19652            m as SaSint,
19653            f as SaSint,
19654            4,
19655            &mut thread_state,
19656        );
19657
19658        assert_eq!(threaded, single);
19659    }
19660
19661    #[test]
19662    fn merge_compacted_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
19663        let n = 131_200usize;
19664        let m = 65_600usize;
19665        let mut t = vec![1; n];
19666        for i in (0..n).step_by(257) {
19667            t[i] = SAINT_MIN | ((i % 251) as SaSint);
19668        }
19669        let f = t.iter().filter(|&&value| value < 0).count();
19670
19671        let mut sa = vec![1; n];
19672        let zero_count = (0..m).filter(|i| i % 17 == 0).count();
19673        for i in (0..m).step_by(17) {
19674            sa[i] = 0;
19675        }
19676        let unique_src = n - m - 1;
19677        for i in 0..f {
19678            sa[unique_src + i] = i as SaSint;
19679        }
19680        for i in 0..zero_count {
19681            sa[unique_src + f + i] = 10_000 + i as SaSint;
19682        }
19683
19684        let mut single_t = t.clone();
19685        let mut single_sa = sa.clone();
19686        let mut threaded_t = t;
19687        let mut threaded_sa = sa;
19688        let mut single_state = alloc_thread_state(1).unwrap();
19689        let mut threaded_state = alloc_thread_state(4).unwrap();
19690        merge_compacted_lms_suffixes_32s_omp(
19691            &mut single_t,
19692            &mut single_sa,
19693            n as SaSint,
19694            m as SaSint,
19695            f as SaSint,
19696            1,
19697            &mut single_state,
19698        );
19699        merge_compacted_lms_suffixes_32s_omp(
19700            &mut threaded_t,
19701            &mut threaded_sa,
19702            n as SaSint,
19703            m as SaSint,
19704            f as SaSint,
19705            4,
19706            &mut threaded_state,
19707        );
19708
19709        assert_eq!(threaded_t, single_t);
19710        assert_eq!(threaded_sa, single_sa);
19711    }
19712
19713    #[test]
19714    fn bwt_copy_8u_copies_low_bytes_from_suffix_array_storage() {
19715        let a = vec![65, 255, 256, -1];
19716        let mut u = vec![0_u8; 4];
19717
19718        bwt_copy_8u(&mut u, &a, 4);
19719
19720        assert_eq!(u, vec![65, 255, 0, 255]);
19721    }
19722
19723    #[test]
19724    fn bwt_copy_8u_omp_matches_sequential_copy() {
19725        let a = vec![1, 2, 3, 4, 5];
19726        let mut u = vec![0_u8; 5];
19727
19728        bwt_copy_8u_omp(&mut u, &a, 5, 4);
19729
19730        assert_eq!(u, vec![1, 2, 3, 4, 5]);
19731    }
19732
19733    #[test]
19734    fn bwt_copy_8u_omp_uses_block_partition_for_large_inputs() {
19735        let n = 65_600usize;
19736        let a: Vec<SaSint> = (0..n).map(|i| (i * 17) as SaSint).collect();
19737        let mut threaded = vec![0; n];
19738        let mut sequential = vec![0; n];
19739
19740        bwt_copy_8u_omp(&mut threaded, &a, n as SaSint, 4);
19741        bwt_copy_8u(&mut sequential, &a, n as SaSint);
19742
19743        assert_eq!(threaded, sequential);
19744    }
19745
19746    #[test]
19747    fn plcp_lcp_omp_wrappers_match_single_thread_on_large_inputs() {
19748        let n = 65_600usize;
19749        let text: Vec<u8> = (0..n).map(|i| (1 + (i % 251)) as u8).collect();
19750        let sa: Vec<SaSint> = (0..n as SaSint).collect();
19751
19752        let mut plcp_single = vec![0; n];
19753        let mut plcp_threaded = vec![0; n];
19754        compute_phi_omp(&sa, &mut plcp_single, n as SaSint, 1);
19755        compute_phi_omp(&sa, &mut plcp_threaded, n as SaSint, 4);
19756        assert_eq!(plcp_threaded, plcp_single);
19757
19758        compute_plcp_omp(&text, &mut plcp_single, n as SaSint, 1);
19759        compute_plcp_omp(&text, &mut plcp_threaded, n as SaSint, 4);
19760        assert_eq!(plcp_threaded, plcp_single);
19761
19762        let mut lcp_single = vec![0; n];
19763        let mut lcp_threaded = vec![0; n];
19764        compute_lcp_omp(&plcp_single, &sa, &mut lcp_single, n as SaSint, 1);
19765        compute_lcp_omp(&plcp_threaded, &sa, &mut lcp_threaded, n as SaSint, 4);
19766        assert_eq!(lcp_threaded, lcp_single);
19767    }
19768
19769    #[test]
19770    fn count_and_gather_lms_suffixes_8u_omp_preserves_sequential_wrapper_behavior() {
19771        let t = vec![2_u8, 1, 3, 1, 0];
19772        let mut sa = vec![0; t.len()];
19773        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
19774        let mut thread_state = alloc_thread_state(2).unwrap();
19775        let m = count_and_gather_lms_suffixes_8u_omp(
19776            &t,
19777            &mut sa,
19778            t.len() as SaSint,
19779            &mut buckets,
19780            2,
19781            &mut thread_state,
19782        );
19783        assert_eq!(m, 1);
19784        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19785    }
19786
19787    #[test]
19788    fn count_and_gather_lms_suffixes_8u_omp_uses_block_partition_for_large_inputs() {
19789        let n = 65_600usize;
19790        let text: Vec<u8> = (0..n)
19791            .map(|i| 1 + ((i * 37 + i / 17) % 251) as u8)
19792            .collect();
19793
19794        let mut sa_threaded = vec![-99; n];
19795        let mut sa_scalar = vec![-99; n];
19796        let mut buckets_threaded = vec![0; 4 * ALPHABET_SIZE];
19797        let mut buckets_scalar = vec![0; 4 * ALPHABET_SIZE];
19798        let mut thread_state = alloc_thread_state(4).unwrap();
19799
19800        let m_threaded = count_and_gather_lms_suffixes_8u_omp(
19801            &text,
19802            &mut sa_threaded,
19803            n as SaSint,
19804            &mut buckets_threaded,
19805            4,
19806            &mut thread_state,
19807        );
19808        let m_scalar = count_and_gather_lms_suffixes_8u(
19809            &text,
19810            &mut sa_scalar,
19811            n as SaSint,
19812            &mut buckets_scalar,
19813            0,
19814            n as FastSint,
19815        );
19816
19817        assert_eq!(m_threaded, m_scalar);
19818        assert_eq!(
19819            &sa_threaded[n - m_threaded as usize..],
19820            &sa_scalar[n - m_scalar as usize..]
19821        );
19822        assert_eq!(buckets_threaded, buckets_scalar);
19823    }
19824
19825    #[test]
19826    fn gather_lms_suffixes_8u_omp_uses_thread_state_for_large_inputs() {
19827        let n = 65_600usize;
19828        let text: Vec<u8> = (0..n)
19829            .map(|i| 1 + ((i * 37 + i / 17) % 251) as u8)
19830            .collect();
19831        let mut thread_state = alloc_thread_state(4).unwrap();
19832        let mut count_sa = vec![-99; n];
19833        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
19834        let m = count_and_gather_lms_suffixes_8u_omp(
19835            &text,
19836            &mut count_sa,
19837            n as SaSint,
19838            &mut buckets,
19839            4,
19840            &mut thread_state,
19841        );
19842
19843        let mut threaded = vec![-99; n];
19844        let mut scalar = vec![-99; n];
19845        gather_lms_suffixes_8u_omp(&text, &mut threaded, n as SaSint, 4, &mut thread_state);
19846        gather_lms_suffixes_8u(
19847            &text,
19848            &mut scalar,
19849            n as SaSint,
19850            n as FastSint - 1,
19851            0,
19852            n as FastSint,
19853        );
19854
19855        assert_eq!(&threaded[n - m as usize..], &scalar[n - m as usize..]);
19856    }
19857
19858    #[test]
19859    fn count_and_gather_lms_suffixes_32s_4k_updates_counts_and_suffixes() {
19860        let t = vec![2, 1, 3, 1, 0];
19861        let mut sa = vec![0; t.len()];
19862        let mut buckets = vec![0; 4 * 4];
19863        let m = count_and_gather_lms_suffixes_32s_4k(
19864            &t,
19865            &mut sa,
19866            t.len() as SaSint,
19867            4,
19868            &mut buckets,
19869            0,
19870            t.len() as FastSint,
19871        );
19872        assert!(m >= 0);
19873        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19874    }
19875
19876    #[test]
19877    fn count_and_gather_lms_suffixes_32s_2k_updates_counts_and_suffixes() {
19878        let t = vec![2, 1, 3, 1, 0];
19879        let mut sa = vec![0; t.len()];
19880        let mut buckets = vec![0; 2 * 4];
19881        let m = count_and_gather_lms_suffixes_32s_2k(
19882            &t,
19883            &mut sa,
19884            t.len() as SaSint,
19885            4,
19886            &mut buckets,
19887            0,
19888            t.len() as FastSint,
19889        );
19890        assert!(m >= 0);
19891        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19892    }
19893
19894    #[test]
19895    fn count_and_gather_compacted_lms_suffixes_32s_2k_updates_counts_and_suffixes() {
19896        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
19897        let mut sa = vec![0; t.len()];
19898        let mut buckets = vec![0; 2 * 4];
19899        let m = count_and_gather_compacted_lms_suffixes_32s_2k(
19900            &t,
19901            &mut sa,
19902            t.len() as SaSint,
19903            4,
19904            &mut buckets,
19905            0,
19906            t.len() as FastSint,
19907        );
19908        assert!(m >= 0);
19909        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19910    }
19911
19912    #[test]
19913    fn count_and_gather_lms_suffixes_32s_4k_nofs_omp_wraps_sequential_version() {
19914        let t = vec![2, 1, 3, 1, 0];
19915        let mut sa = vec![0; t.len()];
19916        let mut buckets = vec![0; 4 * 4];
19917        let m = count_and_gather_lms_suffixes_32s_4k_nofs_omp(
19918            &t,
19919            &mut sa,
19920            t.len() as SaSint,
19921            4,
19922            &mut buckets,
19923            2,
19924        );
19925        assert!(m >= 0);
19926        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19927    }
19928
19929    #[test]
19930    fn count_and_gather_lms_suffixes_32s_2k_nofs_omp_wraps_sequential_version() {
19931        let t = vec![2, 1, 3, 1, 0];
19932        let mut sa = vec![0; t.len()];
19933        let mut buckets = vec![0; 2 * 4];
19934        let m = count_and_gather_lms_suffixes_32s_2k_nofs_omp(
19935            &t,
19936            &mut sa,
19937            t.len() as SaSint,
19938            4,
19939            &mut buckets,
19940            2,
19941        );
19942        assert!(m >= 0);
19943        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19944    }
19945
19946    #[test]
19947    fn count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp_wraps_sequential_version() {
19948        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
19949        let mut sa = vec![0; t.len()];
19950        let mut buckets = vec![0; 2 * 4];
19951        let m = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
19952            &t,
19953            &mut sa,
19954            t.len() as SaSint,
19955            4,
19956            &mut buckets,
19957            2,
19958        );
19959        assert!(m >= 0);
19960        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19961    }
19962
19963    #[test]
19964    fn count_and_gather_lms_suffixes_32s_nofs_omp_uses_large_input_paths() {
19965        let n = 65_600usize;
19966        let k = 257usize;
19967        let text: Vec<SaSint> = (0..n)
19968            .map(|i| 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint)
19969            .collect();
19970
19971        let mut sa_threaded = vec![-99; n];
19972        let mut sa_scalar = vec![-99; n];
19973        let mut buckets_threaded = vec![0; 4 * k];
19974        let mut buckets_scalar = vec![0; 4 * k];
19975        let m_threaded = count_and_gather_lms_suffixes_32s_4k_nofs_omp(
19976            &text,
19977            &mut sa_threaded,
19978            n as SaSint,
19979            k as SaSint,
19980            &mut buckets_threaded,
19981            4,
19982        );
19983        let m_scalar = count_and_gather_lms_suffixes_32s_4k(
19984            &text,
19985            &mut sa_scalar,
19986            n as SaSint,
19987            k as SaSint,
19988            &mut buckets_scalar,
19989            0,
19990            n as FastSint,
19991        );
19992        assert_eq!(m_threaded, m_scalar);
19993        assert_eq!(
19994            &sa_threaded[n - m_threaded as usize..],
19995            &sa_scalar[n - m_scalar as usize..]
19996        );
19997        assert_eq!(buckets_threaded, buckets_scalar);
19998
19999        let mut sa_threaded = vec![-99; n];
20000        let mut sa_scalar = vec![-99; n];
20001        let mut buckets_threaded = vec![0; 2 * k];
20002        let mut buckets_scalar = vec![0; 2 * k];
20003        let m_threaded = count_and_gather_lms_suffixes_32s_2k_nofs_omp(
20004            &text,
20005            &mut sa_threaded,
20006            n as SaSint,
20007            k as SaSint,
20008            &mut buckets_threaded,
20009            4,
20010        );
20011        let m_scalar = count_and_gather_lms_suffixes_32s_2k(
20012            &text,
20013            &mut sa_scalar,
20014            n as SaSint,
20015            k as SaSint,
20016            &mut buckets_scalar,
20017            0,
20018            n as FastSint,
20019        );
20020        assert_eq!(m_threaded, m_scalar);
20021        assert_eq!(
20022            &sa_threaded[n - m_threaded as usize..],
20023            &sa_scalar[n - m_scalar as usize..]
20024        );
20025        assert_eq!(buckets_threaded, buckets_scalar);
20026    }
20027
20028    #[test]
20029    fn count_and_gather_lms_suffixes_32s_fs_omp_uses_large_input_paths() {
20030        let n = 65_600usize;
20031        let k = 257usize;
20032        let text: Vec<SaSint> = (0..n)
20033            .map(|i| 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint)
20034            .collect();
20035        let mut thread_state = alloc_thread_state(4).unwrap();
20036
20037        let mut sa_threaded = vec![-99; n];
20038        let mut sa_scalar = vec![-99; n];
20039        let mut buckets_threaded = vec![0; 4 * k];
20040        let mut buckets_scalar = vec![0; 4 * k];
20041        let m_threaded = count_and_gather_lms_suffixes_32s_4k_fs_omp(
20042            &text,
20043            &mut sa_threaded,
20044            n as SaSint,
20045            k as SaSint,
20046            &mut buckets_threaded,
20047            0,
20048            4,
20049            &mut thread_state,
20050        );
20051        let m_scalar = count_and_gather_lms_suffixes_32s_4k(
20052            &text,
20053            &mut sa_scalar,
20054            n as SaSint,
20055            k as SaSint,
20056            &mut buckets_scalar,
20057            0,
20058            n as FastSint,
20059        );
20060        assert_eq!(m_threaded, m_scalar);
20061        assert_eq!(
20062            &sa_threaded[n - m_threaded as usize..],
20063            &sa_scalar[n - m_scalar as usize..]
20064        );
20065        assert_eq!(buckets_threaded, buckets_scalar);
20066
20067        let mut sa_threaded = vec![-99; n];
20068        let mut sa_scalar = vec![-99; n];
20069        let mut buckets_threaded = vec![0; 2 * k];
20070        let mut buckets_scalar = vec![0; 2 * k];
20071        let m_threaded = count_and_gather_lms_suffixes_32s_2k_fs_omp(
20072            &text,
20073            &mut sa_threaded,
20074            n as SaSint,
20075            k as SaSint,
20076            &mut buckets_threaded,
20077            0,
20078            4,
20079            &mut thread_state,
20080        );
20081        let m_scalar = count_and_gather_lms_suffixes_32s_2k(
20082            &text,
20083            &mut sa_scalar,
20084            n as SaSint,
20085            k as SaSint,
20086            &mut buckets_scalar,
20087            0,
20088            n as FastSint,
20089        );
20090        assert_eq!(m_threaded, m_scalar);
20091        assert_eq!(
20092            &sa_threaded[n - m_threaded as usize..],
20093            &sa_scalar[n - m_scalar as usize..]
20094        );
20095        assert_eq!(buckets_threaded, buckets_scalar);
20096    }
20097
20098    #[test]
20099    fn count_and_gather_compacted_lms_suffixes_32s_nofs_omp_uses_large_input_path() {
20100        let n = 65_600usize;
20101        let k = 257usize;
20102        let text: Vec<SaSint> = (0..n)
20103            .map(|i| {
20104                let value = 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint;
20105                if i % 19 == 0 {
20106                    value | SAINT_MIN
20107                } else {
20108                    value
20109                }
20110            })
20111            .collect();
20112
20113        let mut sa_threaded = vec![-99; n];
20114        let mut sa_split = vec![-99; n];
20115        let mut buckets_threaded = vec![0; 2 * k];
20116        let mut buckets_split = vec![0; 2 * k];
20117        let m_threaded = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
20118            &text,
20119            &mut sa_threaded,
20120            n as SaSint,
20121            k as SaSint,
20122            &mut buckets_threaded,
20123            4,
20124        );
20125        count_compacted_lms_suffixes_32s_2k(&text, n as SaSint, k as SaSint, &mut buckets_split);
20126        let m_split = gather_compacted_lms_suffixes_32s(&text, &mut sa_split, n as SaSint);
20127
20128        assert_eq!(m_threaded, m_split);
20129        assert_eq!(
20130            &sa_threaded[n - m_threaded as usize..],
20131            &sa_split[n - m_split as usize..]
20132        );
20133        assert_eq!(buckets_threaded, buckets_split);
20134    }
20135
20136    #[test]
20137    fn count_and_gather_compacted_lms_suffixes_32s_fs_omp_uses_large_input_path() {
20138        let n = 65_600usize;
20139        let k = 257usize;
20140        let text: Vec<SaSint> = (0..n)
20141            .map(|i| {
20142                let value = 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint;
20143                if i % 19 == 0 {
20144                    value | SAINT_MIN
20145                } else {
20146                    value
20147                }
20148            })
20149            .collect();
20150
20151        let mut sa_threaded = vec![-99; 2 * n];
20152        let mut sa_scalar = vec![-99; n];
20153        let mut buckets_threaded = vec![0; 2 * k];
20154        let mut buckets_scalar = vec![0; 2 * k];
20155        let mut thread_state = alloc_thread_state(4).unwrap();
20156        count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
20157            &text,
20158            &mut sa_threaded,
20159            n as SaSint,
20160            k as SaSint,
20161            &mut buckets_threaded,
20162            0,
20163            4,
20164            &mut thread_state,
20165        );
20166        let m_scalar = count_and_gather_compacted_lms_suffixes_32s_2k(
20167            &text,
20168            &mut sa_scalar,
20169            n as SaSint,
20170            k as SaSint,
20171            &mut buckets_scalar,
20172            0,
20173            n as FastSint,
20174        );
20175
20176        assert_eq!(
20177            &sa_threaded[n - m_scalar as usize..n],
20178            &sa_scalar[n - m_scalar as usize..]
20179        );
20180        assert_eq!(buckets_threaded, buckets_scalar);
20181    }
20182
20183    #[test]
20184    fn accumulate_counts_helpers_match_prefix_bucket_addition() {
20185        let mut bucket00 = vec![4, 5, 6];
20186        let bucket01 = vec![1, 2, 3];
20187        let bucket02 = vec![7, 8, 9];
20188        let bucket03 = vec![10, 11, 12];
20189        let bucket04 = vec![13, 14, 15];
20190        let bucket05 = vec![16, 17, 18];
20191        let bucket06 = vec![19, 20, 21];
20192        let bucket07 = vec![22, 23, 24];
20193        let bucket08 = vec![25, 26, 27];
20194
20195        accumulate_counts_s32_2(&mut bucket00, &bucket01);
20196        assert_eq!(bucket00, vec![5, 7, 9]);
20197
20198        accumulate_counts_s32_3(&mut bucket00, &bucket01, &bucket02);
20199        assert_eq!(bucket00, vec![13, 17, 21]);
20200
20201        accumulate_counts_s32_4(&mut bucket00, &bucket01, &bucket02, &bucket03);
20202        assert_eq!(bucket00, vec![31, 38, 45]);
20203
20204        accumulate_counts_s32_5(&mut bucket00, &bucket01, &bucket02, &bucket03, &bucket04);
20205        assert_eq!(bucket00, vec![62, 73, 84]);
20206
20207        accumulate_counts_s32_6(
20208            &mut bucket00,
20209            &bucket01,
20210            &bucket02,
20211            &bucket03,
20212            &bucket04,
20213            &bucket05,
20214        );
20215        assert_eq!(bucket00, vec![109, 125, 141]);
20216
20217        accumulate_counts_s32_7(
20218            &mut bucket00,
20219            &bucket01,
20220            &bucket02,
20221            &bucket03,
20222            &bucket04,
20223            &bucket05,
20224            &bucket06,
20225        );
20226        assert_eq!(bucket00, vec![175, 197, 219]);
20227
20228        accumulate_counts_s32_8(
20229            &mut bucket00,
20230            &bucket01,
20231            &bucket02,
20232            &bucket03,
20233            &bucket04,
20234            &bucket05,
20235            &bucket06,
20236            &bucket07,
20237        );
20238        assert_eq!(bucket00, vec![263, 292, 321]);
20239
20240        accumulate_counts_s32_9(
20241            &mut bucket00,
20242            &bucket01,
20243            &bucket02,
20244            &bucket03,
20245            &bucket04,
20246            &bucket05,
20247            &bucket06,
20248            &bucket07,
20249            &bucket08,
20250        );
20251        assert_eq!(bucket00, vec![376, 413, 450]);
20252    }
20253
20254    #[test]
20255    fn accumulate_counts_s32_matches_c_dispatch_for_small_bucket_counts() {
20256        let mut buckets = vec![1, 2, 3, 4, 5, 6, 7, 8];
20257        accumulate_counts_s32(&mut buckets, 2, 2, 4);
20258        assert_eq!(buckets, vec![1, 2, 3, 4, 5, 6, 16, 20]);
20259    }
20260
20261    #[test]
20262    fn accumulate_counts_s32_matches_c_dispatch_for_nine_buckets() {
20263        let mut buckets = vec![
20264            1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80, 9, 90,
20265        ];
20266        accumulate_counts_s32(&mut buckets, 2, 2, 9);
20267        assert_eq!(
20268            buckets,
20269            vec![1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80, 45, 450]
20270        );
20271    }
20272
20273    #[test]
20274    fn accumulate_counts_s32_matches_c_chunked_nine_then_tail_behavior() {
20275        let mut buckets = (1..=11).collect::<Vec<SaSint>>();
20276        accumulate_counts_s32(&mut buckets, 1, 1, 11);
20277        assert_eq!(buckets, vec![1, 2, 3, 4, 5, 6, 7, 8, 45, 10, 66]);
20278    }
20279
20280    fn deterministic_large_input(seed: u64, len: usize, alphabet: u32) -> Vec<u8> {
20281        let mut rng = seed | 1;
20282        (0..len)
20283            .map(|_| {
20284                rng = rng.wrapping_add(0x9e3779b97f4a7c15);
20285                let mut z = rng;
20286                z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
20287                z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
20288                z ^= z >> 31;
20289                (z % u64::from(alphabet)) as u8
20290            })
20291            .collect()
20292    }
20293
20294    /// `threads>1` must hit the parallel branch (n>=65_536) and produce the
20295    /// exact same suffix array as the single-thread path. Regression test for
20296    /// the OMP-to-rayon port.
20297    #[test]
20298    fn libsais_omp_threads_2_matches_single_thread_on_large_input() {
20299        let text = deterministic_large_input(0xC0FFEE, 200_000, 4);
20300        let mut sa_single = vec![0 as SaSint; text.len() + 1];
20301        let mut sa_omp = vec![0 as SaSint; text.len() + 1];
20302        assert_eq!(libsais(&text, &mut sa_single, 0, None), 0);
20303        assert_eq!(libsais_omp(&text, &mut sa_omp, 0, None, 2), 0);
20304        assert_eq!(sa_single, sa_omp, "threads=2 SA must match threads=1 SA");
20305    }
20306
20307    #[test]
20308    fn libsais_omp_threads_4_matches_single_thread_on_large_input() {
20309        let text = deterministic_large_input(0xBADF00D, 250_000, 8);
20310        let mut sa_single = vec![0 as SaSint; text.len() + 1];
20311        let mut sa_omp = vec![0 as SaSint; text.len() + 1];
20312        assert_eq!(libsais(&text, &mut sa_single, 0, None), 0);
20313        assert_eq!(libsais_omp(&text, &mut sa_omp, 0, None, 4), 0);
20314        assert_eq!(sa_single, sa_omp, "threads=4 SA must match threads=1 SA");
20315    }
20316
20317    /// Guard against the README's "futex deadlock when called from inside a
20318    /// host rayon pool" regression. The crate must not build a nested rayon
20319    /// pool — `run_rayon_with_threads` checks `current_thread_index()` and
20320    /// reuses the ambient pool in that case.
20321    #[test]
20322    fn libsais_omp_does_not_deadlock_when_invoked_from_rayon_worker() {
20323        use rayon::prelude::*;
20324        let text = deterministic_large_input(0x5EED, 100_000, 4);
20325        let inputs: Vec<&[u8]> = vec![&text, &text, &text, &text];
20326        let results: Vec<SaSint> = inputs
20327            .par_iter()
20328            .map(|t| {
20329                let mut sa = vec![0 as SaSint; t.len() + 1];
20330                libsais_omp(t, &mut sa, 0, None, 4)
20331            })
20332            .collect();
20333        assert!(results.iter().all(|&rc| rc == 0));
20334    }
20335}