Skip to main content

libsais_rs/
libsais64.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 64-bit suffix array, BWT, unBWT, PLCP and LCP entry
5//! points (mirroring `libsais64.h`).
6
7use std::marker::PhantomData;
8use std::mem;
9#[cfg(feature = "upstream-c")]
10use std::mem::MaybeUninit;
11
12use crate::{run_rayon_with_threads, SyncMutPtr};
13use rayon::prelude::*;
14
15pub type SaSint = i64;
16pub type SaUint = u64;
17pub type FastSint = isize;
18pub type FastUint = usize;
19
20pub const SAINT_BIT: u32 = 64;
21pub const SAINT_MAX: SaSint = i64::MAX;
22pub const SAINT_MIN: SaSint = i64::MIN;
23
24pub const ALPHABET_SIZE: usize = 1usize << 8;
25pub const UNBWT_FASTBITS: usize = 17;
26
27pub const SUFFIX_GROUP_BIT: u32 = SAINT_BIT - 1;
28pub const SUFFIX_GROUP_MARKER: SaSint = 1_i64 << (SUFFIX_GROUP_BIT - 1);
29
30pub const LIBSAIS_LOCAL_BUFFER_SIZE: usize = 1000;
31pub const LIBSAIS_PER_THREAD_CACHE_SIZE: usize = 24_576;
32
33pub const LIBSAIS_FLAGS_NONE: SaSint = 0;
34pub const LIBSAIS_FLAGS_BWT: SaSint = 1;
35pub const LIBSAIS_FLAGS_GSA: SaSint = 2;
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub struct ThreadCache {
39    pub symbol: SaSint,
40    pub index: SaSint,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct ThreadState {
45    pub position: FastSint,
46    pub count: FastSint,
47    pub m: FastSint,
48    pub last_lms_suffix: FastSint,
49    pub buckets: Vec<SaSint>,
50    pub cache: Vec<ThreadCache>,
51}
52
53impl ThreadState {
54    fn new() -> Self {
55        Self {
56            position: 0,
57            count: 0,
58            m: 0,
59            last_lms_suffix: 0,
60            buckets: vec![0; 4 * ALPHABET_SIZE],
61            cache: vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE],
62        }
63    }
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct Context {
68    pub buckets: Vec<SaSint>,
69    pub thread_state: Option<Vec<ThreadState>>,
70    pub threads: FastSint,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct UnbwtContext {
75    pub bucket2: Vec<SaUint>,
76    pub fastbits: Vec<u16>,
77    pub buckets: Option<Vec<SaUint>>,
78    pub threads: FastSint,
79}
80
81/// Internal helper: buckets index2.
82#[doc(hidden)]
83pub fn buckets_index2(c: FastUint, s: FastUint) -> FastUint {
84    (c << 1) + s
85}
86
87/// Internal helper: buckets index4.
88#[doc(hidden)]
89pub fn buckets_index4(c: FastUint, s: FastUint) -> FastUint {
90    (c << 2) + s
91}
92
93/// Internal helper: align up.
94#[doc(hidden)]
95pub fn align_up(value: usize, alignment: usize) -> usize {
96    debug_assert!(alignment.is_power_of_two());
97    (value + alignment - 1) & !(alignment - 1)
98}
99
100/// Internal helper: alloc thread state.
101#[doc(hidden)]
102pub fn alloc_thread_state(threads: SaSint) -> Option<Vec<ThreadState>> {
103    if threads <= 0 {
104        return None;
105    }
106
107    let len = usize::try_from(threads).ok()?;
108    Some((0..len).map(|_| ThreadState::new()).collect())
109}
110
111/// Internal helper: create ctx main.
112#[doc(hidden)]
113pub fn create_ctx_main(threads: SaSint) -> Option<Context> {
114    if threads <= 0 {
115        return None;
116    }
117
118    let thread_state = if threads > 1 {
119        Some(alloc_thread_state(threads)?)
120    } else {
121        None
122    };
123
124    Some(Context {
125        buckets: vec![0; 8 * ALPHABET_SIZE],
126        thread_state,
127        threads: threads as FastSint,
128    })
129}
130
131/// Creates the libsais64 context that allows reusing allocated memory with each libsais64 operation.
132///
133/// In multi-threaded environments, use one context per thread for parallel executions.
134///
135/// Returns the context, or `None` on allocation failure.
136pub fn create_ctx() -> Option<Context> {
137    create_ctx_main(1)
138}
139
140/// Destroys the libsais64 context and frees previously allocated memory.
141pub fn free_ctx(_ctx: Context) {}
142
143/// Internal helper: unbwt create ctx main.
144#[doc(hidden)]
145pub fn unbwt_create_ctx_main(threads: SaSint) -> Option<UnbwtContext> {
146    if threads <= 0 {
147        return None;
148    }
149
150    let buckets = if threads > 1 {
151        let len = usize::try_from(threads).ok()? * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE);
152        Some(vec![0; len])
153    } else {
154        None
155    };
156
157    Some(UnbwtContext {
158        bucket2: vec![0; ALPHABET_SIZE * ALPHABET_SIZE],
159        fastbits: vec![0; 1 + (1 << UNBWT_FASTBITS)],
160        buckets,
161        threads: threads as FastSint,
162    })
163}
164
165/// Internal helper: unbwt free ctx main.
166#[doc(hidden)]
167pub fn unbwt_free_ctx_main(_ctx: UnbwtContext) {}
168
169/// Creates the libsais64 reverse-BWT context that allows reusing allocated memory with each `libsais64_unbwt_*` operation.
170///
171/// In multi-threaded environments, use one context per thread for parallel executions.
172///
173/// Returns the context, or `None` on allocation failure.
174pub fn unbwt_create_ctx() -> Option<UnbwtContext> {
175    unbwt_create_ctx_main(1)
176}
177
178/// Destroys the libsais64 reverse-BWT context and frees previously allocated memory.
179pub fn unbwt_free_ctx(_ctx: UnbwtContext) {}
180
181/// Internal helper: count negative marked suffixes.
182#[doc(hidden)]
183pub fn count_negative_marked_suffixes(
184    sa: &[SaSint],
185    block_start: FastSint,
186    block_size: FastSint,
187) -> SaSint {
188    block_slice(sa, block_start, block_size)
189        .iter()
190        .map(|&value| SaSint::from(value < 0))
191        .sum()
192}
193
194/// Internal helper: count zero marked suffixes.
195#[doc(hidden)]
196pub fn count_zero_marked_suffixes(
197    sa: &[SaSint],
198    block_start: FastSint,
199    block_size: FastSint,
200) -> SaSint {
201    block_slice(sa, block_start, block_size)
202        .iter()
203        .map(|&value| SaSint::from(value == 0))
204        .sum()
205}
206
207/// Internal helper: place cached suffixes.
208#[doc(hidden)]
209pub fn place_cached_suffixes(
210    sa: &mut [SaSint],
211    cache: &[ThreadCache],
212    block_start: FastSint,
213    block_size: FastSint,
214) {
215    let start = usize::try_from(block_start).expect("block_start must be non-negative");
216    let len = usize::try_from(block_size).expect("block_size must be non-negative");
217    let entries = if cache.len() >= start + len {
218        &cache[start..start + len]
219    } else {
220        &cache[..len]
221    };
222
223    for entry in entries {
224        let slot = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
225        sa[slot] = entry.index;
226    }
227}
228
229/// Internal helper: compact and place cached suffixes.
230#[doc(hidden)]
231pub fn compact_and_place_cached_suffixes(
232    sa: &mut [SaSint],
233    cache: &mut [ThreadCache],
234    block_start: FastSint,
235    block_size: FastSint,
236) {
237    let start = usize::try_from(block_start).expect("block_start must be non-negative");
238    let len = usize::try_from(block_size).expect("block_size must be non-negative");
239    let read_start = if cache.len() >= start + len { start } else { 0 };
240    let read_end = read_start + len;
241
242    let mut write = read_start;
243    for read in read_start..read_end {
244        let entry = cache[read];
245        if entry.symbol >= 0 {
246            cache[write] = entry;
247            write += 1;
248        }
249    }
250
251    place_cached_suffixes(sa, cache, block_start, (write - read_start) as FastSint);
252}
253
254/// Internal helper: flip suffix markers (OpenMP variant).
255#[doc(hidden)]
256pub fn flip_suffix_markers_omp(sa: &mut [SaSint], l: SaSint, threads: SaSint) {
257    let len = usize::try_from(l).expect("l must be non-negative");
258    let omp_num_threads = if threads > 1 && l >= 65_536 {
259        usize::try_from(threads).expect("threads must be non-negative")
260    } else {
261        1
262    };
263    if omp_num_threads > 1 {
264        let chunk_size = ((len / omp_num_threads) & !15usize).max(16);
265        run_rayon_with_threads(omp_num_threads, || {
266            sa[..len].par_chunks_mut(chunk_size).for_each(|chunk| {
267                for value in chunk {
268                    *value ^= SAINT_MIN;
269                }
270            });
271        });
272        return;
273    }
274
275    let omp_block_stride = (len / omp_num_threads) & !15usize;
276    for omp_thread_num in 0..omp_num_threads {
277        let omp_block_start = omp_thread_num * omp_block_stride;
278        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
279            omp_block_stride
280        } else {
281            len - omp_block_start
282        };
283        for value in &mut sa[omp_block_start..omp_block_start + omp_block_size] {
284            *value ^= SAINT_MIN;
285        }
286    }
287}
288
289/// Internal helper: gather lms suffixes 8u.
290#[doc(hidden)]
291pub fn gather_lms_suffixes_8u(
292    t: &[u8],
293    sa: &mut [SaSint],
294    n: SaSint,
295    mut m: FastSint,
296    omp_block_start: FastSint,
297    omp_block_size: FastSint,
298) {
299    if omp_block_size <= 0 {
300        return;
301    }
302
303    let n = usize::try_from(n).expect("n must be non-negative");
304    let block_start =
305        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
306    let block_size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
307
308    let mut j = block_start + block_size;
309    let mut c0 = t[block_start + block_size - 1] as FastSint;
310    let mut c1 = -1;
311    while j < n {
312        c1 = t[j] as FastSint;
313        if c1 != c0 {
314            break;
315        }
316        j += 1;
317    }
318
319    let mut f0 = usize::from(c0 >= c1);
320    let mut f1: usize;
321    let mut i = block_start + block_size - 2;
322    let limit = block_start + 3;
323
324    while i >= limit {
325        c1 = t[i] as FastSint;
326        f1 = usize::from(c1 > (c0 - f0 as FastSint));
327        sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
328        m -= (f1 & !f0) as FastSint;
329
330        c0 = t[i - 1] as FastSint;
331        f0 = usize::from(c0 > (c1 - f1 as FastSint));
332        sa[usize::try_from(m).expect("m must be non-negative")] = i as SaSint;
333        m -= (f0 & !f1) as FastSint;
334
335        c1 = t[i - 2] as FastSint;
336        f1 = usize::from(c1 > (c0 - f0 as FastSint));
337        sa[usize::try_from(m).expect("m must be non-negative")] = (i - 1) as SaSint;
338        m -= (f1 & !f0) as FastSint;
339
340        c0 = t[i - 3] as FastSint;
341        f0 = usize::from(c0 > (c1 - f1 as FastSint));
342        sa[usize::try_from(m).expect("m must be non-negative")] = (i - 2) as SaSint;
343        m -= (f0 & !f1) as FastSint;
344
345        if i < 4 {
346            break;
347        }
348        i -= 4;
349    }
350
351    let tail_limit = limit - 3;
352    while i >= tail_limit {
353        c1 = c0;
354        c0 = t[i] as FastSint;
355        f1 = f0;
356        f0 = usize::from(c0 > (c1 - f1 as FastSint));
357        sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
358        m -= (f0 & !f1) as FastSint;
359        if i == 0 {
360            break;
361        }
362        i -= 1;
363    }
364
365    sa[usize::try_from(m).expect("m must be non-negative")] = (i + 1) as SaSint;
366}
367
368/// Internal helper: gather lms suffixes 8u (OpenMP variant).
369#[doc(hidden)]
370pub fn gather_lms_suffixes_8u_omp(
371    t: &[u8],
372    sa: &mut [SaSint],
373    n: SaSint,
374    threads: SaSint,
375    thread_state: &mut [ThreadState],
376) {
377    let n_usize = usize::try_from(n).expect("n must be non-negative");
378    let omp_num_threads = if threads > 1 && n >= 65_536 {
379        usize::try_from(threads)
380            .expect("threads must be non-negative")
381            .min(thread_state.len())
382            .max(1)
383    } else {
384        1
385    };
386    if omp_num_threads == 1 {
387        gather_lms_suffixes_8u(t, sa, n, n as FastSint - 1, 0, n as FastSint);
388        return;
389    }
390
391    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
392    let mut suffix_counts_after = vec![0 as FastSint; omp_num_threads];
393    let mut m = 0 as FastSint;
394    for omp_thread_num in (0..omp_num_threads).rev() {
395        suffix_counts_after[omp_thread_num] = m;
396        m += thread_state[omp_thread_num].m;
397    }
398
399    let sa_ptr = SyncMutPtr::new(sa);
400    let suffix_counts_after_slice: &[FastSint] = &suffix_counts_after;
401    let last_lms_suffixes: Vec<FastSint> = thread_state[..omp_num_threads]
402        .iter()
403        .map(|s| {
404            if s.m > 0 {
405                s.last_lms_suffix
406            } else {
407                FastSint::MIN
408            }
409        })
410        .collect();
411
412    run_rayon_with_threads(omp_num_threads, || {
413        (0..omp_num_threads)
414            .into_par_iter()
415            .for_each(|omp_thread_num| {
416                let omp_block_start = omp_thread_num * omp_block_stride;
417                let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
418                    omp_block_stride
419                } else {
420                    n_usize - omp_block_start
421                };
422                // SAFETY: per-thread disjoint sa block plus a unique tail write.
423                let sa = unsafe { sa_ptr.as_slice() };
424                gather_lms_suffixes_8u(
425                    t,
426                    sa,
427                    n,
428                    n as FastSint - 1 - suffix_counts_after_slice[omp_thread_num],
429                    omp_block_start as FastSint,
430                    omp_block_size as FastSint,
431                );
432            });
433    });
434
435    for omp_thread_num in 0..omp_num_threads {
436        if last_lms_suffixes[omp_thread_num] != FastSint::MIN {
437            let dst = usize::try_from(n as FastSint - 1 - suffix_counts_after[omp_thread_num])
438                .expect("destination must be non-negative");
439            sa[dst] = last_lms_suffixes[omp_thread_num] as SaSint;
440        }
441    }
442}
443
444/// Internal helper: gather lms suffixes 32s.
445#[doc(hidden)]
446pub fn gather_lms_suffixes_32s(t: &[SaSint], sa: &mut [SaSint], n: SaSint) -> SaSint {
447    let n_usize = usize::try_from(n).expect("n must be non-negative");
448    let mut i = n as FastSint - 2;
449    let mut m = n_usize - 1;
450    let mut f0 = 1usize;
451    let mut f1: usize;
452    let mut c0 = t[n_usize - 1] as FastSint;
453    let mut c1: FastSint;
454
455    while i >= 3 {
456        c1 = t[i as usize] as FastSint;
457        f1 = usize::from(c1 > (c0 - f0 as FastSint));
458        sa[m] = (i + 1) as SaSint;
459        m -= f1 & !f0;
460
461        c0 = t[(i - 1) as usize] as FastSint;
462        f0 = usize::from(c0 > (c1 - f1 as FastSint));
463        sa[m] = i as SaSint;
464        m -= f0 & !f1;
465
466        c1 = t[(i - 2) as usize] as FastSint;
467        f1 = usize::from(c1 > (c0 - f0 as FastSint));
468        sa[m] = (i - 1) as SaSint;
469        m -= f1 & !f0;
470
471        c0 = t[(i - 3) as usize] as FastSint;
472        f0 = usize::from(c0 > (c1 - f1 as FastSint));
473        sa[m] = (i - 2) as SaSint;
474        m -= f0 & !f1;
475
476        i -= 4;
477    }
478
479    while i >= 0 {
480        c1 = c0;
481        c0 = t[i as usize] as FastSint;
482        f1 = f0;
483        f0 = usize::from(c0 > (c1 - f1 as FastSint));
484        sa[m] = (i + 1) as SaSint;
485        m -= f0 & !f1;
486        i -= 1;
487    }
488
489    (n_usize - 1 - m) as SaSint
490}
491
492/// Internal helper: gather compacted lms suffixes 32s.
493#[doc(hidden)]
494pub fn gather_compacted_lms_suffixes_32s(t: &[SaSint], sa: &mut [SaSint], n: SaSint) -> SaSint {
495    let n_usize = usize::try_from(n).expect("n must be non-negative");
496    let mut i = n as FastSint - 2;
497    let mut m = n_usize - 1;
498    let mut f0 = 1usize;
499    let mut f1: usize;
500    let mut c0 = t[n_usize - 1] as FastSint;
501    let mut c1: FastSint;
502
503    while i >= 3 {
504        c1 = t[i as usize] as FastSint;
505        f1 = usize::from(c1 > (c0 - f0 as FastSint));
506        sa[m] = (i + 1) as SaSint;
507        m -= f1 & !f0 & usize::from(c0 >= 0);
508
509        c0 = t[(i - 1) as usize] as FastSint;
510        f0 = usize::from(c0 > (c1 - f1 as FastSint));
511        sa[m] = i as SaSint;
512        m -= f0 & !f1 & usize::from(c1 >= 0);
513
514        c1 = t[(i - 2) as usize] as FastSint;
515        f1 = usize::from(c1 > (c0 - f0 as FastSint));
516        sa[m] = (i - 1) as SaSint;
517        m -= f1 & !f0 & usize::from(c0 >= 0);
518
519        c0 = t[(i - 3) as usize] as FastSint;
520        f0 = usize::from(c0 > (c1 - f1 as FastSint));
521        sa[m] = (i - 2) as SaSint;
522        m -= f0 & !f1 & usize::from(c1 >= 0);
523
524        i -= 4;
525    }
526
527    while i >= 0 {
528        c1 = c0;
529        c0 = t[i as usize] as FastSint;
530        f1 = f0;
531        f0 = usize::from(c0 > (c1 - f1 as FastSint));
532        sa[m] = (i + 1) as SaSint;
533        m -= f0 & !f1 & usize::from(c1 >= 0);
534        i -= 1;
535    }
536
537    (n_usize - 1 - m) as SaSint
538}
539
540/// Internal helper: count lms suffixes 32s 4k.
541#[doc(hidden)]
542pub fn count_lms_suffixes_32s_4k(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
543    buckets.fill(0);
544    let n_usize = usize::try_from(n).expect("n must be non-negative");
545    let _k_usize = usize::try_from(k).expect("k must be non-negative");
546    let mut i = n as FastSint - 2;
547    let mut f0 = 1usize;
548    let mut f1: usize;
549    let mut c0 = t[n_usize - 1] as FastSint;
550    let mut c1: FastSint;
551
552    while i >= 3 {
553        c1 = t[i as usize] as FastSint;
554        f1 = usize::from(c1 > (c0 - f0 as FastSint));
555        buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
556
557        c0 = t[(i - 1) as usize] as FastSint;
558        f0 = usize::from(c0 > (c1 - f1 as FastSint));
559        buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
560
561        c1 = t[(i - 2) as usize] as FastSint;
562        f1 = usize::from(c1 > (c0 - f0 as FastSint));
563        buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
564
565        c0 = t[(i - 3) as usize] as FastSint;
566        f0 = usize::from(c0 > (c1 - f1 as FastSint));
567        buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
568
569        i -= 4;
570    }
571
572    while i >= 0 {
573        c1 = c0;
574        c0 = t[i as usize] as FastSint;
575        f1 = f0;
576        f0 = usize::from(c0 > (c1 - f1 as FastSint));
577        buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
578        i -= 1;
579    }
580
581    buckets[buckets_index4(c0 as usize, f0 + f0)] += 1;
582}
583
584/// Internal helper: count lms suffixes 32s 2k.
585#[doc(hidden)]
586pub fn count_lms_suffixes_32s_2k(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
587    buckets.fill(0);
588    let n_usize = usize::try_from(n).expect("n must be non-negative");
589    let _k_usize = usize::try_from(k).expect("k must be non-negative");
590    let mut i = n as FastSint - 2;
591    let mut f0 = 1usize;
592    let mut f1: usize;
593    let mut c0 = t[n_usize - 1] as FastSint;
594    let mut c1: FastSint;
595
596    while i >= 3 {
597        c1 = t[i as usize] as FastSint;
598        f1 = usize::from(c1 > (c0 - f0 as FastSint));
599        buckets[buckets_index2(c0 as usize, f1 & !f0)] += 1;
600
601        c0 = t[(i - 1) as usize] as FastSint;
602        f0 = usize::from(c0 > (c1 - f1 as FastSint));
603        buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
604
605        c1 = t[(i - 2) as usize] as FastSint;
606        f1 = usize::from(c1 > (c0 - f0 as FastSint));
607        buckets[buckets_index2(c0 as usize, f1 & !f0)] += 1;
608
609        c0 = t[(i - 3) as usize] as FastSint;
610        f0 = usize::from(c0 > (c1 - f1 as FastSint));
611        buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
612
613        i -= 4;
614    }
615
616    while i >= 0 {
617        c1 = c0;
618        c0 = t[i as usize] as FastSint;
619        f1 = f0;
620        f0 = usize::from(c0 > (c1 - f1 as FastSint));
621        buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
622        i -= 1;
623    }
624
625    buckets[buckets_index2(c0 as usize, 0)] += 1;
626}
627
628/// Internal helper: count compacted lms suffixes 32s 2k.
629#[doc(hidden)]
630pub fn count_compacted_lms_suffixes_32s_2k(
631    t: &[SaSint],
632    n: SaSint,
633    k: SaSint,
634    buckets: &mut [SaSint],
635) {
636    buckets.fill(0);
637    let n_usize = usize::try_from(n).expect("n must be non-negative");
638    let _k_usize = usize::try_from(k).expect("k must be non-negative");
639    let mut i = n as FastSint - 2;
640    let mut f0 = 1usize;
641    let mut f1: usize;
642    let mut c0 = t[n_usize - 1] as FastSint;
643    let mut c1: FastSint;
644
645    while i >= 3 {
646        c1 = t[i as usize] as FastSint;
647        f1 = usize::from(c1 > (c0 - f0 as FastSint));
648        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
649
650        c0 = t[(i - 1) as usize] as FastSint;
651        f0 = usize::from(c0 > (c1 - f1 as FastSint));
652        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
653
654        c1 = t[(i - 2) as usize] as FastSint;
655        f1 = usize::from(c1 > (c0 - f0 as FastSint));
656        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
657
658        c0 = t[(i - 3) as usize] as FastSint;
659        f0 = usize::from(c0 > (c1 - f1 as FastSint));
660        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
661
662        i -= 4;
663    }
664
665    while i >= 0 {
666        c1 = c0;
667        c0 = t[i as usize] as FastSint;
668        f1 = f0;
669        f0 = usize::from(c0 > (c1 - f1 as FastSint));
670        buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
671        i -= 1;
672    }
673
674    buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, 0)] += 1;
675}
676
677/// Internal helper: count and gather lms suffixes 8u.
678#[doc(hidden)]
679pub fn count_and_gather_lms_suffixes_8u(
680    t: &[u8],
681    sa: &mut [SaSint],
682    n: SaSint,
683    buckets: &mut [SaSint],
684    omp_block_start: FastSint,
685    omp_block_size: FastSint,
686) -> SaSint {
687    buckets.fill(0);
688    let n = n as FastSint;
689    let mut m = omp_block_start + omp_block_size - 1;
690
691    if omp_block_size > 0 {
692        let prefetch_distance = 256 as FastSint;
693        let mut j = m + 1;
694        let mut c0 = t[m as usize] as FastSint;
695        let mut c1 = -1;
696        while j < n {
697            c1 = t[j as usize] as FastSint;
698            if c1 != c0 {
699                break;
700            }
701            j += 1;
702        }
703
704        let mut f0 = usize::from(c0 >= c1);
705        let mut f1: usize;
706        let mut i = m - 1;
707        let limit = omp_block_start + 3;
708
709        while i >= limit {
710            let _prefetch_index = i - prefetch_distance;
711            c1 = t[i as usize] as FastSint;
712            f1 = usize::from(c1 > (c0 - f0 as FastSint));
713            sa[m as usize] = (i + 1) as SaSint;
714            m -= (f1 & !f0) as FastSint;
715            buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
716
717            c0 = t[(i - 1) as usize] as FastSint;
718            f0 = usize::from(c0 > (c1 - f1 as FastSint));
719            sa[m as usize] = i as SaSint;
720            m -= (f0 & !f1) as FastSint;
721            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
722
723            c1 = t[(i - 2) as usize] as FastSint;
724            f1 = usize::from(c1 > (c0 - f0 as FastSint));
725            sa[m as usize] = (i - 1) as SaSint;
726            m -= (f1 & !f0) as FastSint;
727            buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
728
729            c0 = t[(i - 3) as usize] as FastSint;
730            f0 = usize::from(c0 > (c1 - f1 as FastSint));
731            sa[m as usize] = (i - 2) as SaSint;
732            m -= (f0 & !f1) as FastSint;
733            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
734
735            i -= 4;
736        }
737
738        let tail_limit = limit - 3;
739        while i >= tail_limit {
740            c1 = c0;
741            c0 = t[i as usize] as FastSint;
742            f1 = f0;
743            f0 = usize::from(c0 > (c1 - f1 as FastSint));
744            sa[m as usize] = (i + 1) as SaSint;
745            m -= (f0 & !f1) as FastSint;
746            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
747            i -= 1;
748        }
749
750        c1 = if i >= 0 {
751            t[i as usize] as FastSint
752        } else {
753            -1
754        };
755        f1 = usize::from(c1 > (c0 - f0 as FastSint));
756        sa[m as usize] = (i + 1) as SaSint;
757        m -= (f1 & !f0) as FastSint;
758        buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
759    }
760
761    (omp_block_start + omp_block_size - 1 - m) as SaSint
762}
763
764/// Internal helper: count and gather lms suffixes 8u (OpenMP variant).
765#[doc(hidden)]
766pub fn count_and_gather_lms_suffixes_8u_omp(
767    t: &[u8],
768    sa: &mut [SaSint],
769    n: SaSint,
770    buckets: &mut [SaSint],
771    threads: SaSint,
772    thread_state: &mut [ThreadState],
773) -> SaSint {
774    let mut m = 0;
775    let n_usize = usize::try_from(n).expect("n must be non-negative");
776    let omp_num_threads = if threads > 1 && n >= 65_536 {
777        usize::try_from(threads)
778            .expect("threads must be non-negative")
779            .min(thread_state.len())
780            .max(1)
781    } else {
782        1
783    };
784    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
785
786    if omp_num_threads == 1 {
787        return count_and_gather_lms_suffixes_8u(t, sa, n, buckets, 0, n as FastSint);
788    }
789
790    let sa_ptr = SyncMutPtr::new(sa);
791    run_rayon_with_threads(omp_num_threads, || {
792        thread_state[..omp_num_threads]
793            .par_iter_mut()
794            .enumerate()
795            .for_each(|(omp_thread_num, state)| {
796                let omp_block_start = omp_thread_num * omp_block_stride;
797                let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
798                    omp_block_stride
799                } else {
800                    n_usize - omp_block_start
801                };
802
803                // SAFETY: each thread writes only to sa[omp_block_start..+omp_block_size].
804                let sa = unsafe { sa_ptr.as_slice() };
805
806                state.position = FastSint::try_from(omp_block_start + omp_block_size)
807                    .expect("position must fit FastSint");
808                state.m = FastSint::try_from(count_and_gather_lms_suffixes_8u(
809                    t,
810                    sa,
811                    n,
812                    &mut state.buckets,
813                    FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
814                    FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
815                ))
816                .expect("m must fit FastSint");
817
818                if state.m > 0 {
819                    let position =
820                        usize::try_from(state.position).expect("position must be non-negative");
821                    state.last_lms_suffix =
822                        FastSint::try_from(sa[position - 1]).expect("suffix must fit FastSint");
823                }
824            });
825    });
826
827    buckets.fill(0);
828
829    for tnum in (0..omp_num_threads).rev() {
830        let state = &mut thread_state[tnum];
831        m += SaSint::try_from(state.m).expect("m must fit SaSint");
832
833        if tnum + 1 < omp_num_threads && state.m > 0 {
834            let position = usize::try_from(state.position).expect("position must be non-negative");
835            let count = usize::try_from(state.m).expect("m must be non-negative");
836            let dst = n_usize - usize::try_from(m).expect("m must be non-negative");
837            sa.copy_within(position - count..position, dst);
838        }
839
840        for s in 0..4 * ALPHABET_SIZE {
841            let a = buckets[s];
842            let b = state.buckets[s];
843            buckets[s] = a + b;
844            state.buckets[s] = a;
845        }
846    }
847
848    m
849}
850
851/// Internal helper: count and gather lms suffixes 32s 4k.
852#[doc(hidden)]
853pub fn count_and_gather_lms_suffixes_32s_4k(
854    t: &[SaSint],
855    sa: &mut [SaSint],
856    n: SaSint,
857    k: SaSint,
858    buckets: &mut [SaSint],
859    omp_block_start: FastSint,
860    omp_block_size: FastSint,
861) -> SaSint {
862    buckets.fill(0);
863    let n = n as FastSint;
864    let _k = k as FastSint;
865    let mut m = omp_block_start + omp_block_size - 1;
866
867    if omp_block_size > 0 {
868        let prefetch_distance = 64 as FastSint;
869        let mut j = m + 1;
870        let mut c0 = t[m as usize] as FastSint;
871        let mut c1 = -1;
872
873        while j < n {
874            c1 = t[j as usize] as FastSint;
875            if c1 != c0 {
876                break;
877            }
878            j += 1;
879        }
880
881        let mut f0 = usize::from(c0 >= c1);
882        let mut f1: usize;
883        let mut i = m - 1;
884        let limit = omp_block_start + prefetch_distance + 3;
885
886        while i >= limit {
887            let _prefetch_index = i - 2 * prefetch_distance;
888            c1 = t[i as usize] as FastSint;
889            f1 = usize::from(c1 > (c0 - f0 as FastSint));
890            sa[m as usize] = (i + 1) as SaSint;
891            m -= (f1 & !f0) as FastSint;
892            buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
893
894            c0 = t[(i - 1) as usize] as FastSint;
895            f0 = usize::from(c0 > (c1 - f1 as FastSint));
896            sa[m as usize] = i as SaSint;
897            m -= (f0 & !f1) as FastSint;
898            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
899
900            c1 = t[(i - 2) as usize] as FastSint;
901            f1 = usize::from(c1 > (c0 - f0 as FastSint));
902            sa[m as usize] = (i - 1) as SaSint;
903            m -= (f1 & !f0) as FastSint;
904            buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
905
906            c0 = t[(i - 3) as usize] as FastSint;
907            f0 = usize::from(c0 > (c1 - f1 as FastSint));
908            sa[m as usize] = (i - 2) as SaSint;
909            m -= (f0 & !f1) as FastSint;
910            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
911
912            i -= 4;
913        }
914
915        let tail_limit = omp_block_start;
916        while i >= tail_limit {
917            c1 = c0;
918            c0 = t[i as usize] as FastSint;
919            f1 = f0;
920            f0 = usize::from(c0 > (c1 - f1 as FastSint));
921            sa[m as usize] = (i + 1) as SaSint;
922            m -= (f0 & !f1) as FastSint;
923            buckets[buckets_index4(c1 as usize, f1 + f1 + f0)] += 1;
924            i -= 1;
925        }
926
927        c1 = if i >= 0 {
928            t[i as usize] as FastSint
929        } else {
930            -1
931        };
932        f1 = usize::from(c1 > (c0 - f0 as FastSint));
933        sa[m as usize] = (i + 1) as SaSint;
934        m -= (f1 & !f0) as FastSint;
935        buckets[buckets_index4(c0 as usize, f0 + f0 + f1)] += 1;
936    }
937
938    (omp_block_start + omp_block_size - 1 - m) as SaSint
939}
940
941/// Internal helper: count and gather lms suffixes 32s 2k.
942#[doc(hidden)]
943pub fn count_and_gather_lms_suffixes_32s_2k(
944    t: &[SaSint],
945    sa: &mut [SaSint],
946    n: SaSint,
947    k: SaSint,
948    buckets: &mut [SaSint],
949    omp_block_start: FastSint,
950    omp_block_size: FastSint,
951) -> SaSint {
952    buckets.fill(0);
953    let n = n as FastSint;
954    let _k = k as FastSint;
955    let mut m = omp_block_start + omp_block_size - 1;
956
957    if omp_block_size > 0 {
958        let prefetch_distance = 64 as FastSint;
959        let mut j = m + 1;
960        let mut c0 = t[m as usize] as FastSint;
961        let mut c1 = -1;
962
963        while j < n {
964            c1 = t[j as usize] as FastSint;
965            if c1 != c0 {
966                break;
967            }
968            j += 1;
969        }
970
971        let mut f0 = usize::from(c0 >= c1);
972        let mut f1: usize;
973        let mut i = m - 1;
974        let limit = omp_block_start + prefetch_distance + 3;
975
976        while i >= limit {
977            let _prefetch_index = i - 2 * prefetch_distance;
978            c1 = t[i as usize] as FastSint;
979            f1 = usize::from(c1 > (c0 - f0 as FastSint));
980            sa[m as usize] = (i + 1) as SaSint;
981            m -= (f1 & !f0) as FastSint;
982            buckets[buckets_index2(c0 as usize, f1 & !f0)] += 1;
983
984            c0 = t[(i - 1) as usize] as FastSint;
985            f0 = usize::from(c0 > (c1 - f1 as FastSint));
986            sa[m as usize] = i as SaSint;
987            m -= (f0 & !f1) as FastSint;
988            buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
989
990            c1 = t[(i - 2) as usize] as FastSint;
991            f1 = usize::from(c1 > (c0 - f0 as FastSint));
992            sa[m as usize] = (i - 1) as SaSint;
993            m -= (f1 & !f0) as FastSint;
994            buckets[buckets_index2(c0 as usize, f1 & !f0)] += 1;
995
996            c0 = t[(i - 3) as usize] as FastSint;
997            f0 = usize::from(c0 > (c1 - f1 as FastSint));
998            sa[m as usize] = (i - 2) as SaSint;
999            m -= (f0 & !f1) as FastSint;
1000            buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
1001
1002            i -= 4;
1003        }
1004
1005        let tail_limit = omp_block_start;
1006        while i >= tail_limit {
1007            c1 = c0;
1008            c0 = t[i as usize] as FastSint;
1009            f1 = f0;
1010            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1011            sa[m as usize] = (i + 1) as SaSint;
1012            m -= (f0 & !f1) as FastSint;
1013            buckets[buckets_index2(c1 as usize, f0 & !f1)] += 1;
1014            i -= 1;
1015        }
1016
1017        c1 = if i >= 0 {
1018            t[i as usize] as FastSint
1019        } else {
1020            -1
1021        };
1022        f1 = usize::from(c1 > (c0 - f0 as FastSint));
1023        sa[m as usize] = (i + 1) as SaSint;
1024        m -= (f1 & !f0) as FastSint;
1025        buckets[buckets_index2(c0 as usize, f1 & !f0)] += 1;
1026    }
1027
1028    (omp_block_start + omp_block_size - 1 - m) as SaSint
1029}
1030
1031/// Internal helper: count and gather compacted lms suffixes 32s 2k.
1032#[doc(hidden)]
1033pub fn count_and_gather_compacted_lms_suffixes_32s_2k(
1034    t: &[SaSint],
1035    sa: &mut [SaSint],
1036    n: SaSint,
1037    k: SaSint,
1038    buckets: &mut [SaSint],
1039    omp_block_start: FastSint,
1040    omp_block_size: FastSint,
1041) -> SaSint {
1042    buckets.fill(0);
1043    let n_usize = usize::try_from(n).expect("n must be non-negative");
1044    let _k_usize = usize::try_from(k).expect("k must be non-negative");
1045    let block_start =
1046        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
1047    let block_size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
1048    let mut m = block_start + block_size - 1;
1049
1050    if omp_block_size > 0 {
1051        let mut j = m + 1;
1052        let mut c0 = t[m] as FastSint;
1053        let mut c1 = -1;
1054
1055        while j < n_usize {
1056            c1 = t[j] as FastSint;
1057            if c1 != c0 {
1058                break;
1059            }
1060            j += 1;
1061        }
1062
1063        let mut f0 = usize::from(c0 >= c1);
1064        let mut f1: usize;
1065        let mut i = m as FastSint - 1;
1066        let limit = block_start as FastSint + 3;
1067
1068        while i >= limit {
1069            c1 = t[i as usize] as FastSint;
1070            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1071            sa[m] = (i + 1) as SaSint;
1072            m -= f1 & !f0 & usize::from(c0 >= 0);
1073            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1074
1075            c0 = t[(i - 1) as usize] as FastSint;
1076            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1077            sa[m] = i as SaSint;
1078            m -= f0 & !f1 & usize::from(c1 >= 0);
1079            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1080
1081            c1 = t[(i - 2) as usize] as FastSint;
1082            f1 = usize::from(c1 > (c0 - f0 as FastSint));
1083            sa[m] = (i - 1) as SaSint;
1084            m -= f1 & !f0 & usize::from(c0 >= 0);
1085            buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1086
1087            c0 = t[(i - 3) as usize] as FastSint;
1088            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1089            sa[m] = (i - 2) as SaSint;
1090            m -= f0 & !f1 & usize::from(c1 >= 0);
1091            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1092
1093            i -= 4;
1094        }
1095
1096        let tail_limit = block_start as FastSint;
1097        while i >= tail_limit {
1098            c1 = c0;
1099            c0 = t[i as usize] as FastSint;
1100            f1 = f0;
1101            f0 = usize::from(c0 > (c1 - f1 as FastSint));
1102            sa[m] = (i + 1) as SaSint;
1103            m -= f0 & !f1 & usize::from(c1 >= 0);
1104            buckets[buckets_index2((c1 as SaSint & SAINT_MAX) as usize, f0 & !f1)] += 1;
1105            i -= 1;
1106        }
1107
1108        c1 = if i >= 0 {
1109            t[i as usize] as FastSint
1110        } else {
1111            -1
1112        };
1113        f1 = usize::from(c1 > (c0 - f0 as FastSint));
1114        sa[m] = (i + 1) as SaSint;
1115        m -= f1 & !f0 & usize::from(c0 >= 0);
1116        buckets[buckets_index2((c0 as SaSint & SAINT_MAX) as usize, f1 & !f0)] += 1;
1117    }
1118
1119    (block_start + block_size - 1 - m) as SaSint
1120}
1121
1122/// Internal helper: get bucket stride.
1123#[doc(hidden)]
1124pub fn get_bucket_stride(
1125    free_space: FastSint,
1126    bucket_size: FastSint,
1127    num_buckets: FastSint,
1128) -> FastSint {
1129    let bucket_size_1024 = (bucket_size + 1023) & (-1024);
1130    if free_space / (num_buckets - 1) >= bucket_size_1024 {
1131        return bucket_size_1024;
1132    }
1133    let bucket_size_16 = (bucket_size + 15) & (-16);
1134    if free_space / (num_buckets - 1) >= bucket_size_16 {
1135        return bucket_size_16;
1136    }
1137    bucket_size
1138}
1139
1140/// Internal helper: count and gather lms suffixes 32s 4k nofs (OpenMP variant).
1141#[doc(hidden)]
1142pub fn count_and_gather_lms_suffixes_32s_4k_nofs_omp(
1143    t: &[SaSint],
1144    sa: &mut [SaSint],
1145    n: SaSint,
1146    k: SaSint,
1147    buckets: &mut [SaSint],
1148    threads: SaSint,
1149) -> SaSint {
1150    let m;
1151    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1152
1153    if omp_num_threads == 1 {
1154        m = count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint);
1155    } else {
1156        count_lms_suffixes_32s_4k(t, n, k, buckets);
1157        m = gather_lms_suffixes_32s(t, sa, n);
1158    }
1159
1160    m
1161}
1162
1163/// Internal helper: count and gather lms suffixes 32s 2k nofs (OpenMP variant).
1164#[doc(hidden)]
1165pub fn count_and_gather_lms_suffixes_32s_2k_nofs_omp(
1166    t: &[SaSint],
1167    sa: &mut [SaSint],
1168    n: SaSint,
1169    k: SaSint,
1170    buckets: &mut [SaSint],
1171    threads: SaSint,
1172) -> SaSint {
1173    let m;
1174    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1175
1176    if omp_num_threads == 1 {
1177        m = count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1178    } else {
1179        count_lms_suffixes_32s_2k(t, n, k, buckets);
1180        m = gather_lms_suffixes_32s(t, sa, n);
1181    }
1182
1183    m
1184}
1185
1186/// Internal helper: count and gather compacted lms suffixes 32s 2k nofs (OpenMP variant).
1187#[doc(hidden)]
1188pub fn count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
1189    t: &[SaSint],
1190    sa: &mut [SaSint],
1191    n: SaSint,
1192    k: SaSint,
1193    buckets: &mut [SaSint],
1194    threads: SaSint,
1195) -> SaSint {
1196    let m;
1197    let omp_num_threads = if threads > 1 && n >= 65_536 { 2 } else { 1 };
1198
1199    if omp_num_threads == 1 {
1200        m = count_and_gather_compacted_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1201    } else {
1202        count_compacted_lms_suffixes_32s_2k(t, n, k, buckets);
1203        m = gather_compacted_lms_suffixes_32s(t, sa, n);
1204    }
1205
1206    m
1207}
1208
1209/// Internal helper: count and gather lms suffixes 32s 4k fs (OpenMP variant).
1210#[doc(hidden)]
1211pub fn count_and_gather_lms_suffixes_32s_4k_fs_omp(
1212    t: &[SaSint],
1213    sa: &mut [SaSint],
1214    n: SaSint,
1215    k: SaSint,
1216    buckets: &mut [SaSint],
1217    local_buckets: SaSint,
1218    threads: SaSint,
1219    thread_state: &mut [ThreadState],
1220) -> SaSint {
1221    let n_usize = usize::try_from(n).expect("n must be non-negative");
1222    let k_usize = usize::try_from(k).expect("k must be non-negative");
1223    let omp_num_threads = usize::try_from(threads).expect("threads must be non-negative");
1224    let bucket_size = FastSint::try_from(4 * k_usize).expect("bucket size must fit FastSint");
1225
1226    if omp_num_threads <= 1 || n < 65_536 {
1227        return count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint);
1228    }
1229
1230    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
1231    let free_space = if local_buckets == 1 {
1232        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1233    } else if local_buckets > 1 {
1234        FastSint::try_from(local_buckets).expect("free space must fit FastSint")
1235    } else {
1236        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1237    };
1238    let bucket_stride = get_bucket_stride(
1239        free_space,
1240        bucket_size,
1241        FastSint::try_from(omp_num_threads).expect("thread count must fit FastSint"),
1242    );
1243    let bucket_size_usize = usize::try_from(bucket_size).expect("bucket size must be non-negative");
1244    let bucket_stride_usize =
1245        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1246    let workspace_len =
1247        bucket_size_usize + bucket_stride_usize.saturating_mul(omp_num_threads.saturating_sub(1));
1248    let mut workspace = vec![0; workspace_len];
1249
1250    {
1251        let sa_ptr = SyncMutPtr::new(sa);
1252        let ws_ptr = SyncMutPtr::new(&mut workspace);
1253        run_rayon_with_threads(omp_num_threads, || {
1254            thread_state[..omp_num_threads]
1255                .par_iter_mut()
1256                .enumerate()
1257                .for_each(|(omp_thread_num, state)| {
1258                    let omp_block_start = omp_thread_num * omp_block_stride;
1259                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1260                        omp_block_stride
1261                    } else {
1262                        n_usize - omp_block_start
1263                    };
1264                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1265                    let workspace_start = workspace_end - bucket_size_usize;
1266                    // SAFETY: per-thread disjoint sa and workspace ranges.
1267                    let sa = unsafe { sa_ptr.as_slice() };
1268                    let workspace = unsafe { ws_ptr.as_slice() };
1269                    let count = count_and_gather_lms_suffixes_32s_4k(
1270                        t,
1271                        sa,
1272                        n,
1273                        k,
1274                        &mut workspace[workspace_start..workspace_end],
1275                        omp_block_start as FastSint,
1276                        omp_block_size as FastSint,
1277                    );
1278                    state.position = (omp_block_start + omp_block_size) as FastSint;
1279                    state.count = count as FastSint;
1280                });
1281        });
1282    }
1283
1284    let mut m = 0;
1285    for t in (0..omp_num_threads).rev() {
1286        m += thread_state[t].count as SaSint;
1287
1288        if t + 1 != omp_num_threads && thread_state[t].count > 0 {
1289            let src_end =
1290                usize::try_from(thread_state[t].position).expect("position must be non-negative");
1291            let src_start = src_end
1292                - usize::try_from(thread_state[t].count).expect("count must be non-negative");
1293            let dst_start = usize::try_from(n - m).expect("destination must be non-negative");
1294            sa.copy_within(src_start..src_end, dst_start);
1295        }
1296    }
1297
1298    let omp_num_threads = omp_num_threads - 1;
1299    let omp_block_stride = (bucket_size_usize / omp_num_threads) & !15usize;
1300    {
1301        let ws_ptr = SyncMutPtr::new(&mut workspace);
1302        run_rayon_with_threads(omp_num_threads, || {
1303            (0..omp_num_threads)
1304                .into_par_iter()
1305                .for_each(|omp_thread_num| {
1306                    let omp_block_start = omp_thread_num * omp_block_stride;
1307                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1308                        omp_block_stride
1309                    } else {
1310                        bucket_size_usize - omp_block_start
1311                    };
1312                    // SAFETY: per-thread disjoint absolute write ranges.
1313                    let workspace = unsafe { ws_ptr.as_slice() };
1314                    accumulate_counts_s32(
1315                        &mut workspace[omp_block_start..],
1316                        omp_block_size as FastSint,
1317                        bucket_stride,
1318                        FastSint::try_from(omp_num_threads + 1)
1319                            .expect("thread count must fit FastSint"),
1320                    );
1321                });
1322        });
1323    }
1324
1325    let accumulated_start = omp_num_threads * bucket_stride_usize;
1326    buckets[..bucket_size_usize]
1327        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size_usize]);
1328    m
1329}
1330
1331/// Internal helper: count and gather lms suffixes 32s 2k fs (OpenMP variant).
1332#[doc(hidden)]
1333pub fn count_and_gather_lms_suffixes_32s_2k_fs_omp(
1334    t: &[SaSint],
1335    sa: &mut [SaSint],
1336    n: SaSint,
1337    k: SaSint,
1338    buckets: &mut [SaSint],
1339    local_buckets: SaSint,
1340    threads: SaSint,
1341    thread_state: &mut [ThreadState],
1342) -> SaSint {
1343    let n_usize = usize::try_from(n).expect("n must be non-negative");
1344    let k_usize = usize::try_from(k).expect("k must be non-negative");
1345    let omp_num_threads = usize::try_from(threads).expect("threads must be non-negative");
1346    let bucket_size = FastSint::try_from(2 * k_usize).expect("bucket size must fit FastSint");
1347
1348    if omp_num_threads <= 1 || n < 65_536 {
1349        return count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1350    }
1351
1352    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
1353    let free_space = if local_buckets == 1 {
1354        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1355    } else if local_buckets > 1 {
1356        FastSint::try_from(local_buckets).expect("free space must fit FastSint")
1357    } else {
1358        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1359    };
1360    let bucket_stride = get_bucket_stride(
1361        free_space,
1362        bucket_size,
1363        FastSint::try_from(omp_num_threads).expect("thread count must fit FastSint"),
1364    );
1365    let bucket_size_usize = usize::try_from(bucket_size).expect("bucket size must be non-negative");
1366    let bucket_stride_usize =
1367        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1368    let workspace_len =
1369        bucket_size_usize + bucket_stride_usize.saturating_mul(omp_num_threads.saturating_sub(1));
1370    let mut workspace = vec![0; workspace_len];
1371
1372    {
1373        let sa_ptr = SyncMutPtr::new(sa);
1374        let ws_ptr = SyncMutPtr::new(&mut workspace);
1375        run_rayon_with_threads(omp_num_threads, || {
1376            thread_state[..omp_num_threads]
1377                .par_iter_mut()
1378                .enumerate()
1379                .for_each(|(omp_thread_num, state)| {
1380                    let omp_block_start = omp_thread_num * omp_block_stride;
1381                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1382                        omp_block_stride
1383                    } else {
1384                        n_usize - omp_block_start
1385                    };
1386                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1387                    let workspace_start = workspace_end - bucket_size_usize;
1388                    // SAFETY: per-thread disjoint sa and workspace ranges.
1389                    let sa = unsafe { sa_ptr.as_slice() };
1390                    let workspace = unsafe { ws_ptr.as_slice() };
1391                    let count = count_and_gather_lms_suffixes_32s_2k(
1392                        t,
1393                        sa,
1394                        n,
1395                        k,
1396                        &mut workspace[workspace_start..workspace_end],
1397                        omp_block_start as FastSint,
1398                        omp_block_size as FastSint,
1399                    );
1400                    state.position = (omp_block_start + omp_block_size) as FastSint;
1401                    state.count = count as FastSint;
1402                });
1403        });
1404    }
1405
1406    let mut m = 0;
1407    for t in (0..omp_num_threads).rev() {
1408        m += thread_state[t].count as SaSint;
1409        if t + 1 != omp_num_threads && thread_state[t].count > 0 {
1410            let src_end =
1411                usize::try_from(thread_state[t].position).expect("position must be non-negative");
1412            let src_start = src_end
1413                - usize::try_from(thread_state[t].count).expect("count must be non-negative");
1414            let dst_start = usize::try_from(n - m).expect("destination must be non-negative");
1415            sa.copy_within(src_start..src_end, dst_start);
1416        }
1417    }
1418
1419    let omp_num_threads = omp_num_threads - 1;
1420    let omp_block_stride = (bucket_size_usize / omp_num_threads) & !15usize;
1421    {
1422        let ws_ptr = SyncMutPtr::new(&mut workspace);
1423        run_rayon_with_threads(omp_num_threads, || {
1424            (0..omp_num_threads)
1425                .into_par_iter()
1426                .for_each(|omp_thread_num| {
1427                    let omp_block_start = omp_thread_num * omp_block_stride;
1428                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
1429                        omp_block_stride
1430                    } else {
1431                        bucket_size_usize - omp_block_start
1432                    };
1433                    // SAFETY: per-thread disjoint absolute write ranges.
1434                    let workspace = unsafe { ws_ptr.as_slice() };
1435                    accumulate_counts_s32(
1436                        &mut workspace[omp_block_start..],
1437                        omp_block_size as FastSint,
1438                        bucket_stride,
1439                        FastSint::try_from(omp_num_threads + 1)
1440                            .expect("thread count must fit FastSint"),
1441                    );
1442                });
1443        });
1444    }
1445
1446    let accumulated_start = omp_num_threads * bucket_stride_usize;
1447    buckets[..bucket_size_usize]
1448        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size_usize]);
1449    m
1450}
1451
1452/// Internal helper: count and gather compacted lms suffixes 32s 2k fs (OpenMP variant).
1453#[doc(hidden)]
1454pub fn count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
1455    t: &[SaSint],
1456    sa: &mut [SaSint],
1457    n: SaSint,
1458    k: SaSint,
1459    buckets: &mut [SaSint],
1460    _local_buckets: SaSint,
1461    threads: SaSint,
1462    thread_state: &mut [ThreadState],
1463) {
1464    let n_usize = usize::try_from(n).expect("n must be non-negative");
1465    let k_usize = usize::try_from(k).expect("k must be non-negative");
1466    let thread_count = usize::try_from(threads).expect("threads must be non-negative");
1467    let bucket_size = 2 * k_usize;
1468
1469    if thread_count <= 1 || n < 65_536 {
1470        let _ =
1471            count_and_gather_compacted_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
1472        return;
1473    }
1474
1475    if thread_state.len() < thread_count || sa.len() < 2 * n_usize {
1476        let _ =
1477            count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(t, sa, n, k, buckets, threads);
1478        return;
1479    }
1480
1481    let omp_block_stride = (n_usize / thread_count) & !15usize;
1482    let free_space = if _local_buckets != 0 {
1483        FastSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("free space must fit FastSint")
1484    } else {
1485        FastSint::try_from(buckets.len()).expect("free space must fit FastSint")
1486    };
1487    let bucket_stride = get_bucket_stride(
1488        free_space,
1489        FastSint::try_from(bucket_size).expect("bucket size must fit FastSint"),
1490        FastSint::try_from(thread_count).expect("thread count must fit FastSint"),
1491    );
1492    let bucket_stride_usize =
1493        usize::try_from(bucket_stride).expect("bucket stride must be non-negative");
1494    let workspace_len =
1495        bucket_size + bucket_stride_usize.saturating_mul(thread_count.saturating_sub(1));
1496    let mut workspace = vec![0; workspace_len];
1497
1498    let usable_thread_state_len = thread_count.min(thread_state.len());
1499
1500    {
1501        let sa_ptr = SyncMutPtr::new(sa);
1502        let ws_ptr = SyncMutPtr::new(&mut workspace);
1503        let state_ptr = SyncMutPtr::new(thread_state);
1504        run_rayon_with_threads(thread_count, || {
1505            (0..thread_count)
1506                .into_par_iter()
1507                .for_each(|omp_thread_num| {
1508                    let omp_block_start = omp_thread_num * omp_block_stride;
1509                    let omp_block_size = if omp_thread_num + 1 < thread_count {
1510                        omp_block_stride
1511                    } else {
1512                        n_usize - omp_block_start
1513                    };
1514
1515                    let workspace_end = workspace_len - omp_thread_num * bucket_stride_usize;
1516                    let workspace_start = workspace_end - bucket_size;
1517                    // SAFETY: per-thread disjoint sa block, workspace range, and thread_state index.
1518                    let sa = unsafe { sa_ptr.as_slice() };
1519                    let workspace = unsafe { ws_ptr.as_slice() };
1520                    let count = count_and_gather_compacted_lms_suffixes_32s_2k(
1521                        t,
1522                        &mut sa[n_usize..],
1523                        n,
1524                        k,
1525                        &mut workspace[workspace_start..workspace_end],
1526                        omp_block_start as FastSint,
1527                        omp_block_size as FastSint,
1528                    );
1529
1530                    if omp_thread_num < usable_thread_state_len {
1531                        let states = unsafe { state_ptr.as_slice() };
1532                        states[omp_thread_num].position =
1533                            (omp_block_start + omp_block_size) as FastSint;
1534                        states[omp_thread_num].count = count as FastSint;
1535                    }
1536                });
1537        });
1538    }
1539
1540    let mut m = 0usize;
1541    for omp_thread_num in (0..thread_count).rev() {
1542        let count = usize::try_from(thread_state[omp_thread_num].count)
1543            .expect("count must be non-negative");
1544        m += count;
1545        if count > 0 {
1546            let position = usize::try_from(thread_state[omp_thread_num].position)
1547                .expect("position must be non-negative");
1548            let src_start = n_usize + position - count;
1549            let src_end = n_usize + position;
1550            let dst_start = n_usize - m;
1551            sa.copy_within(src_start..src_end, dst_start);
1552        }
1553    }
1554
1555    let accumulation_threads = thread_count;
1556    let omp_block_stride = (bucket_size / accumulation_threads) & !15usize;
1557    {
1558        let ws_ptr = SyncMutPtr::new(&mut workspace);
1559        run_rayon_with_threads(accumulation_threads, || {
1560            (0..accumulation_threads)
1561                .into_par_iter()
1562                .for_each(|omp_thread_num| {
1563                    let omp_block_start = omp_thread_num * omp_block_stride;
1564                    let omp_block_size = if omp_thread_num + 1 < accumulation_threads {
1565                        omp_block_stride
1566                    } else {
1567                        bucket_size - omp_block_start
1568                    };
1569                    // SAFETY: per-thread disjoint absolute write ranges.
1570                    let workspace = unsafe { ws_ptr.as_slice() };
1571                    accumulate_counts_s32(
1572                        &mut workspace[omp_block_start..],
1573                        omp_block_size as FastSint,
1574                        bucket_stride,
1575                        FastSint::try_from(thread_count).expect("thread count must fit FastSint"),
1576                    );
1577                });
1578        });
1579    }
1580    let accumulated_start = (accumulation_threads - 1) * bucket_stride_usize;
1581    buckets[..bucket_size]
1582        .copy_from_slice(&workspace[accumulated_start..accumulated_start + bucket_size]);
1583}
1584
1585/// Internal helper: count and gather lms suffixes 32s 4k (OpenMP variant).
1586#[doc(hidden)]
1587pub fn count_and_gather_lms_suffixes_32s_4k_omp(
1588    t: &[SaSint],
1589    sa: &mut [SaSint],
1590    n: SaSint,
1591    k: SaSint,
1592    buckets: &mut [SaSint],
1593    local_buckets: SaSint,
1594    threads: SaSint,
1595    thread_state: &mut [ThreadState],
1596) -> SaSint {
1597    let free_space = if local_buckets != 0 {
1598        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1599    } else {
1600        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1601    };
1602    let threads_fast = threads as FastSint;
1603    let mut max_threads = (free_space / (((4 * k as FastSint) + 15) & -16)).min(threads_fast);
1604
1605    if max_threads > 1 && n >= 65_536 && n / k >= 2 {
1606        let thread_cap = (n / (16 * k)) as FastSint;
1607        if max_threads > thread_cap {
1608            max_threads = thread_cap;
1609        }
1610        return count_and_gather_lms_suffixes_32s_4k_fs_omp(
1611            t,
1612            sa,
1613            n,
1614            k,
1615            buckets,
1616            local_buckets,
1617            max_threads.max(2) as SaSint,
1618            thread_state,
1619        );
1620    }
1621
1622    if threads > 1 && n >= 65_536 {
1623        count_lms_suffixes_32s_4k(t, n, k, buckets);
1624        gather_lms_suffixes_32s(t, sa, n)
1625    } else {
1626        count_and_gather_lms_suffixes_32s_4k(t, sa, n, k, buckets, 0, n as FastSint)
1627    }
1628}
1629
1630/// Internal helper: count and gather lms suffixes 32s 2k (OpenMP variant).
1631#[doc(hidden)]
1632pub fn count_and_gather_lms_suffixes_32s_2k_omp(
1633    t: &[SaSint],
1634    sa: &mut [SaSint],
1635    n: SaSint,
1636    k: SaSint,
1637    buckets: &mut [SaSint],
1638    local_buckets: SaSint,
1639    threads: SaSint,
1640    thread_state: &mut [ThreadState],
1641) -> SaSint {
1642    let free_space = if local_buckets != 0 {
1643        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1644    } else {
1645        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1646    };
1647    let threads_fast = threads as FastSint;
1648    let mut max_threads = (free_space / (((2 * k as FastSint) + 15) & -16)).min(threads_fast);
1649
1650    if max_threads > 1 && n >= 65_536 && n / k >= 2 {
1651        let thread_cap = (n / (8 * k)) as FastSint;
1652        if max_threads > thread_cap {
1653            max_threads = thread_cap;
1654        }
1655        return count_and_gather_lms_suffixes_32s_2k_fs_omp(
1656            t,
1657            sa,
1658            n,
1659            k,
1660            buckets,
1661            local_buckets,
1662            max_threads.max(2) as SaSint,
1663            thread_state,
1664        );
1665    }
1666
1667    if threads > 1 && n >= 65_536 {
1668        count_lms_suffixes_32s_2k(t, n, k, buckets);
1669        gather_lms_suffixes_32s(t, sa, n)
1670    } else {
1671        count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint)
1672    }
1673}
1674
1675/// Internal helper: count and gather compacted lms suffixes 32s 2k (OpenMP variant).
1676#[doc(hidden)]
1677pub fn count_and_gather_compacted_lms_suffixes_32s_2k_omp(
1678    t: &[SaSint],
1679    sa: &mut [SaSint],
1680    n: SaSint,
1681    k: SaSint,
1682    buckets: &mut [SaSint],
1683    local_buckets: SaSint,
1684    threads: SaSint,
1685    thread_state: &mut [ThreadState],
1686) {
1687    let free_space = if local_buckets != 0 {
1688        LIBSAIS_LOCAL_BUFFER_SIZE as FastSint
1689    } else {
1690        FastSint::try_from(buckets.len()).expect("bucket length must fit FastSint")
1691    };
1692    let threads_fast = threads as FastSint;
1693    let mut max_threads = (free_space / (((2 * k as FastSint) + 15) & -16)).min(threads_fast);
1694
1695    if local_buckets == 0 && max_threads > 1 && n >= 65_536 && n / k >= 2 {
1696        let thread_cap = (n / (8 * k)) as FastSint;
1697        if max_threads > thread_cap {
1698            max_threads = thread_cap;
1699        }
1700        count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
1701            t,
1702            sa,
1703            n,
1704            k,
1705            buckets,
1706            local_buckets,
1707            max_threads.max(2) as SaSint,
1708            thread_state,
1709        );
1710        return;
1711    }
1712
1713    let _ = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(t, sa, n, k, buckets, threads);
1714}
1715
1716/// Internal helper: count suffixes 32s.
1717#[doc(hidden)]
1718pub fn count_suffixes_32s(t: &[SaSint], n: SaSint, k: SaSint, buckets: &mut [SaSint]) {
1719    let n_usize = usize::try_from(n).expect("n must be non-negative");
1720    let k_usize = usize::try_from(k).expect("k must be non-negative");
1721    buckets[..k_usize].fill(0);
1722
1723    let mut i = 0usize;
1724    let mut j = n_usize.saturating_sub(7);
1725    while i < j {
1726        buckets[t[i] as usize] += 1;
1727        buckets[t[i + 1] as usize] += 1;
1728        buckets[t[i + 2] as usize] += 1;
1729        buckets[t[i + 3] as usize] += 1;
1730        buckets[t[i + 4] as usize] += 1;
1731        buckets[t[i + 5] as usize] += 1;
1732        buckets[t[i + 6] as usize] += 1;
1733        buckets[t[i + 7] as usize] += 1;
1734        i += 8;
1735    }
1736
1737    j += 7;
1738    while i < j {
1739        buckets[t[i] as usize] += 1;
1740        i += 1;
1741    }
1742}
1743
1744/// Internal helper: initialize buckets start and end 8u.
1745#[doc(hidden)]
1746pub fn initialize_buckets_start_and_end_8u(
1747    buckets: &mut [SaSint],
1748    freq: Option<&mut [SaSint]>,
1749) -> SaSint {
1750    let start_offset = 6 * ALPHABET_SIZE;
1751    let end_offset = 7 * ALPHABET_SIZE;
1752    let mut k = -1isize;
1753    let mut sum = 0;
1754
1755    match freq {
1756        Some(freq) => {
1757            for j in 0..ALPHABET_SIZE {
1758                let i = buckets_index4(j, 0);
1759                let total = buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1760                buckets[start_offset + j] = sum;
1761                sum += total;
1762                buckets[end_offset + j] = sum;
1763                if total > 0 {
1764                    k = j as isize;
1765                }
1766                freq[j] = total;
1767            }
1768        }
1769        None => {
1770            for j in 0..ALPHABET_SIZE {
1771                let i = buckets_index4(j, 0);
1772                let total = buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1773                buckets[start_offset + j] = sum;
1774                sum += total;
1775                buckets[end_offset + j] = sum;
1776                if total > 0 {
1777                    k = j as isize;
1778                }
1779            }
1780        }
1781    }
1782
1783    (k + 1) as SaSint
1784}
1785
1786/// Internal helper: initialize buckets start and end 32s 6k.
1787#[doc(hidden)]
1788pub fn initialize_buckets_start_and_end_32s_6k(k: SaSint, buckets: &mut [SaSint]) {
1789    let k_usize = usize::try_from(k).expect("k must be non-negative");
1790    let start_offset = 4 * k_usize;
1791    let end_offset = 5 * k_usize;
1792    let mut sum = 0;
1793    for j in 0..k_usize {
1794        let i = buckets_index4(j, 0);
1795        buckets[start_offset + j] = sum;
1796        sum += buckets[i] + buckets[i + 1] + buckets[i + 2] + buckets[i + 3];
1797        buckets[end_offset + j] = sum;
1798    }
1799}
1800
1801/// Internal helper: initialize buckets start and end 32s 4k.
1802#[doc(hidden)]
1803pub fn initialize_buckets_start_and_end_32s_4k(k: SaSint, buckets: &mut [SaSint]) {
1804    let k_usize = usize::try_from(k).expect("k must be non-negative");
1805    let start_offset = 2 * k_usize;
1806    let end_offset = 3 * k_usize;
1807    let mut sum = 0;
1808    for j in 0..k_usize {
1809        let i = buckets_index2(j, 0);
1810        buckets[start_offset + j] = sum;
1811        sum += buckets[i] + buckets[i + 1];
1812        buckets[end_offset + j] = sum;
1813    }
1814}
1815
1816/// Internal helper: initialize buckets end 32s 2k.
1817#[doc(hidden)]
1818pub fn initialize_buckets_end_32s_2k(k: SaSint, buckets: &mut [SaSint]) {
1819    let k_usize = usize::try_from(k).expect("k must be non-negative");
1820    let mut sum0 = 0;
1821    for j in 0..k_usize {
1822        let i = buckets_index2(j, 0);
1823        sum0 += buckets[i] + buckets[i + 1];
1824        buckets[i] = sum0;
1825    }
1826}
1827
1828/// Internal helper: initialize buckets start and end 32s 2k.
1829#[doc(hidden)]
1830pub fn initialize_buckets_start_and_end_32s_2k(k: SaSint, buckets: &mut [SaSint]) {
1831    let k_usize = usize::try_from(k).expect("k must be non-negative");
1832    for j in 0..k_usize {
1833        let i = buckets_index2(j, 0);
1834        buckets[j] = buckets[i];
1835    }
1836    buckets[k_usize] = 0;
1837    for j in 1..k_usize {
1838        buckets[k_usize + j] = buckets[j - 1];
1839    }
1840}
1841
1842/// Internal helper: initialize buckets start 32s 1k.
1843#[doc(hidden)]
1844pub fn initialize_buckets_start_32s_1k(k: SaSint, buckets: &mut [SaSint]) {
1845    let k_usize = usize::try_from(k).expect("k must be non-negative");
1846    let mut sum = 0;
1847    for bucket in buckets.iter_mut().take(k_usize) {
1848        let tmp = *bucket;
1849        *bucket = sum;
1850        sum += tmp;
1851    }
1852}
1853
1854/// Internal helper: initialize buckets end 32s 1k.
1855#[doc(hidden)]
1856pub fn initialize_buckets_end_32s_1k(k: SaSint, buckets: &mut [SaSint]) {
1857    let k_usize = usize::try_from(k).expect("k must be non-negative");
1858    let mut sum = 0;
1859    for bucket in buckets.iter_mut().take(k_usize) {
1860        sum += *bucket;
1861        *bucket = sum;
1862    }
1863}
1864
1865/// Internal helper: initialize buckets for lms suffixes radix sort 8u.
1866#[doc(hidden)]
1867pub fn initialize_buckets_for_lms_suffixes_radix_sort_8u(
1868    t: &[u8],
1869    buckets: &mut [SaSint],
1870    mut first_lms_suffix: SaSint,
1871) -> SaSint {
1872    let mut f0 = 0usize;
1873    let mut f1: usize;
1874    let mut c0 = t[first_lms_suffix as usize] as FastSint;
1875    let mut c1: FastSint;
1876
1877    while {
1878        first_lms_suffix -= 1;
1879        first_lms_suffix >= 0
1880    } {
1881        c1 = c0;
1882        c0 = t[first_lms_suffix as usize] as FastSint;
1883        f1 = f0;
1884        f0 = usize::from(c0 > (c1 - f1 as FastSint));
1885        let idx = 4 * c1 as usize + (f1 + f1 + f0);
1886        buckets[idx] -= 1;
1887    }
1888    buckets[4 * c0 as usize + (f0 + f0)] -= 1;
1889
1890    let temp_offset = 4 * ALPHABET_SIZE;
1891    let mut sum = 0;
1892    for j in 0..ALPHABET_SIZE {
1893        let i = 4 * j;
1894        let tj = 2 * j;
1895        buckets[temp_offset + tj + 1] = sum;
1896        sum += buckets[i + 1] + buckets[i + 3];
1897        buckets[temp_offset + tj] = sum;
1898    }
1899    sum
1900}
1901
1902/// Internal helper: initialize buckets for lms suffixes radix sort 32s 2k.
1903#[doc(hidden)]
1904pub fn initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(
1905    t: &[SaSint],
1906    k: SaSint,
1907    buckets: &mut [SaSint],
1908    first_lms_suffix: SaSint,
1909) {
1910    let _k_usize = usize::try_from(k).expect("k must be non-negative");
1911    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 0)] += 1;
1912    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 1)] -= 1;
1913
1914    let mut sum0 = 0;
1915    let mut sum1 = 0;
1916    for j in 0..usize::try_from(k).unwrap() {
1917        let i = buckets_index2(j, 0);
1918        sum0 += buckets[i] + buckets[i + 1];
1919        sum1 += buckets[i + 1];
1920        buckets[i] = sum0;
1921        buckets[i + 1] = sum1;
1922    }
1923}
1924
1925/// Internal helper: initialize buckets for lms suffixes radix sort 32s 6k.
1926#[doc(hidden)]
1927pub fn initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(
1928    t: &[SaSint],
1929    k: SaSint,
1930    buckets: &mut [SaSint],
1931    mut first_lms_suffix: SaSint,
1932) -> SaSint {
1933    let mut f0 = 0usize;
1934    let mut f1: usize;
1935    let mut c0 = t[first_lms_suffix as usize] as FastSint;
1936    let mut c1: FastSint;
1937
1938    while {
1939        first_lms_suffix -= 1;
1940        first_lms_suffix >= 0
1941    } {
1942        c1 = c0;
1943        c0 = t[first_lms_suffix as usize] as FastSint;
1944        f1 = f0;
1945        f0 = usize::from(c0 > (c1 - f1 as FastSint));
1946        buckets[4 * c1 as usize + (f1 + f1 + f0)] -= 1;
1947    }
1948    buckets[4 * c0 as usize + (f0 + f0)] -= 1;
1949
1950    let temp_offset = 4 * usize::try_from(k).unwrap();
1951    let mut sum = 0;
1952    for j in 0..usize::try_from(k).unwrap() {
1953        let i = 4 * j;
1954        sum += buckets[i + 1] + buckets[i + 3];
1955        buckets[temp_offset + j] = sum;
1956    }
1957    sum
1958}
1959
1960/// Internal helper: initialize buckets for radix and partial sorting 32s 4k.
1961#[doc(hidden)]
1962pub fn initialize_buckets_for_radix_and_partial_sorting_32s_4k(
1963    t: &[SaSint],
1964    k: SaSint,
1965    buckets: &mut [SaSint],
1966    first_lms_suffix: SaSint,
1967) {
1968    let k_usize = usize::try_from(k).expect("k must be non-negative");
1969    let start_offset = 2 * k_usize;
1970    let end_offset = 3 * k_usize;
1971
1972    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 0)] += 1;
1973    buckets[buckets_index2(t[first_lms_suffix as usize] as usize, 1)] -= 1;
1974
1975    let mut sum0 = 0;
1976    let mut sum1 = 0;
1977    for j in 0..k_usize {
1978        let i = buckets_index2(j, 0);
1979        buckets[start_offset + j] = sum1;
1980        sum0 += buckets[i + 1];
1981        sum1 += buckets[i] + buckets[i + 1];
1982        buckets[i + 1] = sum0;
1983        buckets[end_offset + j] = sum1;
1984    }
1985}
1986
1987/// Internal helper: radix sort lms suffixes 8u.
1988#[doc(hidden)]
1989pub fn radix_sort_lms_suffixes_8u(
1990    t: &[u8],
1991    sa: &mut [SaSint],
1992    induction_bucket: &mut [SaSint],
1993    omp_block_start: FastSint,
1994    omp_block_size: FastSint,
1995) {
1996    let prefetch_distance = 64 as FastSint;
1997    let mut i = omp_block_start + omp_block_size - 1;
1998    let mut j = omp_block_start + prefetch_distance + 3;
1999
2000    while i >= j {
2001        let p0 = sa[i as usize];
2002        let idx0 = buckets_index2(t[p0 as usize] as usize, 0);
2003        induction_bucket[idx0] -= 1;
2004        sa[induction_bucket[idx0] as usize] = p0;
2005
2006        let p1 = sa[(i - 1) as usize];
2007        let idx1 = buckets_index2(t[p1 as usize] as usize, 0);
2008        induction_bucket[idx1] -= 1;
2009        sa[induction_bucket[idx1] as usize] = p1;
2010
2011        let p2 = sa[(i - 2) as usize];
2012        let idx2 = buckets_index2(t[p2 as usize] as usize, 0);
2013        induction_bucket[idx2] -= 1;
2014        sa[induction_bucket[idx2] as usize] = p2;
2015
2016        let p3 = sa[(i - 3) as usize];
2017        let idx3 = buckets_index2(t[p3 as usize] as usize, 0);
2018        induction_bucket[idx3] -= 1;
2019        sa[induction_bucket[idx3] as usize] = p3;
2020
2021        i -= 4;
2022    }
2023
2024    j -= prefetch_distance + 3;
2025    while i >= j {
2026        let p = sa[i as usize];
2027        let idx = buckets_index2(t[p as usize] as usize, 0);
2028        induction_bucket[idx] -= 1;
2029        sa[induction_bucket[idx] as usize] = p;
2030        i -= 1;
2031    }
2032}
2033
2034/// Internal helper: radix sort lms suffixes 8u (OpenMP variant).
2035#[doc(hidden)]
2036pub fn radix_sort_lms_suffixes_8u_omp(
2037    t: &[u8],
2038    sa: &mut [SaSint],
2039    n: SaSint,
2040    m: SaSint,
2041    flags: SaSint,
2042    buckets: &mut [SaSint],
2043    threads: SaSint,
2044    thread_state: &mut [ThreadState],
2045) {
2046    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
2047        buckets[4 * ALPHABET_SIZE] -= 1;
2048    }
2049
2050    let omp_num_threads = if threads > 1 && n >= 65_536 && m >= 65_536 {
2051        usize::try_from(threads)
2052            .expect("threads must be non-negative")
2053            .min(thread_state.len())
2054            .max(1)
2055    } else {
2056        1
2057    };
2058
2059    if omp_num_threads == 1 {
2060        radix_sort_lms_suffixes_8u(
2061            t,
2062            sa,
2063            &mut buckets[4 * ALPHABET_SIZE..],
2064            n as FastSint - m as FastSint + 1,
2065            m as FastSint - 1,
2066        );
2067        return;
2068    }
2069
2070    let src_bucket: &[SaSint] = &buckets[4 * ALPHABET_SIZE..];
2071    let m_values: Vec<FastSint> = thread_state[..omp_num_threads]
2072        .iter()
2073        .map(|s| s.m)
2074        .collect();
2075    let m_total = m as FastSint;
2076    let sa_ptr = SyncMutPtr::new(sa);
2077
2078    run_rayon_with_threads(omp_num_threads, || {
2079        thread_state[..omp_num_threads]
2080            .par_iter_mut()
2081            .enumerate()
2082            .for_each(|(thread_num, state)| {
2083                for (i, j) in (0..=buckets_index2(ALPHABET_SIZE - 1, 0))
2084                    .step_by(buckets_index2(1, 0))
2085                    .zip((buckets_index4(0, 1)..).step_by(buckets_index4(1, 0)))
2086                {
2087                    state.buckets[i] = src_bucket[i] - state.buckets[j];
2088                }
2089
2090                let mut omp_block_start: FastSint = 0;
2091                for &other_m in m_values[thread_num..omp_num_threads].iter().rev() {
2092                    omp_block_start += other_m;
2093                }
2094
2095                let mut omp_block_size = m_values[thread_num];
2096                if omp_block_start == m_total && omp_block_size > 0 {
2097                    omp_block_start -= 1;
2098                    omp_block_size -= 1;
2099                }
2100
2101                // SAFETY: per-thread disjoint sa block determined by `n - omp_block_start`.
2102                let sa = unsafe { sa_ptr.as_slice() };
2103                radix_sort_lms_suffixes_8u(
2104                    t,
2105                    sa,
2106                    &mut state.buckets,
2107                    n as FastSint - omp_block_start,
2108                    omp_block_size,
2109                );
2110            });
2111    });
2112}
2113
2114/// Internal helper: radix sort lms suffixes 32s 6k.
2115#[doc(hidden)]
2116pub fn radix_sort_lms_suffixes_32s_6k(
2117    t: &[SaSint],
2118    sa: &mut [SaSint],
2119    induction_bucket: &mut [SaSint],
2120    omp_block_start: FastSint,
2121    omp_block_size: FastSint,
2122) {
2123    let prefetch_distance = 64 as FastSint;
2124    let mut i = omp_block_start + omp_block_size - 1;
2125    let mut j = omp_block_start + 2 * prefetch_distance + 3;
2126
2127    while i >= j {
2128        let p0 = sa[i as usize];
2129        let idx0 = t[p0 as usize] as usize;
2130        induction_bucket[idx0] -= 1;
2131        sa[induction_bucket[idx0] as usize] = p0;
2132
2133        let p1 = sa[(i - 1) as usize];
2134        let idx1 = t[p1 as usize] as usize;
2135        induction_bucket[idx1] -= 1;
2136        sa[induction_bucket[idx1] as usize] = p1;
2137
2138        let p2 = sa[(i - 2) as usize];
2139        let idx2 = t[p2 as usize] as usize;
2140        induction_bucket[idx2] -= 1;
2141        sa[induction_bucket[idx2] as usize] = p2;
2142
2143        let p3 = sa[(i - 3) as usize];
2144        let idx3 = t[p3 as usize] as usize;
2145        induction_bucket[idx3] -= 1;
2146        sa[induction_bucket[idx3] as usize] = p3;
2147
2148        i -= 4;
2149    }
2150
2151    j -= 2 * prefetch_distance + 3;
2152    while i >= j {
2153        let p = sa[i as usize];
2154        let idx = t[p as usize] as usize;
2155        induction_bucket[idx] -= 1;
2156        sa[induction_bucket[idx] as usize] = p;
2157        i -= 1;
2158    }
2159}
2160
2161/// Internal helper: radix sort lms suffixes 32s 2k.
2162#[doc(hidden)]
2163pub fn radix_sort_lms_suffixes_32s_2k(
2164    t: &[SaSint],
2165    sa: &mut [SaSint],
2166    induction_bucket: &mut [SaSint],
2167    omp_block_start: FastSint,
2168    omp_block_size: FastSint,
2169) {
2170    let prefetch_distance = 64 as FastSint;
2171    let mut i = omp_block_start + omp_block_size - 1;
2172    let mut j = omp_block_start + 2 * prefetch_distance + 3;
2173
2174    while i >= j {
2175        let p0 = sa[i as usize];
2176        let idx0 = buckets_index2(t[p0 as usize] as usize, 0);
2177        induction_bucket[idx0] -= 1;
2178        sa[induction_bucket[idx0] as usize] = p0;
2179
2180        let p1 = sa[(i - 1) as usize];
2181        let idx1 = buckets_index2(t[p1 as usize] as usize, 0);
2182        induction_bucket[idx1] -= 1;
2183        sa[induction_bucket[idx1] as usize] = p1;
2184
2185        let p2 = sa[(i - 2) as usize];
2186        let idx2 = buckets_index2(t[p2 as usize] as usize, 0);
2187        induction_bucket[idx2] -= 1;
2188        sa[induction_bucket[idx2] as usize] = p2;
2189
2190        let p3 = sa[(i - 3) as usize];
2191        let idx3 = buckets_index2(t[p3 as usize] as usize, 0);
2192        induction_bucket[idx3] -= 1;
2193        sa[induction_bucket[idx3] as usize] = p3;
2194
2195        i -= 4;
2196    }
2197
2198    j -= 2 * prefetch_distance + 3;
2199    while i >= j {
2200        let p = sa[i as usize];
2201        let idx = buckets_index2(t[p as usize] as usize, 0);
2202        induction_bucket[idx] -= 1;
2203        sa[induction_bucket[idx] as usize] = p;
2204        i -= 1;
2205    }
2206}
2207
2208/// Internal helper: radix sort lms suffixes 32s block gather.
2209#[doc(hidden)]
2210pub fn radix_sort_lms_suffixes_32s_block_gather(
2211    t: &[SaSint],
2212    sa: &[SaSint],
2213    cache: &mut [ThreadCache],
2214    omp_block_start: FastSint,
2215    omp_block_size: FastSint,
2216) {
2217    if omp_block_size <= 0 {
2218        return;
2219    }
2220
2221    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
2222    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
2223    let mut i = start;
2224    let mut j = if size > 67 { start + size - 67 } else { start };
2225
2226    while i < j {
2227        for current in [i, i + 1, i + 2, i + 3] {
2228            let ci = current - start;
2229            let index = sa[current];
2230            cache[ci].index = index;
2231            cache[ci].symbol = t[index as usize];
2232        }
2233        i += 4;
2234    }
2235
2236    j = if size > 67 { j + 67 } else { start + size };
2237    while i < j {
2238        let ci = i - start;
2239        let index = sa[i];
2240        cache[ci].index = index;
2241        cache[ci].symbol = t[index as usize];
2242        i += 1;
2243    }
2244}
2245
2246/// Internal helper: radix sort lms suffixes 32s 6k block sort.
2247#[doc(hidden)]
2248pub fn radix_sort_lms_suffixes_32s_6k_block_sort(
2249    induction_bucket: &mut [SaSint],
2250    cache: &mut [ThreadCache],
2251    omp_block_start: FastSint,
2252    omp_block_size: FastSint,
2253) {
2254    if omp_block_size <= 0 {
2255        return;
2256    }
2257
2258    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
2259    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
2260    let mut i = start + size - 1;
2261    let mut j = start + 64 + 3;
2262
2263    while i >= j {
2264        for current in [i, i - 1, i - 2, i - 3] {
2265            let ci = current - start;
2266            let v = cache[ci].symbol as usize;
2267            induction_bucket[v] -= 1;
2268            cache[ci].symbol = induction_bucket[v];
2269        }
2270        i -= 4;
2271    }
2272
2273    j -= 64 + 3;
2274    while i >= j {
2275        let ci = i - start;
2276        let v = cache[ci].symbol as usize;
2277        induction_bucket[v] -= 1;
2278        cache[ci].symbol = induction_bucket[v];
2279        if i == 0 {
2280            break;
2281        }
2282        i -= 1;
2283    }
2284}
2285
2286/// Internal helper: radix sort lms suffixes 32s 2k block sort.
2287#[doc(hidden)]
2288pub fn radix_sort_lms_suffixes_32s_2k_block_sort(
2289    induction_bucket: &mut [SaSint],
2290    cache: &mut [ThreadCache],
2291    omp_block_start: FastSint,
2292    omp_block_size: FastSint,
2293) {
2294    if omp_block_size <= 0 {
2295        return;
2296    }
2297
2298    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
2299    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
2300    let mut i = start + size - 1;
2301    let mut j = start + 64 + 3;
2302
2303    while i >= j {
2304        for current in [i, i - 1, i - 2, i - 3] {
2305            let ci = current - start;
2306            let v = buckets_index2(cache[ci].symbol as usize, 0);
2307            induction_bucket[v] -= 1;
2308            cache[ci].symbol = induction_bucket[v];
2309        }
2310        i -= 4;
2311    }
2312
2313    j -= 64 + 3;
2314    while i >= j {
2315        let ci = i - start;
2316        let v = buckets_index2(cache[ci].symbol as usize, 0);
2317        induction_bucket[v] -= 1;
2318        cache[ci].symbol = induction_bucket[v];
2319        if i == 0 {
2320            break;
2321        }
2322        i -= 1;
2323    }
2324}
2325
2326/// Internal helper: radix sort lms suffixes 32s 6k block (OpenMP variant).
2327#[doc(hidden)]
2328pub fn radix_sort_lms_suffixes_32s_6k_block_omp(
2329    t: &[SaSint],
2330    sa: &mut [SaSint],
2331    induction_bucket: &mut [SaSint],
2332    cache: &mut [ThreadCache],
2333    block_start: FastSint,
2334    block_size: FastSint,
2335    threads: SaSint,
2336) {
2337    if threads <= 1 || block_size < 16_384 {
2338        radix_sort_lms_suffixes_32s_6k(t, sa, induction_bucket, block_start, block_size);
2339        return;
2340    }
2341
2342    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
2343    let threads_usize = usize::try_from(threads)
2344        .expect("threads must be positive")
2345        .min(block_size_usize.max(1));
2346    let omp_block_stride = (block_size_usize / threads_usize) & !15usize;
2347
2348    {
2349        let sa_ro: &[SaSint] = sa;
2350        let t_ro: &[SaSint] = t;
2351        let cache_ptr = SyncMutPtr::new(cache);
2352        run_rayon_with_threads(threads_usize, || {
2353            (0..threads_usize)
2354                .into_par_iter()
2355                .for_each(|omp_thread_num| {
2356                    let omp_block_start = omp_thread_num * omp_block_stride;
2357                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2358                        omp_block_stride
2359                    } else {
2360                        block_size_usize - omp_block_start
2361                    };
2362                    if omp_block_size > 0 {
2363                        // SAFETY: per-thread disjoint cache range.
2364                        let cache = unsafe { cache_ptr.as_slice() };
2365                        radix_sort_lms_suffixes_32s_block_gather(
2366                            t_ro,
2367                            sa_ro,
2368                            &mut cache[omp_block_start..],
2369                            block_start + omp_block_start as FastSint,
2370                            omp_block_size as FastSint,
2371                        );
2372                    }
2373                });
2374        });
2375    }
2376
2377    radix_sort_lms_suffixes_32s_6k_block_sort(induction_bucket, cache, block_start, block_size);
2378
2379    {
2380        let sa_ptr = SyncMutPtr::new(sa);
2381        let cache_ro: &[ThreadCache] = cache;
2382        run_rayon_with_threads(threads_usize, || {
2383            (0..threads_usize)
2384                .into_par_iter()
2385                .for_each(|omp_thread_num| {
2386                    let omp_block_start = omp_thread_num * omp_block_stride;
2387                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2388                        omp_block_stride
2389                    } else {
2390                        block_size_usize - omp_block_start
2391                    };
2392                    if omp_block_size > 0 {
2393                        // SAFETY: per-thread sa writes go to distinct symbol-indexed positions.
2394                        let sa = unsafe { sa_ptr.as_slice() };
2395                        place_cached_suffixes(
2396                            sa,
2397                            &cache_ro[omp_block_start..],
2398                            0,
2399                            omp_block_size as FastSint,
2400                        );
2401                    }
2402                });
2403        });
2404    }
2405}
2406
2407/// Internal helper: radix sort lms suffixes 32s 2k block (OpenMP variant).
2408#[doc(hidden)]
2409pub fn radix_sort_lms_suffixes_32s_2k_block_omp(
2410    t: &[SaSint],
2411    sa: &mut [SaSint],
2412    induction_bucket: &mut [SaSint],
2413    cache: &mut [ThreadCache],
2414    block_start: FastSint,
2415    block_size: FastSint,
2416    threads: SaSint,
2417) {
2418    if threads <= 1 || block_size < 16_384 {
2419        radix_sort_lms_suffixes_32s_2k(t, sa, induction_bucket, block_start, block_size);
2420        return;
2421    }
2422
2423    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
2424    let threads_usize = usize::try_from(threads)
2425        .expect("threads must be positive")
2426        .min(block_size_usize.max(1));
2427    let omp_block_stride = (block_size_usize / threads_usize) & !15usize;
2428
2429    {
2430        let sa_ro: &[SaSint] = sa;
2431        let t_ro: &[SaSint] = t;
2432        let cache_ptr = SyncMutPtr::new(cache);
2433        run_rayon_with_threads(threads_usize, || {
2434            (0..threads_usize)
2435                .into_par_iter()
2436                .for_each(|omp_thread_num| {
2437                    let omp_block_start = omp_thread_num * omp_block_stride;
2438                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2439                        omp_block_stride
2440                    } else {
2441                        block_size_usize - omp_block_start
2442                    };
2443                    if omp_block_size > 0 {
2444                        let cache = unsafe { cache_ptr.as_slice() };
2445                        radix_sort_lms_suffixes_32s_block_gather(
2446                            t_ro,
2447                            sa_ro,
2448                            &mut cache[omp_block_start..],
2449                            block_start + omp_block_start as FastSint,
2450                            omp_block_size as FastSint,
2451                        );
2452                    }
2453                });
2454        });
2455    }
2456
2457    radix_sort_lms_suffixes_32s_2k_block_sort(induction_bucket, cache, block_start, block_size);
2458
2459    {
2460        let sa_ptr = SyncMutPtr::new(sa);
2461        let cache_ro: &[ThreadCache] = cache;
2462        run_rayon_with_threads(threads_usize, || {
2463            (0..threads_usize)
2464                .into_par_iter()
2465                .for_each(|omp_thread_num| {
2466                    let omp_block_start = omp_thread_num * omp_block_stride;
2467                    let omp_block_size = if omp_thread_num + 1 < threads_usize {
2468                        omp_block_stride
2469                    } else {
2470                        block_size_usize - omp_block_start
2471                    };
2472                    if omp_block_size > 0 {
2473                        let sa = unsafe { sa_ptr.as_slice() };
2474                        place_cached_suffixes(
2475                            sa,
2476                            &cache_ro[omp_block_start..],
2477                            0,
2478                            omp_block_size as FastSint,
2479                        );
2480                    }
2481                });
2482        });
2483    }
2484}
2485
2486/// Internal helper: radix sort lms suffixes 32s 6k (OpenMP variant).
2487#[doc(hidden)]
2488pub fn radix_sort_lms_suffixes_32s_6k_omp(
2489    t: &[SaSint],
2490    sa: &mut [SaSint],
2491    n: SaSint,
2492    m: SaSint,
2493    induction_bucket: &mut [SaSint],
2494    threads: SaSint,
2495    _thread_state: &mut [ThreadState],
2496) {
2497    if threads <= 1 || m < 65_536 {
2498        radix_sort_lms_suffixes_32s_6k(
2499            t,
2500            sa,
2501            induction_bucket,
2502            n as FastSint - m as FastSint + 1,
2503            m as FastSint - 1,
2504        );
2505        return;
2506    }
2507
2508    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2509    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
2510    let mut block_start = 0usize;
2511    let m_usize = usize::try_from(m).expect("m must be non-negative");
2512    let n_usize = usize::try_from(n).expect("n must be non-negative");
2513    let last = m_usize - 1;
2514
2515    while block_start < last {
2516        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(last);
2517        radix_sort_lms_suffixes_32s_6k_block_omp(
2518            t,
2519            sa,
2520            induction_bucket,
2521            &mut cache,
2522            (n_usize - block_end) as FastSint,
2523            (block_end - block_start) as FastSint,
2524            threads,
2525        );
2526        block_start = block_end;
2527    }
2528}
2529
2530/// Internal helper: radix sort lms suffixes 32s 2k (OpenMP variant).
2531#[doc(hidden)]
2532pub fn radix_sort_lms_suffixes_32s_2k_omp(
2533    t: &[SaSint],
2534    sa: &mut [SaSint],
2535    n: SaSint,
2536    m: SaSint,
2537    induction_bucket: &mut [SaSint],
2538    threads: SaSint,
2539    _thread_state: &mut [ThreadState],
2540) {
2541    if threads <= 1 || m < 65_536 {
2542        radix_sort_lms_suffixes_32s_2k(
2543            t,
2544            sa,
2545            induction_bucket,
2546            n as FastSint - m as FastSint + 1,
2547            m as FastSint - 1,
2548        );
2549        return;
2550    }
2551
2552    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2553    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
2554    let mut block_start = 0usize;
2555    let m_usize = usize::try_from(m).expect("m must be non-negative");
2556    let n_usize = usize::try_from(n).expect("n must be non-negative");
2557    let last = m_usize - 1;
2558
2559    while block_start < last {
2560        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(last);
2561        radix_sort_lms_suffixes_32s_2k_block_omp(
2562            t,
2563            sa,
2564            induction_bucket,
2565            &mut cache,
2566            (n_usize - block_end) as FastSint,
2567            (block_end - block_start) as FastSint,
2568            threads,
2569        );
2570        block_start = block_end;
2571    }
2572}
2573
2574/// Internal helper: radix sort lms suffixes 32s 1k.
2575#[doc(hidden)]
2576pub fn radix_sort_lms_suffixes_32s_1k(
2577    t: &[SaSint],
2578    sa: &mut [SaSint],
2579    n: SaSint,
2580    buckets: &mut [SaSint],
2581) -> SaSint {
2582    let n_usize = usize::try_from(n).expect("n must be non-negative");
2583    let mut i = n as FastSint - 2;
2584    let mut m = 0;
2585    let mut f0 = 1usize;
2586    let mut f1: usize;
2587    let mut c0 = t[n_usize - 1] as FastSint;
2588    let mut c1: FastSint;
2589    let mut c2 = 0 as FastSint;
2590
2591    while i >= 67 {
2592        c1 = t[i as usize] as FastSint;
2593        f1 = usize::from(c1 > (c0 - f0 as FastSint));
2594        if (f1 & !f0) != 0 {
2595            c2 = c0;
2596            buckets[c2 as usize] -= 1;
2597            sa[buckets[c2 as usize] as usize] = (i + 1) as SaSint;
2598            m += 1;
2599        }
2600
2601        c0 = t[(i - 1) as usize] as FastSint;
2602        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2603        if (f0 & !f1) != 0 {
2604            c2 = c1;
2605            buckets[c2 as usize] -= 1;
2606            sa[buckets[c2 as usize] as usize] = i as SaSint;
2607            m += 1;
2608        }
2609
2610        c1 = t[(i - 2) as usize] as FastSint;
2611        f1 = usize::from(c1 > (c0 - f0 as FastSint));
2612        if (f1 & !f0) != 0 {
2613            c2 = c0;
2614            buckets[c2 as usize] -= 1;
2615            sa[buckets[c2 as usize] as usize] = (i - 1) as SaSint;
2616            m += 1;
2617        }
2618
2619        c0 = t[(i - 3) as usize] as FastSint;
2620        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2621        if (f0 & !f1) != 0 {
2622            c2 = c1;
2623            buckets[c2 as usize] -= 1;
2624            sa[buckets[c2 as usize] as usize] = (i - 2) as SaSint;
2625            m += 1;
2626        }
2627
2628        i -= 4;
2629    }
2630
2631    while i >= 0 {
2632        c1 = c0;
2633        c0 = t[i as usize] as FastSint;
2634        f1 = f0;
2635        f0 = usize::from(c0 > (c1 - f1 as FastSint));
2636        if (f0 & !f1) != 0 {
2637            c2 = c1;
2638            buckets[c2 as usize] -= 1;
2639            sa[buckets[c2 as usize] as usize] = (i + 1) as SaSint;
2640            m += 1;
2641        }
2642        i -= 1;
2643    }
2644
2645    if m > 1 {
2646        sa[buckets[c2 as usize] as usize] = 0;
2647    }
2648
2649    m
2650}
2651
2652/// Internal helper: radix sort set markers 32s 6k.
2653#[doc(hidden)]
2654pub fn radix_sort_set_markers_32s_6k(
2655    sa: &mut [SaSint],
2656    induction_bucket: &[SaSint],
2657    omp_block_start: FastSint,
2658    omp_block_size: FastSint,
2659) {
2660    let mut i = omp_block_start;
2661    let mut j = omp_block_start + omp_block_size - 67;
2662
2663    while i < j {
2664        sa[induction_bucket[i as usize] as usize] |= SAINT_MIN;
2665        sa[induction_bucket[(i + 1) as usize] as usize] |= SAINT_MIN;
2666        sa[induction_bucket[(i + 2) as usize] as usize] |= SAINT_MIN;
2667        sa[induction_bucket[(i + 3) as usize] as usize] |= SAINT_MIN;
2668        i += 4;
2669    }
2670
2671    j += 67;
2672    while i < j {
2673        sa[induction_bucket[i as usize] as usize] |= SAINT_MIN;
2674        i += 1;
2675    }
2676}
2677
2678/// Internal helper: radix sort set markers 32s 4k.
2679#[doc(hidden)]
2680pub fn radix_sort_set_markers_32s_4k(
2681    sa: &mut [SaSint],
2682    induction_bucket: &[SaSint],
2683    omp_block_start: FastSint,
2684    omp_block_size: FastSint,
2685) {
2686    let mut i = omp_block_start;
2687    let mut j = omp_block_start + omp_block_size - 67;
2688
2689    while i < j {
2690        sa[induction_bucket[buckets_index2(i as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2691        sa[induction_bucket[buckets_index2((i + 1) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2692        sa[induction_bucket[buckets_index2((i + 2) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2693        sa[induction_bucket[buckets_index2((i + 3) as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2694        i += 4;
2695    }
2696
2697    j += 67;
2698    while i < j {
2699        sa[induction_bucket[buckets_index2(i as usize, 0)] as usize] |= SUFFIX_GROUP_MARKER;
2700        i += 1;
2701    }
2702}
2703
2704/// Internal helper: radix sort set markers 32s 6k (OpenMP variant).
2705#[doc(hidden)]
2706pub fn radix_sort_set_markers_32s_6k_omp(
2707    sa: &mut [SaSint],
2708    k: SaSint,
2709    induction_bucket: &[SaSint],
2710    threads: SaSint,
2711) {
2712    if k <= 1 {
2713        return;
2714    }
2715
2716    if threads <= 1 || k < 65_536 {
2717        radix_sort_set_markers_32s_6k(sa, induction_bucket, 0, k as FastSint - 1);
2718        return;
2719    }
2720
2721    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2722    let last = usize::try_from(k - 1).expect("k must be positive");
2723    let stride = (last / threads_usize) & !15usize;
2724
2725    {
2726        let sa_ptr = SyncMutPtr::new(sa);
2727        run_rayon_with_threads(threads_usize, || {
2728            (0..threads_usize).into_par_iter().for_each(|thread| {
2729                let start = thread * stride;
2730                let end = if thread + 1 == threads_usize {
2731                    last
2732                } else {
2733                    start + stride
2734                };
2735                if end > start {
2736                    // SAFETY: per-thread disjoint sa[start..end] block.
2737                    let sa = unsafe { sa_ptr.as_slice() };
2738                    radix_sort_set_markers_32s_6k(
2739                        sa,
2740                        induction_bucket,
2741                        start as FastSint,
2742                        (end - start) as FastSint,
2743                    );
2744                }
2745            });
2746        });
2747    }
2748}
2749
2750/// Internal helper: radix sort set markers 32s 4k (OpenMP variant).
2751#[doc(hidden)]
2752pub fn radix_sort_set_markers_32s_4k_omp(
2753    sa: &mut [SaSint],
2754    k: SaSint,
2755    induction_bucket: &[SaSint],
2756    threads: SaSint,
2757) {
2758    if k <= 1 {
2759        return;
2760    }
2761
2762    if threads <= 1 || k < 65_536 {
2763        radix_sort_set_markers_32s_4k(sa, induction_bucket, 0, k as FastSint - 1);
2764        return;
2765    }
2766
2767    let threads_usize = usize::try_from(threads).expect("threads must be positive");
2768    let last = usize::try_from(k - 1).expect("k must be positive");
2769    let stride = (last / threads_usize) & !15usize;
2770
2771    {
2772        let sa_ptr = SyncMutPtr::new(sa);
2773        run_rayon_with_threads(threads_usize, || {
2774            (0..threads_usize).into_par_iter().for_each(|thread| {
2775                let start = thread * stride;
2776                let end = if thread + 1 == threads_usize {
2777                    last
2778                } else {
2779                    start + stride
2780                };
2781                if end > start {
2782                    let sa = unsafe { sa_ptr.as_slice() };
2783                    radix_sort_set_markers_32s_4k(
2784                        sa,
2785                        induction_bucket,
2786                        start as FastSint,
2787                        (end - start) as FastSint,
2788                    );
2789                }
2790            });
2791        });
2792    }
2793}
2794
2795/// Internal helper: initialize buckets for partial sorting 8u.
2796#[doc(hidden)]
2797pub fn initialize_buckets_for_partial_sorting_8u(
2798    t: &[u8],
2799    buckets: &mut [SaSint],
2800    first_lms_suffix: SaSint,
2801    left_suffixes_count: SaSint,
2802) {
2803    let temp_offset = 4 * ALPHABET_SIZE;
2804    buckets[buckets_index4(t[first_lms_suffix as usize] as usize, 1)] += 1;
2805
2806    let mut sum0 = left_suffixes_count + 1;
2807    let mut sum1 = 0;
2808    for j in 0..ALPHABET_SIZE {
2809        let i = buckets_index4(j, 0);
2810        let tj = buckets_index2(j, 0);
2811        buckets[temp_offset + tj] = sum0;
2812        sum0 += buckets[i] + buckets[i + 2];
2813        sum1 += buckets[i + 1];
2814        buckets[tj] = sum0;
2815        buckets[tj + 1] = sum1;
2816    }
2817}
2818
2819/// Internal helper: initialize buckets for partial sorting 32s 6k.
2820#[doc(hidden)]
2821pub fn initialize_buckets_for_partial_sorting_32s_6k(
2822    t: &[SaSint],
2823    k: SaSint,
2824    buckets: &mut [SaSint],
2825    first_lms_suffix: SaSint,
2826    left_suffixes_count: SaSint,
2827) {
2828    let k_usize = usize::try_from(k).expect("k must be non-negative");
2829    let temp_offset = 4 * k_usize;
2830    let first_symbol = t[first_lms_suffix as usize] as usize;
2831    let mut sum0 = left_suffixes_count + 1;
2832    let mut sum1 = 0;
2833    let mut sum2 = 0;
2834
2835    for j in 0..first_symbol {
2836        let i = buckets_index4(j, 0);
2837        let tj = buckets_index2(j, 0);
2838        let ss = buckets[i];
2839        let ls = buckets[i + 1];
2840        let sl = buckets[i + 2];
2841        let ll = buckets[i + 3];
2842
2843        buckets[i] = sum0;
2844        buckets[i + 1] = sum2;
2845        buckets[i + 2] = 0;
2846        buckets[i + 3] = 0;
2847
2848        sum0 += ss + sl;
2849        sum1 += ls;
2850        sum2 += ls + ll;
2851
2852        buckets[temp_offset + tj] = sum0;
2853        buckets[temp_offset + tj + 1] = sum1;
2854    }
2855
2856    sum1 += 1;
2857    for j in first_symbol..k_usize {
2858        let i = buckets_index4(j, 0);
2859        let tj = buckets_index2(j, 0);
2860        let ss = buckets[i];
2861        let ls = buckets[i + 1];
2862        let sl = buckets[i + 2];
2863        let ll = buckets[i + 3];
2864
2865        buckets[i] = sum0;
2866        buckets[i + 1] = sum2;
2867        buckets[i + 2] = 0;
2868        buckets[i + 3] = 0;
2869
2870        sum0 += ss + sl;
2871        sum1 += ls;
2872        sum2 += ls + ll;
2873
2874        buckets[temp_offset + tj] = sum0;
2875        buckets[temp_offset + tj + 1] = sum1;
2876    }
2877}
2878
2879/// Internal helper: partial sorting scan left to right 8u.
2880#[doc(hidden)]
2881pub fn partial_sorting_scan_left_to_right_8u(
2882    t: &[u8],
2883    sa: &mut [SaSint],
2884    buckets: &mut [SaSint],
2885    mut d: SaSint,
2886    omp_block_start: FastSint,
2887    omp_block_size: FastSint,
2888) -> SaSint {
2889    let induction_offset = 4 * ALPHABET_SIZE;
2890    let distinct_offset = 2 * ALPHABET_SIZE;
2891    let prefetch_distance = 64 as FastSint;
2892    let mut i = omp_block_start;
2893    let mut j = if omp_block_size > prefetch_distance + 1 {
2894        omp_block_start + omp_block_size - prefetch_distance - 1
2895    } else {
2896        omp_block_start
2897    };
2898
2899    while i < j {
2900        let mut p0 = sa[i as usize];
2901        d += SaSint::from(p0 < 0);
2902        p0 &= SAINT_MAX;
2903        let v0 = buckets_index2(
2904            t[(p0 - 1) as usize] as usize,
2905            usize::from(t[(p0 - 2) as usize] >= t[(p0 - 1) as usize]),
2906        );
2907        let pos0 = buckets[induction_offset + v0] as usize;
2908        sa[pos0] = (p0 - 1) | (((buckets[distinct_offset + v0] != d) as SaSint) << (SAINT_BIT - 1));
2909        buckets[induction_offset + v0] += 1;
2910        buckets[distinct_offset + v0] = d;
2911
2912        let mut p1 = sa[(i + 1) as usize];
2913        d += SaSint::from(p1 < 0);
2914        p1 &= SAINT_MAX;
2915        let v1 = buckets_index2(
2916            t[(p1 - 1) as usize] as usize,
2917            usize::from(t[(p1 - 2) as usize] >= t[(p1 - 1) as usize]),
2918        );
2919        let pos1 = buckets[induction_offset + v1] as usize;
2920        sa[pos1] = (p1 - 1) | (((buckets[distinct_offset + v1] != d) as SaSint) << (SAINT_BIT - 1));
2921        buckets[induction_offset + v1] += 1;
2922        buckets[distinct_offset + v1] = d;
2923
2924        i += 2;
2925    }
2926
2927    j = omp_block_start + omp_block_size;
2928    while i < j {
2929        let mut p = sa[i as usize];
2930        d += SaSint::from(p < 0);
2931        p &= SAINT_MAX;
2932        let v = buckets_index2(
2933            t[(p - 1) as usize] as usize,
2934            usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
2935        );
2936        let pos = buckets[induction_offset + v] as usize;
2937        sa[pos] = (p - 1) | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
2938        buckets[induction_offset + v] += 1;
2939        buckets[distinct_offset + v] = d;
2940        i += 1;
2941    }
2942
2943    d
2944}
2945
2946/// Internal helper: partial sorting scan left to right 8u (OpenMP variant).
2947#[doc(hidden)]
2948pub fn partial_sorting_scan_left_to_right_8u_omp(
2949    t: &[u8],
2950    sa: &mut [SaSint],
2951    n: SaSint,
2952    k: SaSint,
2953    buckets: &mut [SaSint],
2954    left_suffixes_count: SaSint,
2955    mut d: SaSint,
2956    threads: SaSint,
2957    thread_state: &mut [ThreadState],
2958) -> SaSint {
2959    let v = buckets_index2(
2960        t[(n - 1) as usize] as usize,
2961        usize::from(t[(n - 2) as usize] >= t[(n - 1) as usize]),
2962    );
2963    let induction_offset = 4 * ALPHABET_SIZE;
2964    let distinct_offset = 2 * ALPHABET_SIZE;
2965    let pos = buckets[induction_offset + v] as usize;
2966    sa[pos] = (n - 1) | SAINT_MIN;
2967    buckets[induction_offset + v] += 1;
2968    d += 1;
2969    buckets[distinct_offset + v] = d;
2970
2971    if threads == 1 || left_suffixes_count < 65_536 {
2972        return partial_sorting_scan_left_to_right_8u(
2973            t,
2974            sa,
2975            buckets,
2976            d,
2977            0,
2978            left_suffixes_count as FastSint,
2979        );
2980    }
2981
2982    let mut block_start = 0usize;
2983    let left_suffixes_count =
2984        usize::try_from(left_suffixes_count).expect("left_suffixes_count must be non-negative");
2985    let threads_usize = usize::try_from(threads)
2986        .expect("threads must be non-negative")
2987        .min(thread_state.len())
2988        .max(1);
2989    while block_start < left_suffixes_count {
2990        if sa[block_start] == 0 {
2991            block_start += 1;
2992        } else {
2993            let mut block_max_end =
2994                block_start + threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
2995            if block_max_end > left_suffixes_count {
2996                block_max_end = left_suffixes_count;
2997            }
2998            let mut block_end = block_start + 1;
2999            while block_end < block_max_end && sa[block_end] != 0 {
3000                block_end += 1;
3001            }
3002            let block_size = block_end - block_start;
3003
3004            if block_size < 32 {
3005                while block_start < block_end {
3006                    let p = sa[block_start];
3007                    d += SaSint::from(p < 0);
3008                    let p = p & SAINT_MAX;
3009                    let v = buckets_index2(
3010                        t[(p - 1) as usize] as usize,
3011                        usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
3012                    );
3013                    let pos = buckets[induction_offset + v] as usize;
3014                    sa[pos] = (p - 1)
3015                        | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
3016                    buckets[induction_offset + v] += 1;
3017                    buckets[distinct_offset + v] = d;
3018                    block_start += 1;
3019                }
3020            } else {
3021                d = partial_sorting_scan_left_to_right_8u_block_omp(
3022                    t,
3023                    sa,
3024                    k,
3025                    buckets,
3026                    d,
3027                    block_start as FastSint,
3028                    block_size as FastSint,
3029                    threads,
3030                    thread_state,
3031                );
3032                block_start = block_end;
3033            }
3034        }
3035    }
3036
3037    d
3038}
3039
3040/// Internal helper: partial sorting scan left to right 32s 6k.
3041#[doc(hidden)]
3042pub fn partial_sorting_scan_left_to_right_32s_6k(
3043    t: &[SaSint],
3044    sa: &mut [SaSint],
3045    buckets: &mut [SaSint],
3046    mut d: SaSint,
3047    omp_block_start: FastSint,
3048    omp_block_size: FastSint,
3049) -> SaSint {
3050    let prefetch_distance: FastSint = 64;
3051
3052    let mut i = omp_block_start;
3053    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3054    while i < j {
3055        let mut p0 = sa[i as usize];
3056        d += SaSint::from(p0 < 0);
3057        p0 &= SAINT_MAX;
3058        let p0u = p0 as usize;
3059        let v0 = buckets_index4(t[p0u - 1] as usize, usize::from(t[p0u - 2] >= t[p0u - 1]));
3060        let pos0 = buckets[v0] as usize;
3061        sa[pos0] = (p0 - 1) | (((buckets[2 + v0] != d) as SaSint) << (SAINT_BIT - 1));
3062        buckets[v0] += 1;
3063        buckets[2 + v0] = d;
3064
3065        let mut p1 = sa[(i + 1) as usize];
3066        d += SaSint::from(p1 < 0);
3067        p1 &= SAINT_MAX;
3068        let p1u = p1 as usize;
3069        let v1 = buckets_index4(t[p1u - 1] as usize, usize::from(t[p1u - 2] >= t[p1u - 1]));
3070        let pos1 = buckets[v1] as usize;
3071        sa[pos1] = (p1 - 1) | (((buckets[2 + v1] != d) as SaSint) << (SAINT_BIT - 1));
3072        buckets[v1] += 1;
3073        buckets[2 + v1] = d;
3074
3075        i += 2;
3076    }
3077
3078    j += 2 * prefetch_distance + 1;
3079    while i < j {
3080        let mut p = sa[i as usize];
3081        d += SaSint::from(p < 0);
3082        p &= SAINT_MAX;
3083        let pu = p as usize;
3084        let v = buckets_index4(t[pu - 1] as usize, usize::from(t[pu - 2] >= t[pu - 1]));
3085        let pos = buckets[v] as usize;
3086        sa[pos] = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
3087        buckets[v] += 1;
3088        buckets[2 + v] = d;
3089        i += 1;
3090    }
3091
3092    d
3093}
3094
3095/// Internal helper: partial sorting scan left to right 32s 4k.
3096#[doc(hidden)]
3097pub fn partial_sorting_scan_left_to_right_32s_4k(
3098    t: &[SaSint],
3099    sa: &mut [SaSint],
3100    k: SaSint,
3101    buckets: &mut [SaSint],
3102    mut d: SaSint,
3103    omp_block_start: FastSint,
3104    omp_block_size: FastSint,
3105) -> SaSint {
3106    let k_usize = usize::try_from(k).expect("k must be non-negative");
3107    let prefetch_distance: FastSint = 64;
3108    let induction_offset = 2 * k_usize;
3109    let mut i = omp_block_start;
3110    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3111
3112    while i < j {
3113        let i0 = i as usize;
3114        let mut p0 = sa[i0];
3115        sa[i0] = p0 & SAINT_MAX;
3116        if p0 > 0 {
3117            sa[i0] = 0;
3118            d += p0 >> (SUFFIX_GROUP_BIT - 1);
3119            p0 &= !SUFFIX_GROUP_MARKER;
3120            let p0u = p0 as usize;
3121            let c0 = t[p0u - 1];
3122            let f0 = usize::from(t[p0u - 2] < c0);
3123            let v0 = buckets_index2(c0 as usize, f0);
3124            let c0u = c0 as usize;
3125            let pos0 = buckets[induction_offset + c0u] as usize;
3126            sa[pos0] = (p0 - 1)
3127                | ((f0 as SaSint) << (SAINT_BIT - 1))
3128                | (((buckets[v0] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3129            buckets[induction_offset + c0u] += 1;
3130            buckets[v0] = d;
3131        }
3132
3133        let i1 = (i + 1) as usize;
3134        let mut p1 = sa[i1];
3135        sa[i1] = p1 & SAINT_MAX;
3136        if p1 > 0 {
3137            sa[i1] = 0;
3138            d += p1 >> (SUFFIX_GROUP_BIT - 1);
3139            p1 &= !SUFFIX_GROUP_MARKER;
3140            let p1u = p1 as usize;
3141            let c1 = t[p1u - 1];
3142            let f1 = usize::from(t[p1u - 2] < c1);
3143            let v1 = buckets_index2(c1 as usize, f1);
3144            let c1u = c1 as usize;
3145            let pos1 = buckets[induction_offset + c1u] as usize;
3146            sa[pos1] = (p1 - 1)
3147                | ((f1 as SaSint) << (SAINT_BIT - 1))
3148                | (((buckets[v1] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3149            buckets[induction_offset + c1u] += 1;
3150            buckets[v1] = d;
3151        }
3152
3153        i += 2;
3154    }
3155
3156    j += 2 * prefetch_distance + 1;
3157    while i < j {
3158        let iu = i as usize;
3159        let mut p = sa[iu];
3160        sa[iu] = p & SAINT_MAX;
3161        if p > 0 {
3162            sa[iu] = 0;
3163            d += p >> (SUFFIX_GROUP_BIT - 1);
3164            p &= !SUFFIX_GROUP_MARKER;
3165            let pu = p as usize;
3166            let c = t[pu - 1];
3167            let f = usize::from(t[pu - 2] < c);
3168            let v = buckets_index2(c as usize, f);
3169            let cu = c as usize;
3170            let pos = buckets[induction_offset + cu] as usize;
3171            sa[pos] = (p - 1)
3172                | ((f as SaSint) << (SAINT_BIT - 1))
3173                | (((buckets[v] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3174            buckets[induction_offset + cu] += 1;
3175            buckets[v] = d;
3176        }
3177        i += 1;
3178    }
3179
3180    d
3181}
3182
3183/// Internal helper: partial sorting scan left to right 32s 1k.
3184#[doc(hidden)]
3185pub fn partial_sorting_scan_left_to_right_32s_1k(
3186    t: &[SaSint],
3187    sa: &mut [SaSint],
3188    induction_bucket: &mut [SaSint],
3189    omp_block_start: FastSint,
3190    omp_block_size: FastSint,
3191) {
3192    let prefetch_distance = 64 as FastSint;
3193    let mut i = omp_block_start;
3194    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
3195
3196    while i < j {
3197        let p0 = sa[i as usize];
3198        sa[i as usize] = p0 & SAINT_MAX;
3199        if p0 > 0 {
3200            sa[i as usize] = 0;
3201            let c0 = t[(p0 - 1) as usize] as usize;
3202            let pos0 = induction_bucket[c0] as usize;
3203            induction_bucket[c0] += 1;
3204            sa[pos0] = (p0 - 1)
3205                | ((usize::from(t[(p0 - 2) as usize] < t[(p0 - 1) as usize]) as SaSint)
3206                    << (SAINT_BIT - 1));
3207        }
3208
3209        let p1 = sa[(i + 1) as usize];
3210        sa[(i + 1) as usize] = p1 & SAINT_MAX;
3211        if p1 > 0 {
3212            sa[(i + 1) as usize] = 0;
3213            let c1 = t[(p1 - 1) as usize] as usize;
3214            let pos1 = induction_bucket[c1] as usize;
3215            induction_bucket[c1] += 1;
3216            sa[pos1] = (p1 - 1)
3217                | ((usize::from(t[(p1 - 2) as usize] < t[(p1 - 1) as usize]) as SaSint)
3218                    << (SAINT_BIT - 1));
3219        }
3220
3221        i += 2;
3222    }
3223
3224    j += 2 * prefetch_distance + 1;
3225    while i < j {
3226        let p = sa[i as usize];
3227        sa[i as usize] = p & SAINT_MAX;
3228        if p > 0 {
3229            sa[i as usize] = 0;
3230            let c = t[(p - 1) as usize] as usize;
3231            let pos = induction_bucket[c] as usize;
3232            induction_bucket[c] += 1;
3233            sa[pos] = (p - 1)
3234                | ((usize::from(t[(p - 2) as usize] < t[(p - 1) as usize]) as SaSint)
3235                    << (SAINT_BIT - 1));
3236        }
3237        i += 1;
3238    }
3239}
3240
3241/// Internal helper: partial sorting scan left to right 32s 6k (OpenMP variant).
3242#[doc(hidden)]
3243pub fn partial_sorting_scan_left_to_right_32s_6k_omp(
3244    t: &[SaSint],
3245    sa: &mut [SaSint],
3246    n: SaSint,
3247    buckets: &mut [SaSint],
3248    left_suffixes_count: SaSint,
3249    mut d: SaSint,
3250    threads: SaSint,
3251    thread_state: &mut [ThreadState],
3252) -> SaSint {
3253    let v = buckets_index4(
3254        t[(n - 1) as usize] as usize,
3255        usize::from(t[(n - 2) as usize] >= t[(n - 1) as usize]),
3256    );
3257    let pos = buckets[v] as usize;
3258    sa[pos] = (n - 1) | SAINT_MIN;
3259    buckets[v] += 1;
3260    d += 1;
3261    buckets[2 + v] = d;
3262    if threads == 1 || left_suffixes_count < 65_536 {
3263        return partial_sorting_scan_left_to_right_32s_6k(
3264            t,
3265            sa,
3266            buckets,
3267            d,
3268            0,
3269            left_suffixes_count as FastSint,
3270        );
3271    }
3272    if thread_state.is_empty() {
3273        return partial_sorting_scan_left_to_right_32s_6k(
3274            t,
3275            sa,
3276            buckets,
3277            d,
3278            0,
3279            left_suffixes_count as FastSint,
3280        );
3281    }
3282
3283    let left_suffixes_count =
3284        usize::try_from(left_suffixes_count).expect("left_suffixes_count must be non-negative");
3285    let threads_usize = usize::try_from(threads)
3286        .expect("threads must be non-negative")
3287        .max(1);
3288    let mut block_start = 0usize;
3289    let block_span = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3290    let mut cache = vec![ThreadCache::default(); block_span];
3291    while block_start < left_suffixes_count {
3292        let mut block_end = block_start + block_span;
3293        if block_end > left_suffixes_count {
3294            block_end = left_suffixes_count;
3295        }
3296
3297        d = partial_sorting_scan_left_to_right_32s_6k_block_omp(
3298            t,
3299            sa,
3300            buckets,
3301            d,
3302            &mut cache,
3303            block_start as FastSint,
3304            (block_end - block_start) as FastSint,
3305            threads,
3306        );
3307
3308        block_start = block_end;
3309    }
3310
3311    d
3312}
3313
3314/// Internal helper: partial sorting scan left to right 32s 4k (OpenMP variant).
3315#[doc(hidden)]
3316pub fn partial_sorting_scan_left_to_right_32s_4k_omp(
3317    t: &[SaSint],
3318    sa: &mut [SaSint],
3319    n: SaSint,
3320    k: SaSint,
3321    buckets: &mut [SaSint],
3322    mut d: SaSint,
3323    threads: SaSint,
3324    thread_state: &mut [ThreadState],
3325) -> SaSint {
3326    let k_usize = usize::try_from(k).expect("k must be non-negative");
3327    let induction_offset = 2 * k_usize;
3328    let distinct_offset = 0usize;
3329    let symbol = t[(n - 1) as usize] as usize;
3330    let is_s = usize::from(t[(n - 2) as usize] < t[(n - 1) as usize]);
3331    let pos = buckets[induction_offset + symbol] as usize;
3332    sa[pos] = (n - 1) | ((is_s as SaSint) << (SAINT_BIT - 1)) | SUFFIX_GROUP_MARKER;
3333    buckets[induction_offset + symbol] += 1;
3334    d += 1;
3335    buckets[distinct_offset + buckets_index2(symbol, is_s)] = d;
3336
3337    if threads == 1 || n < 65_536 {
3338        d = partial_sorting_scan_left_to_right_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
3339    } else {
3340        if thread_state.is_empty() {
3341            return partial_sorting_scan_left_to_right_32s_4k(
3342                t,
3343                sa,
3344                k,
3345                buckets,
3346                d,
3347                0,
3348                n as FastSint,
3349            );
3350        }
3351        let mut block_start = 0usize;
3352        let n_usize = usize::try_from(n).expect("n must be non-negative");
3353        let threads_usize = usize::try_from(threads)
3354            .expect("threads must be non-negative")
3355            .max(1);
3356        let chunk_capacity = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3357        let mut cache = vec![ThreadCache::default(); chunk_capacity];
3358
3359        while block_start < n_usize {
3360            let mut block_end = block_start + chunk_capacity;
3361            if block_end > n_usize {
3362                block_end = n_usize;
3363            }
3364
3365            d = partial_sorting_scan_left_to_right_32s_4k_block_omp(
3366                t,
3367                sa,
3368                k,
3369                buckets,
3370                d,
3371                &mut cache,
3372                block_start as FastSint,
3373                (block_end - block_start) as FastSint,
3374                threads,
3375            );
3376
3377            block_start = block_end;
3378        }
3379    }
3380
3381    d
3382}
3383
3384/// Internal helper: partial sorting scan left to right 32s 1k (OpenMP variant).
3385#[doc(hidden)]
3386pub fn partial_sorting_scan_left_to_right_32s_1k_omp(
3387    t: &[SaSint],
3388    sa: &mut [SaSint],
3389    n: SaSint,
3390    buckets: &mut [SaSint],
3391    threads: SaSint,
3392    thread_state: &mut [ThreadState],
3393) {
3394    let symbol = t[(n - 1) as usize] as usize;
3395    let pos = buckets[symbol] as usize;
3396    sa[pos] = (n - 1)
3397        | ((usize::from(t[(n - 2) as usize] < t[(n - 1) as usize]) as SaSint) << (SAINT_BIT - 1));
3398    buckets[symbol] += 1;
3399    if threads == 1 || n < 65_536 {
3400        partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, 0, n as FastSint);
3401    } else {
3402        if thread_state.is_empty() {
3403            partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, 0, n as FastSint);
3404            return;
3405        }
3406        let n_usize = usize::try_from(n).expect("n must be non-negative");
3407        let threads_usize = usize::try_from(threads)
3408            .expect("threads must be non-negative")
3409            .max(1);
3410        let mut block_start = 0usize;
3411        let block_span = threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE;
3412        let mut cache = vec![ThreadCache::default(); block_span];
3413
3414        while block_start < n_usize {
3415            let mut block_end = block_start + block_span;
3416            if block_end > n_usize {
3417                block_end = n_usize;
3418            }
3419
3420            partial_sorting_scan_left_to_right_32s_1k_block_omp(
3421                t,
3422                sa,
3423                buckets,
3424                &mut cache,
3425                block_start as FastSint,
3426                (block_end - block_start) as FastSint,
3427                threads,
3428            );
3429
3430            block_start = block_end;
3431        }
3432    }
3433}
3434
3435/// Internal helper: partial sorting scan left to right 8u block prepare.
3436#[doc(hidden)]
3437pub fn partial_sorting_scan_left_to_right_8u_block_prepare(
3438    t: &[u8],
3439    sa: &[SaSint],
3440    k: SaSint,
3441    buckets: &mut [SaSint],
3442    cache: &mut [ThreadCache],
3443    omp_block_start: FastSint,
3444    omp_block_size: FastSint,
3445) -> (FastSint, FastSint) {
3446    let k_usize = usize::try_from(k).expect("k must be non-negative");
3447    buckets[..2 * k_usize].fill(0);
3448    buckets[2 * k_usize..4 * k_usize].fill(0);
3449
3450    let mut i = omp_block_start;
3451    let mut j = omp_block_start + omp_block_size - 65;
3452    let mut count = 0usize;
3453    let mut d: SaSint = 1;
3454
3455    while i < j {
3456        let mut p0 = sa[i as usize];
3457        cache[count].index = p0;
3458        d += SaSint::from(p0 < 0);
3459        p0 &= SAINT_MAX;
3460        let v0 = buckets_index2(
3461            t[(p0 - 1) as usize] as usize,
3462            usize::from(t[(p0 - 2) as usize] >= t[(p0 - 1) as usize]),
3463        );
3464        cache[count].symbol = v0 as SaSint;
3465        count += 1;
3466        buckets[v0] += 1;
3467        buckets[2 * k_usize + v0] = d;
3468
3469        let mut p1 = sa[(i + 1) as usize];
3470        cache[count].index = p1;
3471        d += SaSint::from(p1 < 0);
3472        p1 &= SAINT_MAX;
3473        let v1 = buckets_index2(
3474            t[(p1 - 1) as usize] as usize,
3475            usize::from(t[(p1 - 2) as usize] >= t[(p1 - 1) as usize]),
3476        );
3477        cache[count].symbol = v1 as SaSint;
3478        count += 1;
3479        buckets[v1] += 1;
3480        buckets[2 * k_usize + v1] = d;
3481
3482        i += 2;
3483    }
3484
3485    j += 65;
3486    while i < j {
3487        let mut p = sa[i as usize];
3488        cache[count].index = p;
3489        d += SaSint::from(p < 0);
3490        p &= SAINT_MAX;
3491        let v = buckets_index2(
3492            t[(p - 1) as usize] as usize,
3493            usize::from(t[(p - 2) as usize] >= t[(p - 1) as usize]),
3494        );
3495        cache[count].symbol = v as SaSint;
3496        count += 1;
3497        buckets[v] += 1;
3498        buckets[2 * k_usize + v] = d;
3499        i += 1;
3500    }
3501
3502    (d as FastSint - 1, count as FastSint)
3503}
3504
3505/// Internal helper: partial sorting scan left to right 8u block place.
3506#[doc(hidden)]
3507pub fn partial_sorting_scan_left_to_right_8u_block_place(
3508    sa: &mut [SaSint],
3509    buckets: &mut [SaSint],
3510    k: SaSint,
3511    cache: &[ThreadCache],
3512    count: FastSint,
3513    mut d: SaSint,
3514) {
3515    let split = 2 * usize::try_from(k).expect("k must be non-negative");
3516    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
3517
3518    let mut i = 0usize;
3519    let mut j = usize::try_from(count)
3520        .expect("count must be non-negative")
3521        .saturating_sub(1);
3522    while i < j {
3523        let p0 = cache[i].index;
3524        d += SaSint::from(p0 < 0);
3525        let v0 = cache[i].symbol as usize;
3526        let pos0 = induction_bucket[v0] as usize;
3527        sa[pos0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
3528        induction_bucket[v0] += 1;
3529        distinct_names[v0] = d;
3530
3531        let p1 = cache[i + 1].index;
3532        d += SaSint::from(p1 < 0);
3533        let v1 = cache[i + 1].symbol as usize;
3534        let pos1 = induction_bucket[v1] as usize;
3535        sa[pos1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
3536        induction_bucket[v1] += 1;
3537        distinct_names[v1] = d;
3538
3539        i += 2;
3540    }
3541
3542    j += 1;
3543    while i < j {
3544        let p = cache[i].index;
3545        d += SaSint::from(p < 0);
3546        let v = cache[i].symbol as usize;
3547        let pos = induction_bucket[v] as usize;
3548        sa[pos] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
3549        induction_bucket[v] += 1;
3550        distinct_names[v] = d;
3551        i += 1;
3552    }
3553}
3554
3555/// Internal helper: partial sorting scan left to right 8u block (OpenMP variant).
3556#[doc(hidden)]
3557pub fn partial_sorting_scan_left_to_right_8u_block_omp(
3558    t: &[u8],
3559    sa: &mut [SaSint],
3560    k: SaSint,
3561    buckets: &mut [SaSint],
3562    d: SaSint,
3563    block_start: FastSint,
3564    block_size: FastSint,
3565    threads: SaSint,
3566    thread_state: &mut [ThreadState],
3567) -> SaSint {
3568    let mut d = d;
3569    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
3570    let k_usize = usize::try_from(k).expect("k must be non-negative");
3571    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
3572        usize::try_from(threads)
3573            .expect("threads must be non-negative")
3574            .min(thread_state.len())
3575            .max(1)
3576    } else {
3577        1
3578    };
3579    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
3580
3581    if omp_num_threads == 1 {
3582        return partial_sorting_scan_left_to_right_8u(t, sa, buckets, d, block_start, block_size);
3583    }
3584
3585    {
3586        let sa_ro: &[SaSint] = sa;
3587        run_rayon_with_threads(omp_num_threads, || {
3588            thread_state[..omp_num_threads]
3589                .par_iter_mut()
3590                .enumerate()
3591                .for_each(|(omp_thread_num, state)| {
3592                    let mut omp_block_start = omp_thread_num * omp_block_stride;
3593                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
3594                        omp_block_stride
3595                    } else {
3596                        block_size_usize - omp_block_start
3597                    };
3598                    omp_block_start +=
3599                        usize::try_from(block_start).expect("block_start must be non-negative");
3600
3601                    let (position, count) = partial_sorting_scan_left_to_right_8u_block_prepare(
3602                        t,
3603                        sa_ro,
3604                        k,
3605                        &mut state.buckets,
3606                        &mut state.cache,
3607                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
3608                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
3609                    );
3610                    state.position = position;
3611                    state.count = count;
3612                });
3613        });
3614    }
3615
3616    let induction_offset = 4 * ALPHABET_SIZE;
3617    let distinct_offset = 2 * ALPHABET_SIZE;
3618    let (prefix, induction_tail) = buckets.split_at_mut(induction_offset);
3619    let induction_bucket = &mut induction_tail[..2 * k_usize];
3620    let distinct_names = &mut prefix[distinct_offset..distinct_offset + 2 * k_usize];
3621
3622    for tnum in 0..omp_num_threads {
3623        let state = &mut thread_state[tnum];
3624        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
3625        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
3626
3627        for c in 0..2 * k_usize {
3628            let a = induction_bucket[c];
3629            let b = temp_induction_bucket[c];
3630            induction_bucket[c] = a + b;
3631            temp_induction_bucket[c] = a;
3632        }
3633
3634        d -= 1;
3635        for c in 0..2 * k_usize {
3636            let a = distinct_names[c];
3637            let b = temp_distinct_names[c];
3638            let next_d = b + d;
3639            distinct_names[c] = if b > 0 { next_d } else { a };
3640            temp_distinct_names[c] = a;
3641        }
3642        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
3643        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
3644    }
3645
3646    {
3647        let sa_ptr = SyncMutPtr::new(sa);
3648        run_rayon_with_threads(omp_num_threads, || {
3649            thread_state[..omp_num_threads]
3650                .par_iter_mut()
3651                .for_each(|state| {
3652                    let sa = unsafe { sa_ptr.as_slice() };
3653                    partial_sorting_scan_left_to_right_8u_block_place(
3654                        sa,
3655                        &mut state.buckets,
3656                        k,
3657                        &state.cache,
3658                        state.count,
3659                        state.position as SaSint,
3660                    );
3661                });
3662        });
3663    }
3664
3665    d
3666}
3667
3668/// Internal helper: partial sorting shift markers 8u (OpenMP variant).
3669#[doc(hidden)]
3670pub fn partial_sorting_shift_markers_8u_omp(
3671    sa: &mut [SaSint],
3672    n: SaSint,
3673    buckets: &[SaSint],
3674    threads: SaSint,
3675) {
3676    let temp_bucket = &buckets[4 * ALPHABET_SIZE..];
3677    let thread_count = if threads > 1 && n >= 65536 {
3678        usize::try_from(threads).expect("threads must be positive")
3679    } else {
3680        1
3681    };
3682    let c_step = buckets_index2(1, 0) as isize;
3683    let c_min = buckets_index2(1, 0) as isize;
3684    let c_max = buckets_index2(ALPHABET_SIZE - 1, 0) as isize;
3685    {
3686        let sa_ptr = SyncMutPtr::new(sa);
3687        let buckets_ref: &[SaSint] = buckets;
3688        let temp_bucket_ref: &[SaSint] = temp_bucket;
3689        run_rayon_with_threads(thread_count, || {
3690            (0..thread_count).into_par_iter().for_each(|t| {
3691                let mut c = c_max - (t as isize * c_step);
3692                let sa = unsafe { sa_ptr.as_slice() };
3693                while c >= c_min {
3694                    let c_usize = c as usize;
3695                    let mut i = temp_bucket_ref[c_usize] as isize - 1;
3696                    let mut j = buckets_ref[c_usize - buckets_index2(1, 0)] as isize + 3;
3697                    let mut s = SAINT_MIN;
3698
3699                    while i >= j {
3700                        let p0 = sa[i as usize];
3701                        let q0 = (p0 & SAINT_MIN) ^ s;
3702                        s ^= q0;
3703                        sa[i as usize] = p0 ^ q0;
3704
3705                        let p1 = sa[(i - 1) as usize];
3706                        let q1 = (p1 & SAINT_MIN) ^ s;
3707                        s ^= q1;
3708                        sa[(i - 1) as usize] = p1 ^ q1;
3709
3710                        let p2 = sa[(i - 2) as usize];
3711                        let q2 = (p2 & SAINT_MIN) ^ s;
3712                        s ^= q2;
3713                        sa[(i - 2) as usize] = p2 ^ q2;
3714
3715                        let p3 = sa[(i - 3) as usize];
3716                        let q3 = (p3 & SAINT_MIN) ^ s;
3717                        s ^= q3;
3718                        sa[(i - 3) as usize] = p3 ^ q3;
3719
3720                        i -= 4;
3721                    }
3722
3723                    j -= 3;
3724                    while i >= j {
3725                        let p = sa[i as usize];
3726                        let q = (p & SAINT_MIN) ^ s;
3727                        s ^= q;
3728                        sa[i as usize] = p ^ q;
3729                        i -= 1;
3730                    }
3731
3732                    c -= c_step * thread_count as isize;
3733                }
3734            });
3735        });
3736    }
3737}
3738
3739/// Internal helper: partial sorting shift markers 32s 6k (OpenMP variant).
3740#[doc(hidden)]
3741pub fn partial_sorting_shift_markers_32s_6k_omp(
3742    sa: &mut [SaSint],
3743    k: SaSint,
3744    buckets: &[SaSint],
3745    threads: SaSint,
3746) {
3747    let k_usize = usize::try_from(k).expect("k must be non-negative");
3748    let temp_bucket = &buckets[4 * k_usize..];
3749    let thread_count = if threads > 1 && k >= 65536 {
3750        usize::try_from(threads).expect("threads must be positive")
3751    } else {
3752        1
3753    };
3754    {
3755        let sa_ptr = SyncMutPtr::new(sa);
3756        let buckets_ref: &[SaSint] = buckets;
3757        let temp_bucket_ref: &[SaSint] = temp_bucket;
3758        run_rayon_with_threads(thread_count, || {
3759            (0..thread_count).into_par_iter().for_each(|t| {
3760                let mut c = k_usize as isize - 1 - t as isize;
3761                let sa = unsafe { sa_ptr.as_slice() };
3762                while c >= 1 {
3763                    let c_usize = c as usize;
3764                    let mut i = buckets_ref[buckets_index4(c_usize, 0)] as isize - 1;
3765                    let mut j = temp_bucket_ref[buckets_index2(c_usize - 1, 0)] as isize + 3;
3766                    let mut s = SAINT_MIN;
3767
3768                    while i >= j {
3769                        let p0 = sa[i as usize];
3770                        let q0 = (p0 & SAINT_MIN) ^ s;
3771                        s ^= q0;
3772                        sa[i as usize] = p0 ^ q0;
3773
3774                        let p1 = sa[(i - 1) as usize];
3775                        let q1 = (p1 & SAINT_MIN) ^ s;
3776                        s ^= q1;
3777                        sa[(i - 1) as usize] = p1 ^ q1;
3778
3779                        let p2 = sa[(i - 2) as usize];
3780                        let q2 = (p2 & SAINT_MIN) ^ s;
3781                        s ^= q2;
3782                        sa[(i - 2) as usize] = p2 ^ q2;
3783
3784                        let p3 = sa[(i - 3) as usize];
3785                        let q3 = (p3 & SAINT_MIN) ^ s;
3786                        s ^= q3;
3787                        sa[(i - 3) as usize] = p3 ^ q3;
3788
3789                        i -= 4;
3790                    }
3791
3792                    j -= 3;
3793                    while i >= j {
3794                        let p = sa[i as usize];
3795                        let q = (p & SAINT_MIN) ^ s;
3796                        s ^= q;
3797                        sa[i as usize] = p ^ q;
3798                        i -= 1;
3799                    }
3800
3801                    c -= thread_count as isize;
3802                }
3803            });
3804        });
3805    }
3806}
3807
3808/// Internal helper: partial sorting shift markers 32s 4k.
3809#[doc(hidden)]
3810pub fn partial_sorting_shift_markers_32s_4k(sa: &mut [SaSint], n: SaSint) {
3811    let mut i = n as isize - 1;
3812    let mut s = SUFFIX_GROUP_MARKER;
3813    while i >= 3 {
3814        let p0 = sa[i as usize];
3815        let q0 =
3816            ((p0 & SUFFIX_GROUP_MARKER) ^ s) & (((p0 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3817        s ^= q0;
3818        sa[i as usize] = p0 ^ q0;
3819
3820        let p1 = sa[(i - 1) as usize];
3821        let q1 =
3822            ((p1 & SUFFIX_GROUP_MARKER) ^ s) & (((p1 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3823        s ^= q1;
3824        sa[(i - 1) as usize] = p1 ^ q1;
3825
3826        let p2 = sa[(i - 2) as usize];
3827        let q2 =
3828            ((p2 & SUFFIX_GROUP_MARKER) ^ s) & (((p2 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3829        s ^= q2;
3830        sa[(i - 2) as usize] = p2 ^ q2;
3831
3832        let p3 = sa[(i - 3) as usize];
3833        let q3 =
3834            ((p3 & SUFFIX_GROUP_MARKER) ^ s) & (((p3 > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3835        s ^= q3;
3836        sa[(i - 3) as usize] = p3 ^ q3;
3837
3838        i -= 4;
3839    }
3840
3841    while i >= 0 {
3842        let p = sa[i as usize];
3843        let q = ((p & SUFFIX_GROUP_MARKER) ^ s) & (((p > 0) as SaSint) << (SUFFIX_GROUP_BIT - 1));
3844        s ^= q;
3845        sa[i as usize] = p ^ q;
3846        i -= 1;
3847    }
3848}
3849
3850/// Internal helper: partial sorting shift buckets 32s 6k.
3851#[doc(hidden)]
3852pub fn partial_sorting_shift_buckets_32s_6k(k: SaSint, buckets: &mut [SaSint]) {
3853    let k_usize = usize::try_from(k).expect("k must be non-negative");
3854    let temp_offset = 4 * k_usize;
3855    for i in 0..k_usize {
3856        let src = buckets_index2(i, 0);
3857        let dst = 2 * src;
3858        buckets[dst] = buckets[temp_offset + src];
3859        buckets[dst + 1] = buckets[temp_offset + src + 1];
3860    }
3861}
3862
3863/// Internal helper: partial sorting scan right to left 8u.
3864#[doc(hidden)]
3865pub fn partial_sorting_scan_right_to_left_8u(
3866    t: &[u8],
3867    sa: &mut [SaSint],
3868    buckets: &mut [SaSint],
3869    mut d: SaSint,
3870    omp_block_start: FastSint,
3871    omp_block_size: FastSint,
3872) -> SaSint {
3873    if omp_block_size <= 0 {
3874        return d;
3875    }
3876
3877    let prefetch_distance = 64usize;
3878    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * ALPHABET_SIZE);
3879    let distinct_names = &mut distinct_names_all[..2 * ALPHABET_SIZE];
3880
3881    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
3882    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
3883    let mut i = start + size - 1;
3884    let mut j = start + prefetch_distance + 1;
3885
3886    while i >= j {
3887        let mut p0 = sa[i];
3888        d += SaSint::from(p0 < 0);
3889        p0 &= SAINT_MAX;
3890
3891        let p0_usize = p0 as usize;
3892        let v0 = buckets_index2(
3893            t[p0_usize - 1] as usize,
3894            usize::from(t[p0_usize - 2] > t[p0_usize - 1]),
3895        );
3896
3897        induction_bucket[v0] -= 1;
3898        let slot0 = induction_bucket[v0] as usize;
3899        sa[slot0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
3900        distinct_names[v0] = d;
3901
3902        let mut p1 = sa[i - 1];
3903        d += SaSint::from(p1 < 0);
3904        p1 &= SAINT_MAX;
3905
3906        let p1_usize = p1 as usize;
3907        let v1 = buckets_index2(
3908            t[p1_usize - 1] as usize,
3909            usize::from(t[p1_usize - 2] > t[p1_usize - 1]),
3910        );
3911
3912        induction_bucket[v1] -= 1;
3913        let slot1 = induction_bucket[v1] as usize;
3914        sa[slot1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
3915        distinct_names[v1] = d;
3916
3917        i -= 2;
3918    }
3919
3920    j = if start + prefetch_distance < start + size {
3921        start
3922    } else {
3923        start
3924    };
3925    while i >= j {
3926        let mut p = sa[i];
3927        d += SaSint::from(p < 0);
3928        p &= SAINT_MAX;
3929
3930        let p_usize = p as usize;
3931        let v = buckets_index2(
3932            t[p_usize - 1] as usize,
3933            usize::from(t[p_usize - 2] > t[p_usize - 1]),
3934        );
3935
3936        induction_bucket[v] -= 1;
3937        let slot = induction_bucket[v] as usize;
3938        sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
3939        distinct_names[v] = d;
3940
3941        if i == 0 {
3942            break;
3943        }
3944        i -= 1;
3945    }
3946
3947    d
3948}
3949
3950/// Internal helper: partial gsa scan right to left 8u.
3951#[doc(hidden)]
3952pub fn partial_gsa_scan_right_to_left_8u(
3953    t: &[u8],
3954    sa: &mut [SaSint],
3955    buckets: &mut [SaSint],
3956    mut d: SaSint,
3957    omp_block_start: FastSint,
3958    omp_block_size: FastSint,
3959) -> SaSint {
3960    if omp_block_size <= 0 {
3961        return d;
3962    }
3963
3964    let prefetch_distance = 64usize;
3965    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * ALPHABET_SIZE);
3966    let distinct_names = &mut distinct_names_all[..2 * ALPHABET_SIZE];
3967
3968    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
3969    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
3970    let mut i = start + size - 1;
3971    let mut j = start + prefetch_distance + 1;
3972
3973    while i >= j {
3974        let mut p0 = sa[i];
3975        d += SaSint::from(p0 < 0);
3976        p0 &= SAINT_MAX;
3977
3978        let p0_usize = p0 as usize;
3979        let v0 = buckets_index2(
3980            t[p0_usize - 1] as usize,
3981            usize::from(t[p0_usize - 2] > t[p0_usize - 1]),
3982        );
3983
3984        if v0 != 1 {
3985            induction_bucket[v0] -= 1;
3986            let slot0 = induction_bucket[v0] as usize;
3987            sa[slot0] = (p0 - 1) | (((distinct_names[v0] != d) as SaSint) << (SAINT_BIT - 1));
3988            distinct_names[v0] = d;
3989        }
3990
3991        let mut p1 = sa[i - 1];
3992        d += SaSint::from(p1 < 0);
3993        p1 &= SAINT_MAX;
3994
3995        let p1_usize = p1 as usize;
3996        let v1 = buckets_index2(
3997            t[p1_usize - 1] as usize,
3998            usize::from(t[p1_usize - 2] > t[p1_usize - 1]),
3999        );
4000
4001        if v1 != 1 {
4002            induction_bucket[v1] -= 1;
4003            let slot1 = induction_bucket[v1] as usize;
4004            sa[slot1] = (p1 - 1) | (((distinct_names[v1] != d) as SaSint) << (SAINT_BIT - 1));
4005            distinct_names[v1] = d;
4006        }
4007
4008        i -= 2;
4009    }
4010
4011    j = start;
4012    while i >= j {
4013        let mut p = sa[i];
4014        d += SaSint::from(p < 0);
4015        p &= SAINT_MAX;
4016
4017        let p_usize = p as usize;
4018        let v = buckets_index2(
4019            t[p_usize - 1] as usize,
4020            usize::from(t[p_usize - 2] > t[p_usize - 1]),
4021        );
4022
4023        if v != 1 {
4024            induction_bucket[v] -= 1;
4025            let slot = induction_bucket[v] as usize;
4026            sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4027            distinct_names[v] = d;
4028        }
4029
4030        if i == 0 {
4031            break;
4032        }
4033        i -= 1;
4034    }
4035
4036    d
4037}
4038
4039/// Internal helper: partial sorting scan right to left 8u block prepare.
4040#[doc(hidden)]
4041pub fn partial_sorting_scan_right_to_left_8u_block_prepare(
4042    t: &[u8],
4043    sa: &[SaSint],
4044    k: SaSint,
4045    buckets: &mut [SaSint],
4046    cache: &mut [ThreadCache],
4047    omp_block_start: FastSint,
4048    omp_block_size: FastSint,
4049) -> (FastSint, FastSint) {
4050    let k_usize = usize::try_from(k).expect("k must be non-negative");
4051    let (induction_bucket, distinct_names_all) = buckets.split_at_mut(2 * k_usize);
4052    let distinct_names = &mut distinct_names_all[..2 * k_usize];
4053    induction_bucket.fill(0);
4054    distinct_names.fill(0);
4055
4056    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4057    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4058    let mut count = 0usize;
4059    let mut d = 1;
4060
4061    let mut i = start + size;
4062    while i > start {
4063        i -= 1;
4064
4065        let mut p = sa[i];
4066        cache[count].index = p;
4067        d += SaSint::from(p < 0);
4068        p &= SAINT_MAX;
4069
4070        let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4071        let v = buckets_index2(
4072            t[p_usize - 1] as usize,
4073            usize::from(t[p_usize - 2] > t[p_usize - 1]),
4074        );
4075
4076        cache[count].symbol = v as SaSint;
4077        induction_bucket[v] += 1;
4078        distinct_names[v] = d;
4079        count += 1;
4080    }
4081
4082    ((d - 1) as FastSint, count as FastSint)
4083}
4084
4085/// Internal helper: partial sorting scan right to left 8u block place.
4086#[doc(hidden)]
4087pub fn partial_sorting_scan_right_to_left_8u_block_place(
4088    sa: &mut [SaSint],
4089    buckets: &mut [SaSint],
4090    k: SaSint,
4091    cache: &[ThreadCache],
4092    count: FastSint,
4093    mut d: SaSint,
4094) {
4095    let split = 2 * usize::try_from(k).expect("k must be non-negative");
4096    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
4097
4098    let count = usize::try_from(count).expect("count must be non-negative");
4099    for entry in &cache[..count] {
4100        let p = entry.index;
4101        d += SaSint::from(p < 0);
4102        let v = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
4103        induction_bucket[v] -= 1;
4104        let slot = usize::try_from(induction_bucket[v]).expect("bucket slot must be non-negative");
4105        sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4106        distinct_names[v] = d;
4107    }
4108}
4109
4110/// Internal helper: partial gsa scan right to left 8u block place.
4111#[doc(hidden)]
4112pub fn partial_gsa_scan_right_to_left_8u_block_place(
4113    sa: &mut [SaSint],
4114    buckets: &mut [SaSint],
4115    k: SaSint,
4116    cache: &[ThreadCache],
4117    count: FastSint,
4118    mut d: SaSint,
4119) {
4120    let split = 2 * usize::try_from(k).expect("k must be non-negative");
4121    let (induction_bucket, distinct_names) = buckets.split_at_mut(split);
4122
4123    let count = usize::try_from(count).expect("count must be non-negative");
4124    for entry in &cache[..count] {
4125        let p = entry.index;
4126        d += SaSint::from(p < 0);
4127        let v = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
4128        if v != 1 {
4129            induction_bucket[v] -= 1;
4130            let slot =
4131                usize::try_from(induction_bucket[v]).expect("bucket slot must be non-negative");
4132            sa[slot] = (p - 1) | (((distinct_names[v] != d) as SaSint) << (SAINT_BIT - 1));
4133            distinct_names[v] = d;
4134        }
4135    }
4136}
4137
4138/// Internal helper: partial sorting scan right to left 8u block (OpenMP variant).
4139#[doc(hidden)]
4140pub fn partial_sorting_scan_right_to_left_8u_block_omp(
4141    t: &[u8],
4142    sa: &mut [SaSint],
4143    k: SaSint,
4144    buckets: &mut [SaSint],
4145    d: SaSint,
4146    block_start: FastSint,
4147    block_size: FastSint,
4148    threads: SaSint,
4149    thread_state: &mut [ThreadState],
4150) -> SaSint {
4151    let mut d = d;
4152    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
4153    let k_usize = usize::try_from(k).expect("k must be non-negative");
4154    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
4155        usize::try_from(threads)
4156            .expect("threads must be non-negative")
4157            .min(thread_state.len())
4158            .max(1)
4159    } else {
4160        1
4161    };
4162    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
4163
4164    if omp_num_threads == 1 {
4165        return partial_sorting_scan_right_to_left_8u(t, sa, buckets, d, block_start, block_size);
4166    }
4167
4168    {
4169        let sa_ro: &[SaSint] = sa;
4170        run_rayon_with_threads(omp_num_threads, || {
4171            thread_state[..omp_num_threads]
4172                .par_iter_mut()
4173                .enumerate()
4174                .for_each(|(omp_thread_num, state)| {
4175                    let mut omp_block_start = omp_thread_num * omp_block_stride;
4176                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
4177                        omp_block_stride
4178                    } else {
4179                        block_size_usize - omp_block_start
4180                    };
4181                    omp_block_start +=
4182                        usize::try_from(block_start).expect("block_start must be non-negative");
4183
4184                    let (position, count) = partial_sorting_scan_right_to_left_8u_block_prepare(
4185                        t,
4186                        sa_ro,
4187                        k,
4188                        &mut state.buckets,
4189                        &mut state.cache,
4190                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
4191                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
4192                    );
4193                    state.position = position;
4194                    state.count = count;
4195                });
4196        });
4197    }
4198
4199    let distinct_offset = 2 * ALPHABET_SIZE;
4200    let (induction_bucket, distinct_tail) = buckets.split_at_mut(distinct_offset);
4201    let distinct_names = &mut distinct_tail[..2 * k_usize];
4202
4203    for tnum in (0..omp_num_threads).rev() {
4204        let state = &mut thread_state[tnum];
4205        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
4206        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
4207
4208        for c in 0..2 * k_usize {
4209            let a = induction_bucket[c];
4210            let b = temp_induction_bucket[c];
4211            induction_bucket[c] = a - b;
4212            temp_induction_bucket[c] = a;
4213        }
4214
4215        d -= 1;
4216        for c in 0..2 * k_usize {
4217            let a = distinct_names[c];
4218            let b = temp_distinct_names[c];
4219            let next_d = b + d;
4220            distinct_names[c] = if b > 0 { next_d } else { a };
4221            temp_distinct_names[c] = a;
4222        }
4223        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
4224        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
4225    }
4226
4227    {
4228        let sa_ptr = SyncMutPtr::new(sa);
4229        run_rayon_with_threads(omp_num_threads, || {
4230            thread_state[..omp_num_threads]
4231                .par_iter_mut()
4232                .for_each(|state| {
4233                    let sa = unsafe { sa_ptr.as_slice() };
4234                    partial_sorting_scan_right_to_left_8u_block_place(
4235                        sa,
4236                        &mut state.buckets,
4237                        k,
4238                        &state.cache,
4239                        state.count,
4240                        state.position as SaSint,
4241                    );
4242                });
4243        });
4244    }
4245
4246    d
4247}
4248
4249/// Internal helper: partial gsa scan right to left 8u block (OpenMP variant).
4250#[doc(hidden)]
4251pub fn partial_gsa_scan_right_to_left_8u_block_omp(
4252    t: &[u8],
4253    sa: &mut [SaSint],
4254    k: SaSint,
4255    buckets: &mut [SaSint],
4256    d: SaSint,
4257    block_start: FastSint,
4258    block_size: FastSint,
4259    threads: SaSint,
4260    thread_state: &mut [ThreadState],
4261) -> SaSint {
4262    let mut d = d;
4263    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
4264    let k_usize = usize::try_from(k).expect("k must be non-negative");
4265    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
4266        usize::try_from(threads)
4267            .expect("threads must be non-negative")
4268            .min(thread_state.len())
4269            .max(1)
4270    } else {
4271        1
4272    };
4273    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
4274
4275    if omp_num_threads == 1 {
4276        return partial_gsa_scan_right_to_left_8u(t, sa, buckets, d, block_start, block_size);
4277    }
4278
4279    {
4280        let sa_ro: &[SaSint] = sa;
4281        run_rayon_with_threads(omp_num_threads, || {
4282            thread_state[..omp_num_threads]
4283                .par_iter_mut()
4284                .enumerate()
4285                .for_each(|(omp_thread_num, state)| {
4286                    let mut omp_block_start = omp_thread_num * omp_block_stride;
4287                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
4288                        omp_block_stride
4289                    } else {
4290                        block_size_usize - omp_block_start
4291                    };
4292                    omp_block_start +=
4293                        usize::try_from(block_start).expect("block_start must be non-negative");
4294
4295                    let (position, count) = partial_sorting_scan_right_to_left_8u_block_prepare(
4296                        t,
4297                        sa_ro,
4298                        k,
4299                        &mut state.buckets,
4300                        &mut state.cache,
4301                        FastSint::try_from(omp_block_start).expect("block start must fit FastSint"),
4302                        FastSint::try_from(omp_block_size).expect("block size must fit FastSint"),
4303                    );
4304                    state.position = position;
4305                    state.count = count;
4306                });
4307        });
4308    }
4309
4310    let distinct_offset = 2 * ALPHABET_SIZE;
4311    let (induction_bucket, distinct_tail) = buckets.split_at_mut(distinct_offset);
4312    let distinct_names = &mut distinct_tail[..2 * k_usize];
4313
4314    for tnum in (0..omp_num_threads).rev() {
4315        let state = &mut thread_state[tnum];
4316        let (temp_induction_bucket, temp_tail) = state.buckets.split_at_mut(2 * k_usize);
4317        let temp_distinct_names = &mut temp_tail[..2 * k_usize];
4318
4319        for c in 0..2 * k_usize {
4320            let a = induction_bucket[c];
4321            let b = temp_induction_bucket[c];
4322            induction_bucket[c] = a - b;
4323            temp_induction_bucket[c] = a;
4324        }
4325
4326        d -= 1;
4327        for c in 0..2 * k_usize {
4328            let a = distinct_names[c];
4329            let b = temp_distinct_names[c];
4330            let next_d = b + d;
4331            distinct_names[c] = if b > 0 { next_d } else { a };
4332            temp_distinct_names[c] = a;
4333        }
4334        d += 1 + SaSint::try_from(state.position).expect("position must fit SaSint");
4335        state.position = FastSint::try_from(d).expect("d must fit FastSint") - state.position;
4336    }
4337
4338    {
4339        let sa_ptr = SyncMutPtr::new(sa);
4340        run_rayon_with_threads(omp_num_threads, || {
4341            thread_state[..omp_num_threads]
4342                .par_iter_mut()
4343                .for_each(|state| {
4344                    let sa = unsafe { sa_ptr.as_slice() };
4345                    partial_gsa_scan_right_to_left_8u_block_place(
4346                        sa,
4347                        &mut state.buckets,
4348                        k,
4349                        &state.cache,
4350                        state.count,
4351                        state.position as SaSint,
4352                    );
4353                });
4354        });
4355    }
4356
4357    d
4358}
4359
4360/// Internal helper: partial sorting scan right to left 8u (OpenMP variant).
4361#[doc(hidden)]
4362pub fn partial_sorting_scan_right_to_left_8u_omp(
4363    t: &[u8],
4364    sa: &mut [SaSint],
4365    n: SaSint,
4366    k: SaSint,
4367    buckets: &mut [SaSint],
4368    first_lms_suffix: SaSint,
4369    left_suffixes_count: SaSint,
4370    mut d: SaSint,
4371    threads: SaSint,
4372    thread_state: &mut [ThreadState],
4373) {
4374    let scan_start = left_suffixes_count as FastSint + 1;
4375    let scan_end = n as FastSint - first_lms_suffix as FastSint;
4376
4377    if threads == 1 || (scan_end - scan_start) < 65_536 {
4378        let _ = partial_sorting_scan_right_to_left_8u(
4379            t,
4380            sa,
4381            buckets,
4382            d,
4383            scan_start,
4384            scan_end - scan_start,
4385        );
4386        return;
4387    }
4388
4389    let distinct_offset = 2 * ALPHABET_SIZE;
4390
4391    let mut block_start = usize::try_from(scan_end - 1).expect("scan end must be positive");
4392    let scan_start_usize = usize::try_from(scan_start).expect("scan_start must be non-negative");
4393    let threads_usize = usize::try_from(threads)
4394        .expect("threads must be non-negative")
4395        .min(thread_state.len())
4396        .max(1);
4397
4398    while block_start >= scan_start_usize {
4399        if sa[block_start] == 0 {
4400            if block_start == 0 {
4401                break;
4402            }
4403            block_start -= 1;
4404        } else {
4405            let mut block_max_end = block_start.saturating_sub(
4406                threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize),
4407            );
4408            if block_max_end + 1 < scan_start_usize {
4409                block_max_end = scan_start_usize.saturating_sub(1);
4410            }
4411            let mut block_end = block_start - 1;
4412            while block_end > block_max_end && sa[block_end] != 0 {
4413                block_end -= 1;
4414            }
4415            let block_size = block_start - block_end;
4416
4417            if block_size < 32 {
4418                while block_start > block_end {
4419                    let p = sa[block_start];
4420                    d += SaSint::from(p < 0);
4421                    let p = p & SAINT_MAX;
4422                    let v = buckets_index2(
4423                        t[(p - 1) as usize] as usize,
4424                        usize::from(t[(p - 2) as usize] > t[(p - 1) as usize]),
4425                    );
4426                    buckets[v] -= 1;
4427                    let slot =
4428                        usize::try_from(buckets[v]).expect("bucket slot must be non-negative");
4429                    sa[slot] = (p - 1)
4430                        | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
4431                    buckets[distinct_offset + v] = d;
4432
4433                    if block_start == 0 {
4434                        break;
4435                    }
4436                    block_start -= 1;
4437                }
4438            } else {
4439                d = partial_sorting_scan_right_to_left_8u_block_omp(
4440                    t,
4441                    sa,
4442                    k,
4443                    buckets,
4444                    d,
4445                    FastSint::try_from(block_end + 1).expect("block start must fit FastSint"),
4446                    FastSint::try_from(block_size).expect("block size must fit FastSint"),
4447                    threads,
4448                    thread_state,
4449                );
4450                block_start = block_end;
4451            }
4452        }
4453    }
4454}
4455
4456/// Internal helper: partial gsa scan right to left 8u (OpenMP variant).
4457#[doc(hidden)]
4458pub fn partial_gsa_scan_right_to_left_8u_omp(
4459    t: &[u8],
4460    sa: &mut [SaSint],
4461    n: SaSint,
4462    k: SaSint,
4463    buckets: &mut [SaSint],
4464    first_lms_suffix: SaSint,
4465    left_suffixes_count: SaSint,
4466    mut d: SaSint,
4467    threads: SaSint,
4468    thread_state: &mut [ThreadState],
4469) {
4470    let scan_start = left_suffixes_count as FastSint + 1;
4471    let scan_end = n as FastSint - first_lms_suffix as FastSint;
4472
4473    if threads == 1 || (scan_end - scan_start) < 65_536 {
4474        let _ =
4475            partial_gsa_scan_right_to_left_8u(t, sa, buckets, d, scan_start, scan_end - scan_start);
4476        return;
4477    }
4478
4479    let distinct_offset = 2 * ALPHABET_SIZE;
4480    let mut block_start = usize::try_from(scan_end - 1).expect("scan end must be positive");
4481    let scan_start_usize = usize::try_from(scan_start).expect("scan_start must be non-negative");
4482    let threads_usize = usize::try_from(threads)
4483        .expect("threads must be non-negative")
4484        .min(thread_state.len())
4485        .max(1);
4486
4487    while block_start >= scan_start_usize {
4488        if sa[block_start] == 0 {
4489            if block_start == 0 {
4490                break;
4491            }
4492            block_start -= 1;
4493        } else {
4494            let mut block_max_end = block_start.saturating_sub(
4495                threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize),
4496            );
4497            if block_max_end + 1 < scan_start_usize {
4498                block_max_end = scan_start_usize.saturating_sub(1);
4499            }
4500            let mut block_end = block_start - 1;
4501            while block_end > block_max_end && sa[block_end] != 0 {
4502                block_end -= 1;
4503            }
4504            let block_size = block_start - block_end;
4505
4506            if block_size < 32 {
4507                while block_start > block_end {
4508                    let p = sa[block_start];
4509                    d += SaSint::from(p < 0);
4510                    let p = p & SAINT_MAX;
4511                    let v = buckets_index2(
4512                        t[(p - 1) as usize] as usize,
4513                        usize::from(t[(p - 2) as usize] > t[(p - 1) as usize]),
4514                    );
4515                    if v != 1 {
4516                        buckets[v] -= 1;
4517                        let slot =
4518                            usize::try_from(buckets[v]).expect("bucket slot must be non-negative");
4519                        sa[slot] = (p - 1)
4520                            | (((buckets[distinct_offset + v] != d) as SaSint) << (SAINT_BIT - 1));
4521                        buckets[distinct_offset + v] = d;
4522                    }
4523
4524                    if block_start == 0 {
4525                        break;
4526                    }
4527                    block_start -= 1;
4528                }
4529            } else {
4530                d = partial_gsa_scan_right_to_left_8u_block_omp(
4531                    t,
4532                    sa,
4533                    k,
4534                    buckets,
4535                    d,
4536                    FastSint::try_from(block_end + 1).expect("block start must fit FastSint"),
4537                    FastSint::try_from(block_size).expect("block size must fit FastSint"),
4538                    threads,
4539                    thread_state,
4540                );
4541                block_start = block_end;
4542            }
4543        }
4544    }
4545}
4546
4547/// Internal helper: partial sorting scan right to left 32s 6k.
4548#[doc(hidden)]
4549pub fn partial_sorting_scan_right_to_left_32s_6k(
4550    t: &[SaSint],
4551    sa: &mut [SaSint],
4552    buckets: &mut [SaSint],
4553    mut d: SaSint,
4554    omp_block_start: FastSint,
4555    omp_block_size: FastSint,
4556) -> SaSint {
4557    if omp_block_size <= 0 {
4558        return d;
4559    }
4560
4561    let prefetch_distance: FastSint = 64;
4562    let mut i = omp_block_start + omp_block_size - 1;
4563    let mut j = omp_block_start + 2 * prefetch_distance + 1;
4564
4565    while i >= j {
4566        let mut p0 = sa[i as usize];
4567        d += SaSint::from(p0 < 0);
4568        p0 &= SAINT_MAX;
4569        let p0u = p0 as usize;
4570        let v0 = buckets_index4(t[p0u - 1] as usize, usize::from(t[p0u - 2] > t[p0u - 1]));
4571        buckets[v0] -= 1;
4572        let slot0 = buckets[v0] as usize;
4573        sa[slot0] = (p0 - 1) | (((buckets[2 + v0] != d) as SaSint) << (SAINT_BIT - 1));
4574        buckets[2 + v0] = d;
4575
4576        let mut p1 = sa[(i - 1) as usize];
4577        d += SaSint::from(p1 < 0);
4578        p1 &= SAINT_MAX;
4579        let p1u = p1 as usize;
4580        let v1 = buckets_index4(t[p1u - 1] as usize, usize::from(t[p1u - 2] > t[p1u - 1]));
4581        buckets[v1] -= 1;
4582        let slot1 = buckets[v1] as usize;
4583        sa[slot1] = (p1 - 1) | (((buckets[2 + v1] != d) as SaSint) << (SAINT_BIT - 1));
4584        buckets[2 + v1] = d;
4585
4586        i -= 2;
4587    }
4588
4589    j -= 2 * prefetch_distance + 1;
4590    while i >= j {
4591        let mut p = sa[i as usize];
4592        d += SaSint::from(p < 0);
4593        p &= SAINT_MAX;
4594        let pu = p as usize;
4595        let v = buckets_index4(t[pu - 1] as usize, usize::from(t[pu - 2] > t[pu - 1]));
4596
4597        buckets[v] -= 1;
4598        let slot = buckets[v] as usize;
4599        sa[slot] = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
4600        buckets[2 + v] = d;
4601        i -= 1;
4602    }
4603
4604    d
4605}
4606
4607/// Internal helper: partial sorting scan right to left 32s 4k.
4608#[doc(hidden)]
4609pub fn partial_sorting_scan_right_to_left_32s_4k(
4610    t: &[SaSint],
4611    sa: &mut [SaSint],
4612    k: SaSint,
4613    buckets: &mut [SaSint],
4614    mut d: SaSint,
4615    omp_block_start: FastSint,
4616    omp_block_size: FastSint,
4617) -> SaSint {
4618    if omp_block_size <= 0 {
4619        return d;
4620    }
4621
4622    let k_usize = usize::try_from(k).expect("k must be non-negative");
4623    let prefetch_distance: FastSint = 64;
4624    let induction_offset = 3 * k_usize;
4625
4626    let mut i = omp_block_start + omp_block_size - 1;
4627    let mut j = omp_block_start + 2 * prefetch_distance + 1;
4628
4629    while i >= j {
4630        let i0 = i as usize;
4631        let mut p0 = sa[i0];
4632        if p0 > 0 {
4633            sa[i0] = 0;
4634            d += p0 >> (SUFFIX_GROUP_BIT - 1);
4635            p0 &= !SUFFIX_GROUP_MARKER;
4636
4637            let p0u = p0 as usize;
4638            let c0 = t[p0u - 1];
4639            let f0 = usize::from(t[p0u - 2] > c0);
4640            let v0 = buckets_index2(c0 as usize, f0);
4641            let c0u = c0 as usize;
4642            buckets[induction_offset + c0u] -= 1;
4643            let slot0 = buckets[induction_offset + c0u] as usize;
4644            sa[slot0] = (p0 - 1)
4645                | ((f0 as SaSint) << (SAINT_BIT - 1))
4646                | (((buckets[v0] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4647            buckets[v0] = d;
4648        }
4649
4650        let i1 = (i - 1) as usize;
4651        let mut p1 = sa[i1];
4652        if p1 > 0 {
4653            sa[i1] = 0;
4654            d += p1 >> (SUFFIX_GROUP_BIT - 1);
4655            p1 &= !SUFFIX_GROUP_MARKER;
4656
4657            let p1u = p1 as usize;
4658            let c1 = t[p1u - 1];
4659            let f1 = usize::from(t[p1u - 2] > c1);
4660            let v1 = buckets_index2(c1 as usize, f1);
4661            let c1u = c1 as usize;
4662            buckets[induction_offset + c1u] -= 1;
4663            let slot1 = buckets[induction_offset + c1u] as usize;
4664            sa[slot1] = (p1 - 1)
4665                | ((f1 as SaSint) << (SAINT_BIT - 1))
4666                | (((buckets[v1] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4667            buckets[v1] = d;
4668        }
4669
4670        i -= 2;
4671    }
4672
4673    j -= 2 * prefetch_distance + 1;
4674    while i >= j {
4675        let iu = i as usize;
4676        let mut p = sa[iu];
4677        if p > 0 {
4678            sa[iu] = 0;
4679            d += p >> (SUFFIX_GROUP_BIT - 1);
4680            p &= !SUFFIX_GROUP_MARKER;
4681
4682            let pu = p as usize;
4683            let c = t[pu - 1];
4684            let f = usize::from(t[pu - 2] > c);
4685            let v = buckets_index2(c as usize, f);
4686            let cu = c as usize;
4687            buckets[induction_offset + cu] -= 1;
4688            let slot = buckets[induction_offset + cu] as usize;
4689            sa[slot] = (p - 1)
4690                | ((f as SaSint) << (SAINT_BIT - 1))
4691                | (((buckets[v] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
4692            buckets[v] = d;
4693        }
4694        i -= 1;
4695    }
4696
4697    d
4698}
4699
4700/// Internal helper: partial sorting scan right to left 32s 1k.
4701#[doc(hidden)]
4702pub fn partial_sorting_scan_right_to_left_32s_1k(
4703    t: &[SaSint],
4704    sa: &mut [SaSint],
4705    induction_bucket: &mut [SaSint],
4706    omp_block_start: FastSint,
4707    omp_block_size: FastSint,
4708) {
4709    if omp_block_size <= 0 {
4710        return;
4711    }
4712
4713    let prefetch_distance = 64usize;
4714    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4715    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4716    let mut i = (start + size - 1) as isize;
4717    let mut j = (start + 2 * prefetch_distance + 1) as isize;
4718
4719    while i >= j {
4720        let p0 = sa[i as usize];
4721        if p0 > 0 {
4722            sa[i as usize] = 0;
4723            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
4724            let bucket_index0 =
4725                usize::try_from(t[p0_usize - 1]).expect("bucket symbol must be non-negative");
4726            induction_bucket[bucket_index0] -= 1;
4727            let slot0 = usize::try_from(induction_bucket[bucket_index0])
4728                .expect("bucket slot must be non-negative");
4729            sa[slot0] = (p0 - 1)
4730                | ((usize::from(t[p0_usize - 2] > t[p0_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4731        }
4732        let p1 = sa[(i - 1) as usize];
4733        if p1 > 0 {
4734            sa[(i - 1) as usize] = 0;
4735            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
4736            let bucket_index1 =
4737                usize::try_from(t[p1_usize - 1]).expect("bucket symbol must be non-negative");
4738            induction_bucket[bucket_index1] -= 1;
4739            let slot1 = usize::try_from(induction_bucket[bucket_index1])
4740                .expect("bucket slot must be non-negative");
4741            sa[slot1] = (p1 - 1)
4742                | ((usize::from(t[p1_usize - 2] > t[p1_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4743        }
4744
4745        i -= 2;
4746    }
4747
4748    j -= (2 * prefetch_distance + 1) as isize;
4749    while i >= j {
4750        let p = sa[i as usize];
4751        if p > 0 {
4752            sa[i as usize] = 0;
4753            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4754            let bucket_index =
4755                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative");
4756            induction_bucket[bucket_index] -= 1;
4757            let slot = usize::try_from(induction_bucket[bucket_index])
4758                .expect("bucket slot must be non-negative");
4759            sa[slot] = (p - 1)
4760                | ((usize::from(t[p_usize - 2] > t[p_usize - 1]) as SaSint) << (SAINT_BIT - 1));
4761        }
4762        if i == 0 {
4763            break;
4764        }
4765        i -= 1;
4766    }
4767}
4768
4769/// Internal helper: partial sorting scan right to left 32s 6k block gather.
4770#[doc(hidden)]
4771pub fn partial_sorting_scan_right_to_left_32s_6k_block_gather(
4772    t: &[SaSint],
4773    sa: &[SaSint],
4774    cache: &mut [ThreadCache],
4775    omp_block_start: FastSint,
4776    omp_block_size: FastSint,
4777) {
4778    if omp_block_size <= 0 {
4779        return;
4780    }
4781
4782    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4783    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4784    for offset in 0..size {
4785        let i = start + offset;
4786        let mut p = sa[i];
4787        let mut symbol = 0usize;
4788        p &= SAINT_MAX;
4789        if p != 0 {
4790            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4791            symbol = buckets_index4(
4792                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
4793                usize::from(t[p_usize - 2] > t[p_usize - 1]),
4794            );
4795        }
4796        cache[offset].index = sa[i];
4797        cache[offset].symbol = symbol as SaSint;
4798    }
4799}
4800
4801/// Internal helper: partial sorting scan right to left 32s 4k block gather.
4802#[doc(hidden)]
4803pub fn partial_sorting_scan_right_to_left_32s_4k_block_gather(
4804    t: &[SaSint],
4805    sa: &mut [SaSint],
4806    cache: &mut [ThreadCache],
4807    omp_block_start: FastSint,
4808    omp_block_size: FastSint,
4809) {
4810    if omp_block_size <= 0 {
4811        return;
4812    }
4813
4814    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4815    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4816    for offset in 0..size {
4817        let i = start + offset;
4818        let mut symbol = SAINT_MIN;
4819        let mut p = sa[i];
4820        if p > 0 {
4821            sa[i] = 0;
4822            cache[offset].index = p;
4823            p &= !SUFFIX_GROUP_MARKER;
4824            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
4825            symbol = buckets_index2(
4826                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
4827                usize::from(t[p_usize - 2] > t[p_usize - 1]),
4828            ) as SaSint;
4829        }
4830        cache[offset].symbol = symbol;
4831    }
4832}
4833
4834/// Internal helper: partial sorting scan right to left 32s 1k block gather.
4835#[doc(hidden)]
4836pub fn partial_sorting_scan_right_to_left_32s_1k_block_gather(
4837    t: &[SaSint],
4838    sa: &mut [SaSint],
4839    cache: &mut [ThreadCache],
4840    omp_block_start: FastSint,
4841    omp_block_size: FastSint,
4842) {
4843    if omp_block_size <= 0 {
4844        return;
4845    }
4846    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
4847    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4848    for offset in 0..size {
4849        let i = start + offset;
4850        let mut symbol = SAINT_MIN;
4851        let p = sa[i];
4852        if p > 0 {
4853            sa[i] = 0;
4854            cache[offset].index = (p - 1)
4855                | ((usize::from(t[p as usize - 2] > t[p as usize - 1]) as SaSint)
4856                    << (SAINT_BIT - 1));
4857            symbol = t[p as usize - 1];
4858        }
4859        cache[offset].symbol = symbol;
4860    }
4861}
4862
4863/// Internal helper: partial sorting scan right to left 32s 6k block sort.
4864#[doc(hidden)]
4865pub fn partial_sorting_scan_right_to_left_32s_6k_block_sort(
4866    t: &[SaSint],
4867    buckets: &mut [SaSint],
4868    mut d: SaSint,
4869    cache: &mut [ThreadCache],
4870    omp_block_start: FastSint,
4871    omp_block_size: FastSint,
4872) -> SaSint {
4873    if omp_block_size <= 0 {
4874        return d;
4875    }
4876
4877    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4878    let mut i = size;
4879    while i > 0 {
4880        i -= 1;
4881
4882        let v = usize::try_from(cache[i].symbol).expect("cache symbol must be non-negative");
4883        let p = cache[i].index;
4884        d += SaSint::from(p < 0);
4885        buckets[v] -= 1;
4886        let target = buckets[v];
4887        cache[i].symbol = target;
4888        cache[i].index = (p - 1) | (((buckets[2 + v] != d) as SaSint) << (SAINT_BIT - 1));
4889        buckets[2 + v] = d;
4890
4891        if target >= omp_block_start as SaSint
4892            && target < (omp_block_start + omp_block_size) as SaSint
4893        {
4894            let s = usize::try_from(target - omp_block_start as SaSint)
4895                .expect("cache slot must be non-negative");
4896            let q = cache[i].index & SAINT_MAX;
4897            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
4898            cache[s].index = cache[i].index;
4899            cache[s].symbol = buckets_index4(
4900                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
4901                usize::from(t[q_usize - 2] > t[q_usize - 1]),
4902            ) as SaSint;
4903        }
4904    }
4905
4906    d
4907}
4908
4909/// Internal helper: partial sorting scan right to left 32s 4k block sort.
4910#[doc(hidden)]
4911pub fn partial_sorting_scan_right_to_left_32s_4k_block_sort(
4912    t: &[SaSint],
4913    k: SaSint,
4914    buckets: &mut [SaSint],
4915    mut d: SaSint,
4916    cache: &mut [ThreadCache],
4917    omp_block_start: FastSint,
4918    omp_block_size: FastSint,
4919) -> SaSint {
4920    if omp_block_size <= 0 {
4921        return d;
4922    }
4923
4924    let k_usize = usize::try_from(k).expect("k must be non-negative");
4925    let (distinct_names, tail) = buckets.split_at_mut(2 * k_usize);
4926    let induction_bucket = &mut tail[k_usize..2 * k_usize];
4927
4928    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4929    let mut i = size;
4930    while i > 0 {
4931        i -= 1;
4932
4933        let v = cache[i].symbol;
4934        if v >= 0 {
4935            let p = cache[i].index;
4936            d += p >> (SUFFIX_GROUP_BIT - 1);
4937            let bucket_index = usize::try_from(v >> 1).expect("bucket symbol must be non-negative");
4938            induction_bucket[bucket_index] -= 1;
4939            let target = induction_bucket[bucket_index];
4940            cache[i].symbol = target;
4941            cache[i].index = (p - 1)
4942                | ((v & 1) << (SAINT_BIT - 1))
4943                | (((distinct_names
4944                    [usize::try_from(v).expect("bucket symbol must be non-negative")]
4945                    != d) as SaSint)
4946                    << (SUFFIX_GROUP_BIT - 1));
4947            distinct_names[usize::try_from(v).expect("bucket symbol must be non-negative")] = d;
4948
4949            if target >= omp_block_start as SaSint
4950                && target < (omp_block_start + omp_block_size) as SaSint
4951            {
4952                let ni = usize::try_from(target - omp_block_start as SaSint)
4953                    .expect("cache slot must be non-negative");
4954                let mut np = cache[i].index;
4955                if np > 0 {
4956                    cache[i].index = 0;
4957                    cache[ni].index = np;
4958                    np &= !SUFFIX_GROUP_MARKER;
4959                    let np_usize = usize::try_from(np).expect("suffix index must be non-negative");
4960                    cache[ni].symbol = buckets_index2(
4961                        usize::try_from(t[np_usize - 1])
4962                            .expect("bucket symbol must be non-negative"),
4963                        usize::from(t[np_usize - 2] > t[np_usize - 1]),
4964                    ) as SaSint;
4965                }
4966            }
4967        }
4968    }
4969
4970    d
4971}
4972
4973/// Internal helper: partial sorting scan right to left 32s 1k block sort.
4974#[doc(hidden)]
4975pub fn partial_sorting_scan_right_to_left_32s_1k_block_sort(
4976    t: &[SaSint],
4977    induction_bucket: &mut [SaSint],
4978    cache: &mut [ThreadCache],
4979    omp_block_start: FastSint,
4980    omp_block_size: FastSint,
4981) {
4982    if omp_block_size <= 0 {
4983        return;
4984    }
4985    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
4986    let mut offset = size;
4987
4988    while offset > 0 {
4989        offset -= 1;
4990        let v = cache[offset].symbol;
4991        if v >= 0 {
4992            let bucket_index = v as usize;
4993            induction_bucket[bucket_index] -= 1;
4994            let target = induction_bucket[bucket_index];
4995            cache[offset].symbol = target;
4996            let block_end = omp_block_start as SaSint + omp_block_size as SaSint;
4997            if target >= omp_block_start as SaSint && target < block_end {
4998                let ni = usize::try_from(target - omp_block_start as SaSint)
4999                    .expect("cache slot must be non-negative");
5000                let np = cache[offset].index;
5001                if np > 0 {
5002                    cache[offset].index = 0;
5003                    cache[ni].index = (np - 1)
5004                        | ((usize::from(t[np as usize - 2] > t[np as usize - 1]) as SaSint)
5005                            << (SAINT_BIT - 1));
5006                    cache[ni].symbol = t[np as usize - 1];
5007                }
5008            }
5009        }
5010    }
5011}
5012
5013/// Internal helper: partial sorting scan right to left 32s 6k block (OpenMP variant).
5014#[doc(hidden)]
5015pub fn partial_sorting_scan_right_to_left_32s_6k_block_omp(
5016    t: &[SaSint],
5017    sa: &mut [SaSint],
5018    buckets: &mut [SaSint],
5019    mut d: SaSint,
5020    cache: &mut [ThreadCache],
5021    block_start: FastSint,
5022    block_size: FastSint,
5023    threads: SaSint,
5024) -> SaSint {
5025    if block_size <= 0 {
5026        return d;
5027    }
5028    if threads == 1 || block_size < 16_384 {
5029        return partial_sorting_scan_right_to_left_32s_6k(
5030            t,
5031            sa,
5032            buckets,
5033            d,
5034            block_start,
5035            block_size,
5036        );
5037    }
5038
5039    let threads_usize = usize::try_from(threads)
5040        .expect("threads must be non-negative")
5041        .max(1);
5042    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5043    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5044    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5045
5046    {
5047        let sa_ro: &[SaSint] = sa;
5048        let t_ro: &[SaSint] = t;
5049        let cache_ptr = SyncMutPtr::new(cache);
5050        run_rayon_with_threads(omp_num_threads, || {
5051            (0..omp_num_threads)
5052                .into_par_iter()
5053                .for_each(|omp_thread_num| {
5054                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5055                        omp_block_stride
5056                    } else {
5057                        block_size_usize - omp_thread_num * omp_block_stride
5058                    };
5059                    let omp_block_start = usize::try_from(block_start)
5060                        .expect("block_start must be non-negative")
5061                        + omp_thread_num * omp_block_stride;
5062                    if omp_block_size > 0 {
5063                        let cache = unsafe { cache_ptr.as_slice() };
5064                        partial_sorting_scan_right_to_left_32s_6k_block_gather(
5065                            t_ro,
5066                            sa_ro,
5067                            &mut cache[omp_thread_num * omp_block_stride
5068                                ..omp_thread_num * omp_block_stride + omp_block_size],
5069                            omp_block_start as FastSint,
5070                            omp_block_size as FastSint,
5071                        );
5072                    }
5073                });
5074        });
5075    }
5076
5077    d = partial_sorting_scan_right_to_left_32s_6k_block_sort(
5078        t,
5079        buckets,
5080        d,
5081        &mut cache[..block_size_usize],
5082        block_start,
5083        block_size,
5084    );
5085
5086    {
5087        let sa_ptr = SyncMutPtr::new(sa);
5088        let cache_ro: &[ThreadCache] = cache;
5089        run_rayon_with_threads(omp_num_threads, || {
5090            (0..omp_num_threads)
5091                .into_par_iter()
5092                .for_each(|omp_thread_num| {
5093                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5094                        omp_block_stride
5095                    } else {
5096                        block_size_usize - omp_thread_num * omp_block_stride
5097                    };
5098                    let cache_start = omp_thread_num * omp_block_stride;
5099                    if omp_block_size > 0 {
5100                        let sa = unsafe { sa_ptr.as_slice() };
5101                        place_cached_suffixes(
5102                            sa,
5103                            &cache_ro[cache_start..],
5104                            0,
5105                            omp_block_size as FastSint,
5106                        );
5107                    }
5108                });
5109        });
5110    }
5111
5112    d
5113}
5114
5115/// Internal helper: partial sorting scan right to left 32s 4k block (OpenMP variant).
5116#[doc(hidden)]
5117pub fn partial_sorting_scan_right_to_left_32s_4k_block_omp(
5118    t: &[SaSint],
5119    sa: &mut [SaSint],
5120    k: SaSint,
5121    buckets: &mut [SaSint],
5122    mut d: SaSint,
5123    cache: &mut [ThreadCache],
5124    block_start: FastSint,
5125    block_size: FastSint,
5126    threads: SaSint,
5127) -> SaSint {
5128    if block_size <= 0 {
5129        return d;
5130    }
5131    if threads == 1 || block_size < 16_384 {
5132        return partial_sorting_scan_right_to_left_32s_4k(
5133            t,
5134            sa,
5135            k,
5136            buckets,
5137            d,
5138            block_start,
5139            block_size,
5140        );
5141    }
5142
5143    let threads_usize = usize::try_from(threads)
5144        .expect("threads must be non-negative")
5145        .max(1);
5146    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5147    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5148    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5149
5150    {
5151        let sa_ptr = SyncMutPtr::new(sa);
5152        let t_ro: &[SaSint] = t;
5153        let cache_ptr = SyncMutPtr::new(cache);
5154        run_rayon_with_threads(omp_num_threads, || {
5155            (0..omp_num_threads)
5156                .into_par_iter()
5157                .for_each(|omp_thread_num| {
5158                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5159                        omp_block_stride
5160                    } else {
5161                        block_size_usize - omp_thread_num * omp_block_stride
5162                    };
5163                    let omp_block_start = usize::try_from(block_start)
5164                        .expect("block_start must be non-negative")
5165                        + omp_thread_num * omp_block_stride;
5166                    if omp_block_size > 0 {
5167                        let cache = unsafe { cache_ptr.as_slice() };
5168                        let sa = unsafe { sa_ptr.as_slice() };
5169                        partial_sorting_scan_right_to_left_32s_4k_block_gather(
5170                            t_ro,
5171                            sa,
5172                            &mut cache[omp_thread_num * omp_block_stride
5173                                ..omp_thread_num * omp_block_stride + omp_block_size],
5174                            omp_block_start as FastSint,
5175                            omp_block_size as FastSint,
5176                        );
5177                    }
5178                });
5179        });
5180    }
5181
5182    d = partial_sorting_scan_right_to_left_32s_4k_block_sort(
5183        t,
5184        k,
5185        buckets,
5186        d,
5187        &mut cache[..block_size_usize],
5188        block_start,
5189        block_size,
5190    );
5191
5192    {
5193        let sa_ptr = SyncMutPtr::new(sa);
5194        let cache_ptr = SyncMutPtr::new(cache);
5195        run_rayon_with_threads(omp_num_threads, || {
5196            (0..omp_num_threads)
5197                .into_par_iter()
5198                .for_each(|omp_thread_num| {
5199                    let omp_block_start = omp_thread_num * omp_block_stride;
5200                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5201                        omp_block_stride
5202                    } else {
5203                        block_size_usize - omp_block_start
5204                    };
5205                    if omp_block_size > 0 {
5206                        let sa = unsafe { sa_ptr.as_slice() };
5207                        let cache = unsafe { cache_ptr.as_slice() };
5208                        compact_and_place_cached_suffixes(
5209                            sa,
5210                            &mut cache[omp_block_start..],
5211                            0,
5212                            omp_block_size as FastSint,
5213                        );
5214                    }
5215                });
5216        });
5217    }
5218
5219    d
5220}
5221
5222/// Internal helper: partial sorting scan right to left 32s 1k block (OpenMP variant).
5223#[doc(hidden)]
5224pub fn partial_sorting_scan_right_to_left_32s_1k_block_omp(
5225    t: &[SaSint],
5226    sa: &mut [SaSint],
5227    buckets: &mut [SaSint],
5228    cache: &mut [ThreadCache],
5229    block_start: FastSint,
5230    block_size: FastSint,
5231    threads: SaSint,
5232) {
5233    if block_size <= 0 {
5234        return;
5235    }
5236    if threads == 1 || block_size < 16_384 {
5237        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, block_start, block_size);
5238        return;
5239    }
5240
5241    let threads_usize = usize::try_from(threads)
5242        .expect("threads must be non-negative")
5243        .max(1);
5244    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5245    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5246    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5247    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5248
5249    {
5250        let sa_ptr = SyncMutPtr::new(sa);
5251        let t_ro: &[SaSint] = t;
5252        let cache_ptr = SyncMutPtr::new(cache);
5253        run_rayon_with_threads(omp_num_threads, || {
5254            (0..omp_num_threads)
5255                .into_par_iter()
5256                .for_each(|omp_thread_num| {
5257                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5258                        omp_block_stride
5259                    } else {
5260                        block_size_usize - omp_thread_num * omp_block_stride
5261                    };
5262                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5263                    if omp_block_size > 0 {
5264                        let cache = unsafe { cache_ptr.as_slice() };
5265                        let sa = unsafe { sa_ptr.as_slice() };
5266                        partial_sorting_scan_right_to_left_32s_1k_block_gather(
5267                            t_ro,
5268                            sa,
5269                            &mut cache[omp_thread_num * omp_block_stride
5270                                ..omp_thread_num * omp_block_stride + omp_block_size],
5271                            omp_block_start as FastSint,
5272                            omp_block_size as FastSint,
5273                        );
5274                    }
5275                });
5276        });
5277    }
5278
5279    let cache = &mut cache[..block_size_usize];
5280    partial_sorting_scan_right_to_left_32s_1k_block_sort(
5281        t,
5282        buckets,
5283        cache,
5284        block_start,
5285        block_size,
5286    );
5287    {
5288        let sa_ptr = SyncMutPtr::new(sa);
5289        let cache_ptr = SyncMutPtr::new(cache);
5290        run_rayon_with_threads(omp_num_threads, || {
5291            (0..omp_num_threads)
5292                .into_par_iter()
5293                .for_each(|omp_thread_num| {
5294                    let omp_block_start = omp_thread_num * omp_block_stride;
5295                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5296                        omp_block_stride
5297                    } else {
5298                        block_size_usize - omp_block_start
5299                    };
5300                    if omp_block_size > 0 {
5301                        let sa = unsafe { sa_ptr.as_slice() };
5302                        let cache = unsafe { cache_ptr.as_slice() };
5303                        compact_and_place_cached_suffixes(
5304                            sa,
5305                            &mut cache[omp_block_start..],
5306                            0,
5307                            omp_block_size as FastSint,
5308                        );
5309                    }
5310                });
5311        });
5312    }
5313}
5314
5315/// Internal helper: partial sorting scan left to right 32s 6k block gather.
5316#[doc(hidden)]
5317pub fn partial_sorting_scan_left_to_right_32s_6k_block_gather(
5318    t: &[SaSint],
5319    sa: &mut [SaSint],
5320    cache: &mut [ThreadCache],
5321    omp_block_start: FastSint,
5322    omp_block_size: FastSint,
5323) {
5324    if omp_block_size <= 0 {
5325        return;
5326    }
5327
5328    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5329    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5330    for offset in 0..size {
5331        let i = start + offset;
5332        let p = sa[i];
5333        cache[offset].index = p;
5334        let q = p & SAINT_MAX;
5335        cache[offset].symbol = if q != 0 {
5336            buckets_index4(
5337                usize::try_from(t[q as usize - 1]).expect("bucket symbol must be non-negative"),
5338                usize::from(t[q as usize - 2] >= t[q as usize - 1]),
5339            ) as SaSint
5340        } else {
5341            0
5342        };
5343    }
5344}
5345
5346/// Internal helper: partial sorting scan left to right 32s 4k block gather.
5347#[doc(hidden)]
5348pub fn partial_sorting_scan_left_to_right_32s_4k_block_gather(
5349    t: &[SaSint],
5350    sa: &mut [SaSint],
5351    cache: &mut [ThreadCache],
5352    omp_block_start: FastSint,
5353    omp_block_size: FastSint,
5354) {
5355    if omp_block_size <= 0 {
5356        return;
5357    }
5358
5359    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5360    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5361    for offset in 0..size {
5362        let i = start + offset;
5363        let mut symbol = SAINT_MIN;
5364        let mut p = sa[i];
5365        if p > 0 {
5366            cache[offset].index = p;
5367            p &= !SUFFIX_GROUP_MARKER;
5368            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
5369            symbol = buckets_index2(
5370                usize::try_from(t[p_usize - 1]).expect("bucket symbol must be non-negative"),
5371                usize::from(t[p_usize - 2] < t[p_usize - 1]),
5372            ) as SaSint;
5373            p = 0;
5374        }
5375        cache[offset].symbol = symbol;
5376        sa[i] = p & SAINT_MAX;
5377    }
5378}
5379
5380/// Internal helper: partial sorting scan left to right 32s 1k block gather.
5381#[doc(hidden)]
5382pub fn partial_sorting_scan_left_to_right_32s_1k_block_gather(
5383    t: &[SaSint],
5384    sa: &mut [SaSint],
5385    cache: &mut [ThreadCache],
5386    omp_block_start: FastSint,
5387    omp_block_size: FastSint,
5388) {
5389    if omp_block_size <= 0 {
5390        return;
5391    }
5392    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5393    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5394    for offset in 0..size {
5395        let i = start + offset;
5396        let mut symbol = SAINT_MIN;
5397        let mut p = sa[i];
5398        if p > 0 {
5399            cache[offset].index = (p - 1)
5400                | ((usize::from(t[p as usize - 2] < t[p as usize - 1]) as SaSint)
5401                    << (SAINT_BIT - 1));
5402            symbol = t[p as usize - 1];
5403            p = 0;
5404        }
5405        cache[offset].symbol = symbol;
5406        sa[i] = p & SAINT_MAX;
5407    }
5408}
5409
5410/// Internal helper: partial sorting scan left to right 32s 6k block sort.
5411#[doc(hidden)]
5412pub fn partial_sorting_scan_left_to_right_32s_6k_block_sort(
5413    t: &[SaSint],
5414    buckets: &mut [SaSint],
5415    mut d: SaSint,
5416    cache: &mut [ThreadCache],
5417    omp_block_start: FastSint,
5418    omp_block_size: FastSint,
5419) -> SaSint {
5420    if omp_block_size <= 0 {
5421        return d;
5422    }
5423
5424    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5425    let block_end =
5426        start + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5427
5428    let mut i = start;
5429    let mut j = block_end.saturating_sub(65);
5430    while i < j {
5431        let cache_i0 = i - start;
5432        let cache_i1 = cache_i0 + 1;
5433
5434        let v0 =
5435            usize::try_from(cache[cache_i0].symbol).expect("cache symbol must be non-negative");
5436        let p0 = cache[cache_i0].index;
5437        d += SaSint::from(p0 < 0);
5438        cache[cache_i0].symbol = buckets[v0];
5439        buckets[v0] += 1;
5440        cache[cache_i0].index =
5441            (p0 - 1) | ((SaSint::from(buckets[2 + v0] != d)) << (SAINT_BIT - 1));
5442        buckets[2 + v0] = d;
5443        if cache[cache_i0].symbol >= omp_block_start as SaSint
5444            && cache[cache_i0].symbol < block_end as SaSint
5445        {
5446            let s = usize::try_from(cache[cache_i0].symbol - omp_block_start as SaSint)
5447                .expect("cache slot must be non-negative");
5448            let q = cache[cache_i0].index & SAINT_MAX;
5449            cache[s].index = cache[cache_i0].index;
5450            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5451            cache[s].symbol = buckets_index4(
5452                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5453                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5454            ) as SaSint;
5455        }
5456
5457        let v1 =
5458            usize::try_from(cache[cache_i1].symbol).expect("cache symbol must be non-negative");
5459        let p1 = cache[cache_i1].index;
5460        d += SaSint::from(p1 < 0);
5461        cache[cache_i1].symbol = buckets[v1];
5462        buckets[v1] += 1;
5463        cache[cache_i1].index =
5464            (p1 - 1) | ((SaSint::from(buckets[2 + v1] != d)) << (SAINT_BIT - 1));
5465        buckets[2 + v1] = d;
5466        if cache[cache_i1].symbol >= omp_block_start as SaSint
5467            && cache[cache_i1].symbol < block_end as SaSint
5468        {
5469            let s = usize::try_from(cache[cache_i1].symbol - omp_block_start as SaSint)
5470                .expect("cache slot must be non-negative");
5471            let q = cache[cache_i1].index & SAINT_MAX;
5472            cache[s].index = cache[cache_i1].index;
5473            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5474            cache[s].symbol = buckets_index4(
5475                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5476                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5477            ) as SaSint;
5478        }
5479
5480        i += 2;
5481    }
5482
5483    j += 65;
5484    while i < j {
5485        let cache_i = i - start;
5486        let v = usize::try_from(cache[cache_i].symbol).expect("cache symbol must be non-negative");
5487        let p = cache[cache_i].index;
5488        d += SaSint::from(p < 0);
5489        cache[cache_i].symbol = buckets[v];
5490        buckets[v] += 1;
5491        cache[cache_i].index = (p - 1) | ((SaSint::from(buckets[2 + v] != d)) << (SAINT_BIT - 1));
5492        buckets[2 + v] = d;
5493        if cache[cache_i].symbol >= omp_block_start as SaSint
5494            && cache[cache_i].symbol < block_end as SaSint
5495        {
5496            let s = usize::try_from(cache[cache_i].symbol - omp_block_start as SaSint)
5497                .expect("cache slot must be non-negative");
5498            let q = cache[cache_i].index & SAINT_MAX;
5499            cache[s].index = cache[cache_i].index;
5500            let q_usize = usize::try_from(q).expect("suffix index must be non-negative");
5501            cache[s].symbol = buckets_index4(
5502                usize::try_from(t[q_usize - 1]).expect("bucket symbol must be non-negative"),
5503                usize::from(t[q_usize - 2] >= t[q_usize - 1]),
5504            ) as SaSint;
5505        }
5506        i += 1;
5507    }
5508
5509    d
5510}
5511
5512/// Internal helper: partial sorting scan left to right 32s 4k block sort.
5513#[doc(hidden)]
5514pub fn partial_sorting_scan_left_to_right_32s_4k_block_sort(
5515    t: &[SaSint],
5516    k: SaSint,
5517    buckets: &mut [SaSint],
5518    mut d: SaSint,
5519    cache: &mut [ThreadCache],
5520    omp_block_start: FastSint,
5521    omp_block_size: FastSint,
5522) -> SaSint {
5523    if omp_block_size <= 0 {
5524        return d;
5525    }
5526
5527    let k_usize = usize::try_from(k).expect("k must be non-negative");
5528    let (distinct_names, tail) = buckets.split_at_mut(2 * k_usize);
5529    let induction_bucket = &mut tail[..k_usize];
5530
5531    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5532    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5533    let block_end = start + size;
5534
5535    for offset in 0..size {
5536        let v = cache[offset].symbol;
5537        if v >= 0 {
5538            let p = cache[offset].index;
5539            d += p >> (SUFFIX_GROUP_BIT - 1);
5540
5541            let bucket_index = usize::try_from(v >> 1).expect("bucket index must be non-negative");
5542            let v_usize = usize::try_from(v).expect("cache symbol must be non-negative");
5543            let target = induction_bucket[bucket_index];
5544            induction_bucket[bucket_index] += 1;
5545
5546            cache[offset].symbol = target;
5547            cache[offset].index = (p - 1)
5548                | ((v & 1) << (SAINT_BIT - 1))
5549                | (((distinct_names[v_usize] != d) as SaSint) << (SUFFIX_GROUP_BIT - 1));
5550            distinct_names[v_usize] = d;
5551
5552            if target >= omp_block_start as SaSint && target < block_end as SaSint {
5553                let ni = usize::try_from(target - omp_block_start as SaSint)
5554                    .expect("cache slot must be non-negative");
5555                let mut np = cache[offset].index;
5556                if np > 0 {
5557                    cache[ni].index = np;
5558                    np &= !SUFFIX_GROUP_MARKER;
5559                    let np_usize = usize::try_from(np).expect("suffix index must be non-negative");
5560                    cache[ni].symbol = buckets_index2(
5561                        usize::try_from(t[np_usize - 1])
5562                            .expect("bucket symbol must be non-negative"),
5563                        usize::from(t[np_usize - 2] < t[np_usize - 1]),
5564                    ) as SaSint;
5565                    np = 0;
5566                }
5567                cache[offset].index = np & SAINT_MAX;
5568            }
5569        }
5570    }
5571
5572    d
5573}
5574
5575/// Internal helper: partial sorting scan left to right 32s 1k block sort.
5576#[doc(hidden)]
5577pub fn partial_sorting_scan_left_to_right_32s_1k_block_sort(
5578    t: &[SaSint],
5579    induction_bucket: &mut [SaSint],
5580    cache: &mut [ThreadCache],
5581    omp_block_start: FastSint,
5582    omp_block_size: FastSint,
5583) {
5584    if omp_block_size <= 0 {
5585        return;
5586    }
5587    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
5588    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
5589    let block_end = start + size;
5590
5591    for offset in 0..size {
5592        let v = cache[offset].symbol;
5593        if v >= 0 {
5594            let v_usize = v as usize;
5595            let target = induction_bucket[v_usize];
5596            cache[offset].symbol = target;
5597            induction_bucket[v_usize] += 1;
5598            if target >= omp_block_start as SaSint && target < block_end as SaSint {
5599                let ni = usize::try_from(target - omp_block_start as SaSint)
5600                    .expect("cache slot must be non-negative");
5601                let mut np = cache[offset].index;
5602                if np > 0 {
5603                    cache[ni].index = (np - 1)
5604                        | ((usize::from(t[np as usize - 2] < t[np as usize - 1]) as SaSint)
5605                            << (SAINT_BIT - 1));
5606                    cache[ni].symbol = t[np as usize - 1];
5607                    np = 0;
5608                }
5609                cache[offset].index = np & SAINT_MAX;
5610            }
5611        }
5612    }
5613}
5614
5615/// Internal helper: partial sorting scan left to right 32s 6k block (OpenMP variant).
5616#[doc(hidden)]
5617pub fn partial_sorting_scan_left_to_right_32s_6k_block_omp(
5618    t: &[SaSint],
5619    sa: &mut [SaSint],
5620    buckets: &mut [SaSint],
5621    d: SaSint,
5622    cache: &mut [ThreadCache],
5623    block_start: FastSint,
5624    block_size: FastSint,
5625    threads: SaSint,
5626) -> SaSint {
5627    if block_size <= 0 {
5628        return d;
5629    }
5630    if threads == 1 || block_size < 16_384 {
5631        return partial_sorting_scan_left_to_right_32s_6k(
5632            t,
5633            sa,
5634            buckets,
5635            d,
5636            block_start,
5637            block_size,
5638        );
5639    }
5640
5641    let threads_usize = usize::try_from(threads)
5642        .expect("threads must be non-negative")
5643        .max(1);
5644    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5645    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5646    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5647    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5648
5649    {
5650        let sa_ptr = SyncMutPtr::new(sa);
5651        let t_ro: &[SaSint] = t;
5652        let cache_ptr = SyncMutPtr::new(cache);
5653        run_rayon_with_threads(omp_num_threads, || {
5654            (0..omp_num_threads)
5655                .into_par_iter()
5656                .for_each(|omp_thread_num| {
5657                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5658                        omp_block_stride
5659                    } else {
5660                        block_size_usize - omp_thread_num * omp_block_stride
5661                    };
5662                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5663                    if omp_block_size > 0 {
5664                        let cache = unsafe { cache_ptr.as_slice() };
5665                        let sa = unsafe { sa_ptr.as_slice() };
5666                        partial_sorting_scan_left_to_right_32s_6k_block_gather(
5667                            t_ro,
5668                            sa,
5669                            &mut cache[omp_thread_num * omp_block_stride
5670                                ..omp_thread_num * omp_block_stride + omp_block_size],
5671                            omp_block_start as FastSint,
5672                            omp_block_size as FastSint,
5673                        );
5674                    }
5675                });
5676        });
5677    }
5678
5679    let d = partial_sorting_scan_left_to_right_32s_6k_block_sort(
5680        t,
5681        buckets,
5682        d,
5683        &mut cache[..block_size_usize],
5684        block_start,
5685        block_size,
5686    );
5687
5688    {
5689        let sa_ptr = SyncMutPtr::new(sa);
5690        let cache_ro: &[ThreadCache] = cache;
5691        run_rayon_with_threads(omp_num_threads, || {
5692            (0..omp_num_threads)
5693                .into_par_iter()
5694                .for_each(|omp_thread_num| {
5695                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5696                        omp_block_stride
5697                    } else {
5698                        block_size_usize - omp_thread_num * omp_block_stride
5699                    };
5700                    if omp_block_size > 0 {
5701                        let sa = unsafe { sa_ptr.as_slice() };
5702                        place_cached_suffixes(
5703                            sa,
5704                            &cache_ro[omp_thread_num * omp_block_stride..],
5705                            0,
5706                            omp_block_size as FastSint,
5707                        );
5708                    }
5709                });
5710        });
5711    }
5712    d
5713}
5714
5715/// Internal helper: partial sorting scan left to right 32s 4k block (OpenMP variant).
5716#[doc(hidden)]
5717pub fn partial_sorting_scan_left_to_right_32s_4k_block_omp(
5718    t: &[SaSint],
5719    sa: &mut [SaSint],
5720    k: SaSint,
5721    buckets: &mut [SaSint],
5722    d: SaSint,
5723    cache: &mut [ThreadCache],
5724    block_start: FastSint,
5725    block_size: FastSint,
5726    threads: SaSint,
5727) -> SaSint {
5728    if block_size <= 0 {
5729        return d;
5730    }
5731    if threads == 1 || block_size < 16_384 {
5732        return partial_sorting_scan_left_to_right_32s_4k(
5733            t,
5734            sa,
5735            k,
5736            buckets,
5737            d,
5738            block_start,
5739            block_size,
5740        );
5741    }
5742
5743    let threads_usize = usize::try_from(threads)
5744        .expect("threads must be non-negative")
5745        .max(1);
5746    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5747    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5748    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5749    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5750
5751    {
5752        let sa_ptr = SyncMutPtr::new(sa);
5753        let t_ro: &[SaSint] = t;
5754        let cache_ptr = SyncMutPtr::new(cache);
5755        run_rayon_with_threads(omp_num_threads, || {
5756            (0..omp_num_threads)
5757                .into_par_iter()
5758                .for_each(|omp_thread_num| {
5759                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5760                        omp_block_stride
5761                    } else {
5762                        block_size_usize - omp_thread_num * omp_block_stride
5763                    };
5764                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5765                    if omp_block_size > 0 {
5766                        let cache = unsafe { cache_ptr.as_slice() };
5767                        let sa = unsafe { sa_ptr.as_slice() };
5768                        partial_sorting_scan_left_to_right_32s_4k_block_gather(
5769                            t_ro,
5770                            sa,
5771                            &mut cache[omp_thread_num * omp_block_stride
5772                                ..omp_thread_num * omp_block_stride + omp_block_size],
5773                            omp_block_start as FastSint,
5774                            omp_block_size as FastSint,
5775                        );
5776                    }
5777                });
5778        });
5779    }
5780
5781    let cache = &mut cache[..block_size_usize];
5782    let d = partial_sorting_scan_left_to_right_32s_4k_block_sort(
5783        t,
5784        k,
5785        buckets,
5786        d,
5787        cache,
5788        block_start,
5789        block_size,
5790    );
5791
5792    {
5793        let sa_ptr = SyncMutPtr::new(sa);
5794        let cache_ptr = SyncMutPtr::new(cache);
5795        run_rayon_with_threads(omp_num_threads, || {
5796            (0..omp_num_threads)
5797                .into_par_iter()
5798                .for_each(|omp_thread_num| {
5799                    let omp_block_start = omp_thread_num * omp_block_stride;
5800                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5801                        omp_block_stride
5802                    } else {
5803                        block_size_usize - omp_block_start
5804                    };
5805                    if omp_block_size > 0 {
5806                        let sa = unsafe { sa_ptr.as_slice() };
5807                        let cache = unsafe { cache_ptr.as_slice() };
5808                        compact_and_place_cached_suffixes(
5809                            sa,
5810                            &mut cache[omp_block_start..],
5811                            0,
5812                            omp_block_size as FastSint,
5813                        );
5814                    }
5815                });
5816        });
5817    }
5818
5819    d
5820}
5821
5822/// Internal helper: partial sorting scan left to right 32s 1k block (OpenMP variant).
5823#[doc(hidden)]
5824pub fn partial_sorting_scan_left_to_right_32s_1k_block_omp(
5825    t: &[SaSint],
5826    sa: &mut [SaSint],
5827    buckets: &mut [SaSint],
5828    cache: &mut [ThreadCache],
5829    block_start: FastSint,
5830    block_size: FastSint,
5831    threads: SaSint,
5832) {
5833    if block_size <= 0 {
5834        return;
5835    }
5836    if threads == 1 || block_size < 16_384 {
5837        partial_sorting_scan_left_to_right_32s_1k(t, sa, buckets, block_start, block_size);
5838        return;
5839    }
5840
5841    let threads_usize = usize::try_from(threads)
5842        .expect("threads must be non-negative")
5843        .max(1);
5844    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
5845    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
5846    let omp_num_threads = threads_usize.min(block_size_usize.max(1));
5847    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
5848
5849    {
5850        let sa_ptr = SyncMutPtr::new(sa);
5851        let t_ro: &[SaSint] = t;
5852        let cache_ptr = SyncMutPtr::new(cache);
5853        run_rayon_with_threads(omp_num_threads, || {
5854            (0..omp_num_threads)
5855                .into_par_iter()
5856                .for_each(|omp_thread_num| {
5857                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5858                        omp_block_stride
5859                    } else {
5860                        block_size_usize - omp_thread_num * omp_block_stride
5861                    };
5862                    let omp_block_start = block_start_usize + omp_thread_num * omp_block_stride;
5863                    if omp_block_size > 0 {
5864                        let cache = unsafe { cache_ptr.as_slice() };
5865                        let sa = unsafe { sa_ptr.as_slice() };
5866                        partial_sorting_scan_left_to_right_32s_1k_block_gather(
5867                            t_ro,
5868                            sa,
5869                            &mut cache[omp_thread_num * omp_block_stride
5870                                ..omp_thread_num * omp_block_stride + omp_block_size],
5871                            omp_block_start as FastSint,
5872                            omp_block_size as FastSint,
5873                        );
5874                    }
5875                });
5876        });
5877    }
5878
5879    let cache = &mut cache[..block_size_usize];
5880    partial_sorting_scan_left_to_right_32s_1k_block_sort(
5881        t,
5882        buckets,
5883        cache,
5884        block_start,
5885        block_size,
5886    );
5887    {
5888        let sa_ptr = SyncMutPtr::new(sa);
5889        let cache_ptr = SyncMutPtr::new(cache);
5890        run_rayon_with_threads(omp_num_threads, || {
5891            (0..omp_num_threads)
5892                .into_par_iter()
5893                .for_each(|omp_thread_num| {
5894                    let omp_block_start = omp_thread_num * omp_block_stride;
5895                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
5896                        omp_block_stride
5897                    } else {
5898                        block_size_usize - omp_block_start
5899                    };
5900                    if omp_block_size > 0 {
5901                        let sa = unsafe { sa_ptr.as_slice() };
5902                        let cache = unsafe { cache_ptr.as_slice() };
5903                        compact_and_place_cached_suffixes(
5904                            sa,
5905                            &mut cache[omp_block_start..],
5906                            0,
5907                            omp_block_size as FastSint,
5908                        );
5909                    }
5910                });
5911        });
5912    }
5913}
5914
5915/// Internal helper: partial sorting scan right to left 32s 6k (OpenMP variant).
5916#[doc(hidden)]
5917pub fn partial_sorting_scan_right_to_left_32s_6k_omp(
5918    t: &[SaSint],
5919    sa: &mut [SaSint],
5920    n: SaSint,
5921    buckets: &mut [SaSint],
5922    first_lms_suffix: SaSint,
5923    left_suffixes_count: SaSint,
5924    mut d: SaSint,
5925    threads: SaSint,
5926    thread_state: &mut [ThreadState],
5927) -> SaSint {
5928    let scan_start = left_suffixes_count as FastSint + 1;
5929    let scan_end = n as FastSint - first_lms_suffix as FastSint;
5930    if threads == 1 || (scan_end - scan_start) < 65_536 {
5931        return partial_sorting_scan_right_to_left_32s_6k(
5932            t,
5933            sa,
5934            buckets,
5935            d,
5936            scan_start,
5937            scan_end - scan_start,
5938        );
5939    }
5940    if thread_state.is_empty() {
5941        return partial_sorting_scan_right_to_left_32s_6k(
5942            t,
5943            sa,
5944            buckets,
5945            d,
5946            scan_start,
5947            scan_end - scan_start,
5948        );
5949    }
5950
5951    let threads_usize = usize::try_from(threads)
5952        .expect("threads must be non-negative")
5953        .max(1);
5954    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
5955    let mut block_start = scan_end - 1;
5956    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
5957        .expect("block span must fit FastSint");
5958    while block_start >= scan_start {
5959        let mut block_end = block_start - block_span;
5960        if block_end < scan_start {
5961            block_end = scan_start - 1;
5962        }
5963
5964        d = partial_sorting_scan_right_to_left_32s_6k_block_omp(
5965            t,
5966            sa,
5967            buckets,
5968            d,
5969            &mut cache,
5970            block_end + 1,
5971            block_start - block_end,
5972            threads,
5973        );
5974
5975        if block_end < scan_start {
5976            break;
5977        }
5978        block_start = block_end;
5979    }
5980
5981    d
5982}
5983
5984/// Internal helper: partial sorting scan right to left 32s 4k (OpenMP variant).
5985#[doc(hidden)]
5986pub fn partial_sorting_scan_right_to_left_32s_4k_omp(
5987    t: &[SaSint],
5988    sa: &mut [SaSint],
5989    n: SaSint,
5990    k: SaSint,
5991    buckets: &mut [SaSint],
5992    mut d: SaSint,
5993    threads: SaSint,
5994    thread_state: &mut [ThreadState],
5995) -> SaSint {
5996    if threads == 1 || n < 65_536 {
5997        return partial_sorting_scan_right_to_left_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
5998    }
5999    if thread_state.is_empty() {
6000        return partial_sorting_scan_right_to_left_32s_4k(t, sa, k, buckets, d, 0, n as FastSint);
6001    }
6002    let threads_usize = usize::try_from(threads)
6003        .expect("threads must be non-negative")
6004        .max(1);
6005    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
6006    let mut block_start = FastSint::try_from(n).expect("n must fit FastSint") - 1;
6007    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
6008        .expect("block span must fit FastSint");
6009    while block_start >= 0 {
6010        let mut block_end = block_start - block_span;
6011        if block_end < 0 {
6012            block_end = -1;
6013        }
6014
6015        d = partial_sorting_scan_right_to_left_32s_4k_block_omp(
6016            t,
6017            sa,
6018            k,
6019            buckets,
6020            d,
6021            &mut cache,
6022            block_end + 1,
6023            block_start - block_end,
6024            threads,
6025        );
6026
6027        if block_end < 0 {
6028            break;
6029        }
6030        block_start = block_end;
6031    }
6032
6033    d
6034}
6035
6036/// Internal helper: partial sorting scan right to left 32s 1k (OpenMP variant).
6037#[doc(hidden)]
6038pub fn partial_sorting_scan_right_to_left_32s_1k_omp(
6039    t: &[SaSint],
6040    sa: &mut [SaSint],
6041    n: SaSint,
6042    buckets: &mut [SaSint],
6043    threads: SaSint,
6044    thread_state: &mut [ThreadState],
6045) {
6046    if threads == 1 || n < 65_536 {
6047        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, 0, n as FastSint);
6048        return;
6049    }
6050    if thread_state.is_empty() {
6051        partial_sorting_scan_right_to_left_32s_1k(t, sa, buckets, 0, n as FastSint);
6052        return;
6053    }
6054
6055    let threads_usize = usize::try_from(threads)
6056        .expect("threads must be non-negative")
6057        .max(1);
6058    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
6059    let mut block_start = FastSint::try_from(n).expect("n must fit FastSint") - 1;
6060    let block_span = FastSint::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
6061        .expect("block span must fit FastSint");
6062    while block_start >= 0 {
6063        let mut block_end = block_start - block_span;
6064        if block_end < 0 {
6065            block_end = -1;
6066        }
6067
6068        partial_sorting_scan_right_to_left_32s_1k_block_omp(
6069            t,
6070            sa,
6071            buckets,
6072            &mut cache,
6073            block_end + 1,
6074            block_start - block_end,
6075            threads,
6076        );
6077
6078        if block_end < 0 {
6079            break;
6080        }
6081        block_start = block_end;
6082    }
6083}
6084
6085/// Internal helper: partial sorting gather lms suffixes 32s 4k.
6086#[doc(hidden)]
6087pub fn partial_sorting_gather_lms_suffixes_32s_4k(
6088    sa: &mut [SaSint],
6089    omp_block_start: FastSint,
6090    omp_block_size: FastSint,
6091) -> FastSint {
6092    if omp_block_size <= 0 {
6093        return omp_block_start;
6094    }
6095
6096    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6097    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6098    let mut l = start;
6099
6100    for i in start..start + size {
6101        let s = sa[i] as SaUint;
6102        sa[l] = ((s.wrapping_sub(SUFFIX_GROUP_MARKER as SaUint)) & !(SUFFIX_GROUP_MARKER as SaUint))
6103            as SaSint;
6104        l += usize::from((s as SaSint) < 0);
6105    }
6106
6107    l as FastSint
6108}
6109
6110/// Internal helper: partial sorting gather lms suffixes 32s 1k.
6111#[doc(hidden)]
6112pub fn partial_sorting_gather_lms_suffixes_32s_1k(
6113    sa: &mut [SaSint],
6114    omp_block_start: FastSint,
6115    omp_block_size: FastSint,
6116) -> FastSint {
6117    if omp_block_size <= 0 {
6118        return omp_block_start;
6119    }
6120
6121    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6122    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6123    let mut l = start;
6124
6125    for i in start..start + size {
6126        let s = sa[i];
6127        sa[l] = s & SAINT_MAX;
6128        l += usize::from(s < 0);
6129    }
6130
6131    l as FastSint
6132}
6133
6134/// Internal helper: partial sorting gather lms suffixes 32s 4k (OpenMP variant).
6135#[doc(hidden)]
6136pub fn partial_sorting_gather_lms_suffixes_32s_4k_omp(
6137    sa: &mut [SaSint],
6138    n: SaSint,
6139    threads: SaSint,
6140    thread_state: &mut [ThreadState],
6141) {
6142    let n_usize = usize::try_from(n).expect("n must be non-negative");
6143    let omp_num_threads = if threads > 1 && n >= 65_536 {
6144        usize::try_from(threads)
6145            .expect("threads must be non-negative")
6146            .min(thread_state.len())
6147            .max(1)
6148    } else {
6149        1
6150    };
6151
6152    if omp_num_threads == 1 {
6153        let _ = partial_sorting_gather_lms_suffixes_32s_4k(sa, 0, n as FastSint);
6154        return;
6155    }
6156
6157    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
6158    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
6159        let block_start = thread_num * omp_block_stride;
6160        let block_size = if thread_num + 1 < omp_num_threads {
6161            omp_block_stride
6162        } else {
6163            n_usize - block_start
6164        };
6165        state.position = block_start as FastSint;
6166        state.count = partial_sorting_gather_lms_suffixes_32s_4k(
6167            sa,
6168            block_start as FastSint,
6169            block_size as FastSint,
6170        ) - block_start as FastSint;
6171    }
6172
6173    let mut position = 0usize;
6174    for (thread_num, state) in thread_state.iter().take(omp_num_threads).enumerate() {
6175        let count = usize::try_from(state.count).expect("count must be non-negative");
6176        let src = usize::try_from(state.position).expect("position must be non-negative");
6177        if thread_num > 0 && count > 0 {
6178            sa.copy_within(src..src + count, position);
6179        }
6180        position += count;
6181    }
6182}
6183
6184/// Internal helper: partial sorting gather lms suffixes 32s 1k (OpenMP variant).
6185#[doc(hidden)]
6186pub fn partial_sorting_gather_lms_suffixes_32s_1k_omp(
6187    sa: &mut [SaSint],
6188    n: SaSint,
6189    threads: SaSint,
6190    thread_state: &mut [ThreadState],
6191) {
6192    let n_usize = usize::try_from(n).expect("n must be non-negative");
6193    let omp_num_threads = if threads > 1 && n >= 65_536 {
6194        usize::try_from(threads)
6195            .expect("threads must be non-negative")
6196            .min(thread_state.len())
6197            .max(1)
6198    } else {
6199        1
6200    };
6201
6202    if omp_num_threads == 1 {
6203        let _ = partial_sorting_gather_lms_suffixes_32s_1k(sa, 0, n as FastSint);
6204        return;
6205    }
6206
6207    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
6208    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
6209        let block_start = thread_num * omp_block_stride;
6210        let block_size = if thread_num + 1 < omp_num_threads {
6211            omp_block_stride
6212        } else {
6213            n_usize - block_start
6214        };
6215        state.position = block_start as FastSint;
6216        state.count = partial_sorting_gather_lms_suffixes_32s_1k(
6217            sa,
6218            block_start as FastSint,
6219            block_size as FastSint,
6220        ) - block_start as FastSint;
6221    }
6222
6223    let mut position = 0usize;
6224    for (thread_num, state) in thread_state.iter().take(omp_num_threads).enumerate() {
6225        let count = usize::try_from(state.count).expect("count must be non-negative");
6226        let src = usize::try_from(state.position).expect("position must be non-negative");
6227        if thread_num > 0 && count > 0 {
6228            sa.copy_within(src..src + count, position);
6229        }
6230        position += count;
6231    }
6232}
6233
6234/// Internal helper: induce partial order 8u (OpenMP variant).
6235#[doc(hidden)]
6236pub fn induce_partial_order_8u_omp(
6237    t: &[u8],
6238    sa: &mut [SaSint],
6239    n: SaSint,
6240    k: SaSint,
6241    flags: SaSint,
6242    buckets: &mut [SaSint],
6243    first_lms_suffix: SaSint,
6244    left_suffixes_count: SaSint,
6245    threads: SaSint,
6246    thread_state: &mut [ThreadState],
6247) {
6248    buckets[2 * ALPHABET_SIZE..4 * ALPHABET_SIZE].fill(0);
6249
6250    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
6251        let left = 4 * ALPHABET_SIZE + buckets_index2(0, 1);
6252        let right = 4 * ALPHABET_SIZE + buckets_index2(1, 1);
6253        buckets[left] = buckets[right] - 1;
6254        flip_suffix_markers_omp(sa, buckets[left], threads);
6255    }
6256
6257    let d = partial_sorting_scan_left_to_right_8u_omp(
6258        t,
6259        sa,
6260        n,
6261        k,
6262        buckets,
6263        left_suffixes_count,
6264        0,
6265        threads,
6266        thread_state,
6267    );
6268    partial_sorting_shift_markers_8u_omp(sa, n, buckets, threads);
6269
6270    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
6271        partial_gsa_scan_right_to_left_8u_omp(
6272            t,
6273            sa,
6274            n,
6275            k,
6276            buckets,
6277            first_lms_suffix,
6278            left_suffixes_count,
6279            d,
6280            threads,
6281            thread_state,
6282        );
6283
6284        if t[usize::try_from(first_lms_suffix).expect("first_lms_suffix must be non-negative")] == 0
6285        {
6286            let count = usize::try_from(buckets[buckets_index2(1, 1)] - 1)
6287                .expect("count must be non-negative");
6288            sa.copy_within(0..count, 1);
6289            sa[0] = first_lms_suffix | SAINT_MIN;
6290        }
6291
6292        buckets[buckets_index2(0, 1)] = 0;
6293    } else {
6294        partial_sorting_scan_right_to_left_8u_omp(
6295            t,
6296            sa,
6297            n,
6298            k,
6299            buckets,
6300            first_lms_suffix,
6301            left_suffixes_count,
6302            d,
6303            threads,
6304            thread_state,
6305        );
6306    }
6307}
6308
6309/// Internal helper: induce partial order 32s 6k (OpenMP variant).
6310#[doc(hidden)]
6311pub fn induce_partial_order_32s_6k_omp(
6312    t: &[SaSint],
6313    sa: &mut [SaSint],
6314    n: SaSint,
6315    k: SaSint,
6316    buckets: &mut [SaSint],
6317    first_lms_suffix: SaSint,
6318    left_suffixes_count: SaSint,
6319    threads: SaSint,
6320    thread_state: &mut [ThreadState],
6321) {
6322    let d = partial_sorting_scan_left_to_right_32s_6k_omp(
6323        t,
6324        sa,
6325        n,
6326        buckets,
6327        left_suffixes_count,
6328        0,
6329        threads,
6330        thread_state,
6331    );
6332    partial_sorting_shift_markers_32s_6k_omp(sa, k, buckets, threads);
6333    partial_sorting_shift_buckets_32s_6k(k, buckets);
6334    let _ = partial_sorting_scan_right_to_left_32s_6k_omp(
6335        t,
6336        sa,
6337        n,
6338        buckets,
6339        first_lms_suffix,
6340        left_suffixes_count,
6341        d,
6342        threads,
6343        thread_state,
6344    );
6345}
6346
6347/// Internal helper: induce partial order 32s 4k (OpenMP variant).
6348#[doc(hidden)]
6349pub fn induce_partial_order_32s_4k_omp(
6350    t: &[SaSint],
6351    sa: &mut [SaSint],
6352    n: SaSint,
6353    k: SaSint,
6354    buckets: &mut [SaSint],
6355    threads: SaSint,
6356    thread_state: &mut [ThreadState],
6357) {
6358    let zero_len = 2 * usize::try_from(k).expect("k must be non-negative");
6359    buckets[..zero_len].fill(0);
6360
6361    let d = partial_sorting_scan_left_to_right_32s_4k_omp(
6362        t,
6363        sa,
6364        n,
6365        k,
6366        buckets,
6367        0,
6368        threads,
6369        thread_state,
6370    );
6371    partial_sorting_shift_markers_32s_4k(sa, n);
6372    let _ = partial_sorting_scan_right_to_left_32s_4k_omp(
6373        t,
6374        sa,
6375        n,
6376        k,
6377        buckets,
6378        d,
6379        threads,
6380        thread_state,
6381    );
6382    partial_sorting_gather_lms_suffixes_32s_4k_omp(sa, n, threads, thread_state);
6383}
6384
6385/// Internal helper: induce partial order 32s 2k (OpenMP variant).
6386#[doc(hidden)]
6387pub fn induce_partial_order_32s_2k_omp(
6388    t: &[SaSint],
6389    sa: &mut [SaSint],
6390    n: SaSint,
6391    k: SaSint,
6392    buckets: &mut [SaSint],
6393    threads: SaSint,
6394    thread_state: &mut [ThreadState],
6395) {
6396    let k_usize = usize::try_from(k).expect("k must be non-negative");
6397    let (left, right) = buckets.split_at_mut(k_usize);
6398    partial_sorting_scan_left_to_right_32s_1k_omp(t, sa, n, right, threads, thread_state);
6399    partial_sorting_scan_right_to_left_32s_1k_omp(t, sa, n, left, threads, thread_state);
6400    partial_sorting_gather_lms_suffixes_32s_1k_omp(sa, n, threads, thread_state);
6401}
6402
6403/// Internal helper: induce partial order 32s 1k (OpenMP variant).
6404#[doc(hidden)]
6405pub fn induce_partial_order_32s_1k_omp(
6406    t: &[SaSint],
6407    sa: &mut [SaSint],
6408    n: SaSint,
6409    k: SaSint,
6410    buckets: &mut [SaSint],
6411    threads: SaSint,
6412    thread_state: &mut [ThreadState],
6413) {
6414    count_suffixes_32s(t, n, k, buckets);
6415    initialize_buckets_start_32s_1k(k, buckets);
6416    partial_sorting_scan_left_to_right_32s_1k_omp(t, sa, n, buckets, threads, thread_state);
6417
6418    count_suffixes_32s(t, n, k, buckets);
6419    initialize_buckets_end_32s_1k(k, buckets);
6420    partial_sorting_scan_right_to_left_32s_1k_omp(t, sa, n, buckets, threads, thread_state);
6421
6422    partial_sorting_gather_lms_suffixes_32s_1k_omp(sa, n, threads, thread_state);
6423}
6424
6425/// Internal helper: renumber lms suffixes 8u.
6426#[doc(hidden)]
6427pub fn renumber_lms_suffixes_8u(
6428    sa: &mut [SaSint],
6429    m: SaSint,
6430    mut name: SaSint,
6431    omp_block_start: FastSint,
6432    omp_block_size: FastSint,
6433) -> SaSint {
6434    if omp_block_size <= 0 {
6435        return name;
6436    }
6437
6438    let m_usize = usize::try_from(m).expect("m must be non-negative");
6439    let (sa_head, sam) = sa.split_at_mut(m_usize);
6440    let mut i = omp_block_start;
6441    let mut j = omp_block_start + omp_block_size - 64 - 3;
6442
6443    while i < j {
6444        let i0 = i as usize;
6445        let p0 = sa_head[i0];
6446        let d0 = ((p0 & SAINT_MAX) >> 1) as usize;
6447        sam[d0] = name | SAINT_MIN;
6448        name += SaSint::from(p0 < 0);
6449
6450        let p1 = sa_head[i0 + 1];
6451        let d1 = ((p1 & SAINT_MAX) >> 1) as usize;
6452        sam[d1] = name | SAINT_MIN;
6453        name += SaSint::from(p1 < 0);
6454
6455        let p2 = sa_head[i0 + 2];
6456        let d2 = ((p2 & SAINT_MAX) >> 1) as usize;
6457        sam[d2] = name | SAINT_MIN;
6458        name += SaSint::from(p2 < 0);
6459
6460        let p3 = sa_head[i0 + 3];
6461        let d3 = ((p3 & SAINT_MAX) >> 1) as usize;
6462        sam[d3] = name | SAINT_MIN;
6463        name += SaSint::from(p3 < 0);
6464
6465        i += 4;
6466    }
6467
6468    j += 64 + 3;
6469    while i < j {
6470        let p = sa_head[i as usize];
6471        let d = ((p & SAINT_MAX) >> 1) as usize;
6472        sam[d] = name | SAINT_MIN;
6473        name += SaSint::from(p < 0);
6474        i += 1;
6475    }
6476
6477    name
6478}
6479
6480/// Internal helper: gather marked lms suffixes.
6481#[doc(hidden)]
6482pub fn gather_marked_lms_suffixes(
6483    sa: &mut [SaSint],
6484    m: SaSint,
6485    l: FastSint,
6486    omp_block_start: FastSint,
6487    omp_block_size: FastSint,
6488) -> FastSint {
6489    if omp_block_size <= 0 {
6490        return l;
6491    }
6492
6493    let mut l = l - 1;
6494    let mut i = m as FastSint + omp_block_start + omp_block_size - 1;
6495    let mut j = m as FastSint + omp_block_start + 3;
6496
6497    while i >= j {
6498        let i0 = i as usize;
6499        let s0 = sa[i0];
6500        sa[l as usize] = s0 & SAINT_MAX;
6501        l -= FastSint::from(s0 < 0);
6502
6503        let s1 = sa[i0 - 1];
6504        sa[l as usize] = s1 & SAINT_MAX;
6505        l -= FastSint::from(s1 < 0);
6506
6507        let s2 = sa[i0 - 2];
6508        sa[l as usize] = s2 & SAINT_MAX;
6509        l -= FastSint::from(s2 < 0);
6510
6511        let s3 = sa[i0 - 3];
6512        sa[l as usize] = s3 & SAINT_MAX;
6513        l -= FastSint::from(s3 < 0);
6514
6515        i -= 4;
6516    }
6517
6518    j -= 3;
6519    while i >= j {
6520        let s = sa[i as usize];
6521        sa[l as usize] = s & SAINT_MAX;
6522        l -= FastSint::from(s < 0);
6523        i -= 1;
6524    }
6525
6526    l + 1
6527}
6528
6529/// Internal helper: renumber lms suffixes 8u (OpenMP variant).
6530#[doc(hidden)]
6531pub fn renumber_lms_suffixes_8u_omp(
6532    sa: &mut [SaSint],
6533    m: SaSint,
6534    threads: SaSint,
6535    thread_state: &mut [ThreadState],
6536) -> SaSint {
6537    let omp_num_threads = if threads > 1 && m >= 65_536 {
6538        usize::try_from(threads)
6539            .expect("threads must be non-negative")
6540            .min(thread_state.len())
6541            .max(1)
6542    } else {
6543        1
6544    };
6545    let omp_block_stride = (m as FastSint / omp_num_threads as FastSint) & !15;
6546
6547    let name = if omp_num_threads == 1 {
6548        renumber_lms_suffixes_8u(sa, m, 0, 0, m as FastSint)
6549    } else {
6550        {
6551            let sa_ro: &[SaSint] = sa;
6552            run_rayon_with_threads(omp_num_threads, || {
6553                thread_state[..omp_num_threads]
6554                    .par_iter_mut()
6555                    .enumerate()
6556                    .for_each(|(omp_thread_num, state)| {
6557                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6558                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6559                            omp_block_stride
6560                        } else {
6561                            m as FastSint - omp_block_start
6562                        };
6563                        state.count =
6564                            count_negative_marked_suffixes(sa_ro, omp_block_start, omp_block_size)
6565                                as FastSint;
6566                    });
6567            });
6568        }
6569
6570        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
6571            .iter()
6572            .map(|s| s.count)
6573            .collect();
6574        let name = counts.iter().sum::<FastSint>() as SaSint;
6575
6576        {
6577            let sa_ptr = SyncMutPtr::new(sa);
6578            let counts_ref: &[FastSint] = &counts;
6579            run_rayon_with_threads(omp_num_threads, || {
6580                (0..omp_num_threads)
6581                    .into_par_iter()
6582                    .for_each(|omp_thread_num| {
6583                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6584                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6585                            omp_block_stride
6586                        } else {
6587                            m as FastSint - omp_block_start
6588                        };
6589                        let mut count: FastSint = 0;
6590                        for tt in 0..omp_thread_num {
6591                            count += counts_ref[tt];
6592                        }
6593                        let sa = unsafe { sa_ptr.as_slice() };
6594                        let _ = renumber_lms_suffixes_8u(
6595                            sa,
6596                            m,
6597                            count as SaSint,
6598                            omp_block_start,
6599                            omp_block_size,
6600                        );
6601                    });
6602            });
6603        }
6604        name
6605    };
6606
6607    name
6608}
6609
6610/// Internal helper: gather marked lms suffixes (OpenMP variant).
6611#[doc(hidden)]
6612pub fn gather_marked_lms_suffixes_omp(
6613    sa: &mut [SaSint],
6614    n: SaSint,
6615    m: SaSint,
6616    fs: SaSint,
6617    threads: SaSint,
6618    thread_state: &mut [ThreadState],
6619) {
6620    let n_fast = n as FastSint;
6621    let m_fast = m as FastSint;
6622    let omp_num_threads = if threads > 1 && n >= 131_072 {
6623        usize::try_from(threads)
6624            .expect("threads must be non-negative")
6625            .min(thread_state.len())
6626            .max(1)
6627    } else {
6628        1
6629    };
6630    let omp_block_stride = ((n_fast >> 1) / omp_num_threads as FastSint) & !15;
6631
6632    if omp_num_threads == 1 {
6633        let _ = gather_marked_lms_suffixes(sa, m, n_fast + fs as FastSint, 0, n_fast >> 1);
6634    } else {
6635        {
6636            let sa_ptr = SyncMutPtr::new(sa);
6637            run_rayon_with_threads(omp_num_threads, || {
6638                thread_state[..omp_num_threads]
6639                    .par_iter_mut()
6640                    .enumerate()
6641                    .for_each(|(omp_thread_num, state)| {
6642                        let omp_block_start = omp_thread_num as FastSint * omp_block_stride;
6643                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6644                            omp_block_stride
6645                        } else {
6646                            (n_fast >> 1) - omp_block_start
6647                        };
6648
6649                        let sa = unsafe { sa_ptr.as_slice() };
6650                        if omp_thread_num < omp_num_threads - 1 {
6651                            state.position = gather_marked_lms_suffixes(
6652                                sa,
6653                                m,
6654                                m_fast + omp_block_start + omp_block_size,
6655                                omp_block_start,
6656                                omp_block_size,
6657                            );
6658                            state.count =
6659                                m_fast + omp_block_start + omp_block_size - state.position;
6660                        } else {
6661                            state.position = gather_marked_lms_suffixes(
6662                                sa,
6663                                m,
6664                                n_fast + fs as FastSint,
6665                                omp_block_start,
6666                                omp_block_size,
6667                            );
6668                            state.count = n_fast + fs as FastSint - state.position;
6669                        }
6670                    });
6671            });
6672        }
6673
6674        let mut position = n_fast + fs as FastSint;
6675        for t in (0..omp_num_threads).rev() {
6676            position -= thread_state[t].count;
6677            if t + 1 != omp_num_threads && thread_state[t].count > 0 {
6678                let src = usize::try_from(thread_state[t].position)
6679                    .expect("position must be non-negative");
6680                let len =
6681                    usize::try_from(thread_state[t].count).expect("count must be non-negative");
6682                let dst = usize::try_from(position).expect("position must be non-negative");
6683                sa.copy_within(src..src + len, dst);
6684            }
6685        }
6686    }
6687}
6688
6689/// Internal helper: renumber and gather lms suffixes (OpenMP variant).
6690#[doc(hidden)]
6691pub fn renumber_and_gather_lms_suffixes_omp(
6692    sa: &mut [SaSint],
6693    n: SaSint,
6694    m: SaSint,
6695    fs: SaSint,
6696    threads: SaSint,
6697    thread_state: &mut [ThreadState],
6698) -> SaSint {
6699    let m_usize = usize::try_from(m).expect("m must be non-negative");
6700    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
6701    sa[m_usize..m_usize + half_n].fill(0);
6702
6703    let name = renumber_lms_suffixes_8u_omp(sa, m, threads, thread_state);
6704    if name < m {
6705        gather_marked_lms_suffixes_omp(sa, n, m, fs, threads, thread_state);
6706    } else {
6707        let mut i = 0;
6708        while i < m_usize {
6709            sa[i] &= SAINT_MAX;
6710            i += 1;
6711        }
6712    }
6713
6714    name
6715}
6716
6717/// Internal helper: renumber distinct lms suffixes 32s 4k.
6718#[doc(hidden)]
6719pub fn renumber_distinct_lms_suffixes_32s_4k(
6720    sa: &mut [SaSint],
6721    m: SaSint,
6722    mut name: SaSint,
6723    omp_block_start: FastSint,
6724    omp_block_size: FastSint,
6725) -> SaSint {
6726    if omp_block_size <= 0 {
6727        return name;
6728    }
6729
6730    let prefetch_distance = 64usize;
6731    let m_usize = usize::try_from(m).expect("m must be non-negative");
6732    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6733    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6734    let (sa_head, sam) = sa.split_at_mut(m_usize);
6735    let mut i = start;
6736    let mut j = start
6737        .saturating_add(size)
6738        .saturating_sub(prefetch_distance + 3);
6739    let mut p0;
6740    let mut p1;
6741    let mut p2;
6742    let mut p3 = 0;
6743
6744    while i < j {
6745        p0 = sa_head[i];
6746        sa_head[i] = p0 & SAINT_MAX;
6747        sam[(sa_head[i] >> 1) as usize] = name | (p0 & p3 & SAINT_MIN);
6748        name += SaSint::from(p0 < 0);
6749
6750        p1 = sa_head[i + 1];
6751        sa_head[i + 1] = p1 & SAINT_MAX;
6752        sam[(sa_head[i + 1] >> 1) as usize] = name | (p1 & p0 & SAINT_MIN);
6753        name += SaSint::from(p1 < 0);
6754
6755        p2 = sa_head[i + 2];
6756        sa_head[i + 2] = p2 & SAINT_MAX;
6757        sam[(sa_head[i + 2] >> 1) as usize] = name | (p2 & p1 & SAINT_MIN);
6758        name += SaSint::from(p2 < 0);
6759
6760        p3 = sa_head[i + 3];
6761        sa_head[i + 3] = p3 & SAINT_MAX;
6762        sam[(sa_head[i + 3] >> 1) as usize] = name | (p3 & p2 & SAINT_MIN);
6763        name += SaSint::from(p3 < 0);
6764
6765        i += 4;
6766    }
6767
6768    j = start + size;
6769    while i < j {
6770        p2 = p3;
6771        p3 = sa_head[i];
6772        sa_head[i] = p3 & SAINT_MAX;
6773        sam[(sa_head[i] >> 1) as usize] = name | (p3 & p2 & SAINT_MIN);
6774        name += SaSint::from(p3 < 0);
6775        i += 1;
6776    }
6777
6778    name
6779}
6780
6781/// Internal helper: mark distinct lms suffixes 32s.
6782#[doc(hidden)]
6783pub fn mark_distinct_lms_suffixes_32s(
6784    sa: &mut [SaSint],
6785    m: SaSint,
6786    omp_block_start: FastSint,
6787    omp_block_size: FastSint,
6788) {
6789    if omp_block_size <= 0 {
6790        return;
6791    }
6792
6793    let m_usize = usize::try_from(m).expect("m must be non-negative");
6794    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6795    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6796    let mut i = m_usize + start;
6797    let mut j = m_usize + start + size.saturating_sub(3);
6798    let mut p3 = 0;
6799
6800    while i < j {
6801        let mut p0 = sa[i];
6802        sa[i] = p0 & (p3 | SAINT_MAX);
6803        p0 = if p0 == 0 { p3 } else { p0 };
6804
6805        let mut p1 = sa[i + 1];
6806        sa[i + 1] = p1 & (p0 | SAINT_MAX);
6807        p1 = if p1 == 0 { p0 } else { p1 };
6808
6809        let mut p2 = sa[i + 2];
6810        sa[i + 2] = p2 & (p1 | SAINT_MAX);
6811        p2 = if p2 == 0 { p1 } else { p2 };
6812
6813        p3 = sa[i + 3];
6814        sa[i + 3] = p3 & (p2 | SAINT_MAX);
6815        p3 = if p3 == 0 { p2 } else { p3 };
6816
6817        i += 4;
6818    }
6819
6820    j = m_usize + start + size;
6821    while i < j {
6822        let p2 = p3;
6823        p3 = sa[i];
6824        sa[i] = p3 & (p2 | SAINT_MAX);
6825        p3 = if p3 == 0 { p2 } else { p3 };
6826        i += 1;
6827    }
6828}
6829
6830/// Internal helper: clamp lms suffixes length 32s.
6831#[doc(hidden)]
6832pub fn clamp_lms_suffixes_length_32s(
6833    sa: &mut [SaSint],
6834    m: SaSint,
6835    omp_block_start: FastSint,
6836    omp_block_size: FastSint,
6837) {
6838    if omp_block_size <= 0 {
6839        return;
6840    }
6841
6842    let m_usize = usize::try_from(m).expect("m must be non-negative");
6843    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
6844    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
6845    let mut i = m_usize + start;
6846    let mut j = m_usize + start + size.saturating_sub(3);
6847
6848    while i < j {
6849        let s0 = sa[i];
6850        sa[i] = if s0 < 0 { s0 } else { 0 } & SAINT_MAX;
6851
6852        let s1 = sa[i + 1];
6853        sa[i + 1] = if s1 < 0 { s1 } else { 0 } & SAINT_MAX;
6854
6855        let s2 = sa[i + 2];
6856        sa[i + 2] = if s2 < 0 { s2 } else { 0 } & SAINT_MAX;
6857
6858        let s3 = sa[i + 3];
6859        sa[i + 3] = if s3 < 0 { s3 } else { 0 } & SAINT_MAX;
6860
6861        i += 4;
6862    }
6863
6864    j = m_usize + start + size;
6865    while i < j {
6866        let s = sa[i];
6867        sa[i] = if s < 0 { s } else { 0 } & SAINT_MAX;
6868        i += 1;
6869    }
6870}
6871
6872/// Internal helper: renumber distinct lms suffixes 32s 4k (OpenMP variant).
6873#[doc(hidden)]
6874pub fn renumber_distinct_lms_suffixes_32s_4k_omp(
6875    sa: &mut [SaSint],
6876    m: SaSint,
6877    threads: SaSint,
6878    thread_state: &mut [ThreadState],
6879) -> SaSint {
6880    let m_usize = usize::try_from(m).expect("m must be non-negative");
6881    let omp_num_threads = if threads > 1 && m >= 65_536 {
6882        usize::try_from(threads)
6883            .expect("threads must be non-negative")
6884            .min(thread_state.len())
6885            .max(1)
6886    } else {
6887        1
6888    };
6889    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
6890
6891    let name = if omp_num_threads == 1 {
6892        let omp_block_start = 0usize;
6893        let omp_block_size = m_usize - omp_block_start;
6894        renumber_distinct_lms_suffixes_32s_4k(
6895            sa,
6896            m,
6897            1,
6898            omp_block_start as FastSint,
6899            omp_block_size as FastSint,
6900        )
6901    } else {
6902        {
6903            let sa_ro: &[SaSint] = sa;
6904            run_rayon_with_threads(omp_num_threads, || {
6905                thread_state[..omp_num_threads]
6906                    .par_iter_mut()
6907                    .enumerate()
6908                    .for_each(|(omp_thread_num, state)| {
6909                        let omp_block_start = omp_thread_num * omp_block_stride;
6910                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6911                            omp_block_stride
6912                        } else {
6913                            m_usize - omp_block_start
6914                        };
6915                        state.count = count_negative_marked_suffixes(
6916                            sa_ro,
6917                            omp_block_start as FastSint,
6918                            omp_block_size as FastSint,
6919                        ) as FastSint;
6920                    });
6921            });
6922        }
6923
6924        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
6925            .iter()
6926            .map(|s| s.count)
6927            .collect();
6928        let name = (1 + counts.iter().sum::<FastSint>()) as SaSint;
6929
6930        {
6931            let sa_ptr = SyncMutPtr::new(sa);
6932            let counts_ref: &[FastSint] = &counts;
6933            run_rayon_with_threads(omp_num_threads, || {
6934                (0..omp_num_threads)
6935                    .into_par_iter()
6936                    .for_each(|omp_thread_num| {
6937                        let omp_block_start = omp_thread_num * omp_block_stride;
6938                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6939                            omp_block_stride
6940                        } else {
6941                            m_usize - omp_block_start
6942                        };
6943                        let mut count: FastSint = 1;
6944                        for tt in 0..omp_thread_num {
6945                            count += counts_ref[tt];
6946                        }
6947                        let sa = unsafe { sa_ptr.as_slice() };
6948                        let _ = renumber_distinct_lms_suffixes_32s_4k(
6949                            sa,
6950                            m,
6951                            count as SaSint,
6952                            omp_block_start as FastSint,
6953                            omp_block_size as FastSint,
6954                        );
6955                    });
6956            });
6957        }
6958        name
6959    };
6960
6961    name - 1
6962}
6963
6964/// Internal helper: mark distinct lms suffixes 32s (OpenMP variant).
6965#[doc(hidden)]
6966pub fn mark_distinct_lms_suffixes_32s_omp(
6967    sa: &mut [SaSint],
6968    n: SaSint,
6969    m: SaSint,
6970    threads: SaSint,
6971) {
6972    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
6973    let omp_num_threads = if threads > 1 && n >= 131_072 {
6974        usize::try_from(threads)
6975            .expect("threads must be non-negative")
6976            .max(1)
6977    } else {
6978        1
6979    };
6980    let omp_block_stride = (half_n / omp_num_threads) & !15usize;
6981
6982    {
6983        let sa_ptr = SyncMutPtr::new(sa);
6984        run_rayon_with_threads(omp_num_threads, || {
6985            (0..omp_num_threads)
6986                .into_par_iter()
6987                .for_each(|omp_thread_num| {
6988                    let omp_block_start = omp_thread_num * omp_block_stride;
6989                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
6990                        omp_block_stride
6991                    } else {
6992                        half_n - omp_block_start
6993                    };
6994                    let sa = unsafe { sa_ptr.as_slice() };
6995                    mark_distinct_lms_suffixes_32s(
6996                        sa,
6997                        m,
6998                        omp_block_start as FastSint,
6999                        omp_block_size as FastSint,
7000                    );
7001                });
7002        });
7003    }
7004}
7005
7006/// Internal helper: clamp lms suffixes length 32s (OpenMP variant).
7007#[doc(hidden)]
7008pub fn clamp_lms_suffixes_length_32s_omp(sa: &mut [SaSint], n: SaSint, m: SaSint, threads: SaSint) {
7009    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
7010    let omp_num_threads = if threads > 1 && n >= 131_072 {
7011        usize::try_from(threads)
7012            .expect("threads must be non-negative")
7013            .max(1)
7014    } else {
7015        1
7016    };
7017    let omp_block_stride = (half_n / omp_num_threads) & !15usize;
7018
7019    {
7020        let sa_ptr = SyncMutPtr::new(sa);
7021        run_rayon_with_threads(omp_num_threads, || {
7022            (0..omp_num_threads)
7023                .into_par_iter()
7024                .for_each(|omp_thread_num| {
7025                    let omp_block_start = omp_thread_num * omp_block_stride;
7026                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7027                        omp_block_stride
7028                    } else {
7029                        half_n - omp_block_start
7030                    };
7031                    let sa = unsafe { sa_ptr.as_slice() };
7032                    clamp_lms_suffixes_length_32s(
7033                        sa,
7034                        m,
7035                        omp_block_start as FastSint,
7036                        omp_block_size as FastSint,
7037                    );
7038                });
7039        });
7040    }
7041}
7042
7043/// Internal helper: renumber and mark distinct lms suffixes 32s 4k (OpenMP variant).
7044#[doc(hidden)]
7045pub fn renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
7046    sa: &mut [SaSint],
7047    n: SaSint,
7048    m: SaSint,
7049    threads: SaSint,
7050    thread_state: &mut [ThreadState],
7051) -> SaSint {
7052    let m_usize = usize::try_from(m).expect("m must be non-negative");
7053    let half_n = usize::try_from(n >> 1).expect("n must be non-negative");
7054    sa[m_usize..m_usize + half_n].fill(0);
7055
7056    let name = renumber_distinct_lms_suffixes_32s_4k_omp(sa, m, threads, thread_state);
7057    if name < m {
7058        mark_distinct_lms_suffixes_32s_omp(sa, n, m, threads);
7059    }
7060
7061    name
7062}
7063
7064/// Internal helper: renumber and mark distinct lms suffixes 32s 1k (OpenMP variant).
7065#[doc(hidden)]
7066pub fn renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(
7067    t: &[SaSint],
7068    sa: &mut [SaSint],
7069    n: SaSint,
7070    m: SaSint,
7071    threads: SaSint,
7072) -> SaSint {
7073    let m_usize = usize::try_from(m).expect("m must be non-negative");
7074    let n_usize = usize::try_from(n).expect("n must be non-negative");
7075
7076    let _ = gather_lms_suffixes_32s(t, sa, n);
7077
7078    let zero_len = n_usize
7079        .checked_sub(m_usize)
7080        .and_then(|v| v.checked_sub(m_usize))
7081        .expect("n must be at least 2*m");
7082    sa[m_usize..m_usize + zero_len].fill(0);
7083
7084    {
7085        let prefetch_distance: FastSint = 64;
7086        let mut i = n as FastSint - m as FastSint;
7087        let mut j = n as FastSint - 1 - prefetch_distance - 3;
7088
7089        while i < j {
7090            let iu = i as usize;
7091            let s0 = (sa[iu] as SaUint >> 1) as usize;
7092            let s1 = (sa[iu + 1] as SaUint >> 1) as usize;
7093            let s2 = (sa[iu + 2] as SaUint >> 1) as usize;
7094            let s3 = (sa[iu + 3] as SaUint >> 1) as usize;
7095
7096            sa[m_usize + s0] = sa[iu + 1] - sa[iu] + 1 + SAINT_MIN;
7097            sa[m_usize + s1] = sa[iu + 2] - sa[iu + 1] + 1 + SAINT_MIN;
7098            sa[m_usize + s2] = sa[iu + 3] - sa[iu + 2] + 1 + SAINT_MIN;
7099            sa[m_usize + s3] = sa[iu + 4] - sa[iu + 3] + 1 + SAINT_MIN;
7100            i += 4;
7101        }
7102
7103        j += prefetch_distance + 3;
7104        while i < j {
7105            let iu = i as usize;
7106            let s = (sa[iu] as SaUint >> 1) as usize;
7107            sa[m_usize + s] = sa[iu + 1] - sa[iu] + 1 + SAINT_MIN;
7108            i += 1;
7109        }
7110
7111        let tail = (sa[n_usize - 1] as SaUint >> 1) as usize;
7112        sa[m_usize + tail] = 1 + SAINT_MIN;
7113    }
7114
7115    clamp_lms_suffixes_length_32s_omp(sa, n, m, threads);
7116
7117    let mut name = 1;
7118    if m_usize > 0 {
7119        let (sa_head, sam) = sa.split_at_mut(m_usize);
7120        let mut i = 1usize;
7121        let prefetch_distance = 64usize;
7122        let mut j = m_usize.saturating_sub(prefetch_distance + 1);
7123        let mut p = usize::try_from(sa_head[0]).expect("suffix index must be non-negative");
7124        let mut plen = sam[p >> 1];
7125        let mut pdiff = SAINT_MIN;
7126
7127        while i < j {
7128            let q = usize::try_from(sa_head[i]).expect("suffix index must be non-negative");
7129            let qlen = sam[q >> 1];
7130            let mut qdiff = SAINT_MIN;
7131            if plen == qlen {
7132                let mut l = 0usize;
7133                while l < qlen as usize {
7134                    if t[p + l] != t[q + l] {
7135                        break;
7136                    }
7137                    l += 1;
7138                }
7139                qdiff = ((l as SaSint) - qlen) & SAINT_MIN;
7140            }
7141            sam[p >> 1] = name | (pdiff & qdiff);
7142            name += SaSint::from(qdiff < 0);
7143
7144            p = usize::try_from(sa_head[i + 1]).expect("suffix index must be non-negative");
7145            plen = sam[p >> 1];
7146            pdiff = SAINT_MIN;
7147            if qlen == plen {
7148                let mut l = 0usize;
7149                while l < plen as usize {
7150                    if t[q + l] != t[p + l] {
7151                        break;
7152                    }
7153                    l += 1;
7154                }
7155                pdiff = ((l as SaSint) - plen) & SAINT_MIN;
7156            }
7157            sam[q >> 1] = name | (qdiff & pdiff);
7158            name += SaSint::from(pdiff < 0);
7159            i += 2;
7160        }
7161
7162        j = m_usize;
7163        while i < j {
7164            let q = usize::try_from(sa_head[i]).expect("suffix index must be non-negative");
7165            let qlen = sam[q >> 1];
7166            let mut qdiff = SAINT_MIN;
7167            if plen == qlen {
7168                let mut l = 0usize;
7169                while l < plen as usize {
7170                    if t[p + l] != t[q + l] {
7171                        break;
7172                    }
7173                    l += 1;
7174                }
7175                qdiff = ((l as SaSint) - plen) & SAINT_MIN;
7176            }
7177            sam[p >> 1] = name | (pdiff & qdiff);
7178            name += SaSint::from(qdiff < 0);
7179
7180            p = q;
7181            plen = qlen;
7182            pdiff = qdiff;
7183            i += 1;
7184        }
7185
7186        sam[p >> 1] = name | pdiff;
7187        name += 1;
7188    }
7189
7190    if name <= m {
7191        mark_distinct_lms_suffixes_32s_omp(sa, n, m, threads);
7192    }
7193
7194    name - 1
7195}
7196
7197/// Internal helper: reconstruct lms suffixes.
7198#[doc(hidden)]
7199pub fn reconstruct_lms_suffixes(
7200    sa: &mut [SaSint],
7201    n: SaSint,
7202    m: SaSint,
7203    omp_block_start: FastSint,
7204    omp_block_size: FastSint,
7205) {
7206    if omp_block_size <= 0 {
7207        return;
7208    }
7209
7210    let prefetch_distance: FastSint = 64;
7211    let base = (n - m) as usize;
7212    let mut i = omp_block_start;
7213    let mut j = omp_block_start + omp_block_size - prefetch_distance - 3;
7214
7215    while i < j {
7216        let iu = i as usize;
7217        let s0 = sa[iu] as usize;
7218        let s1 = sa[iu + 1] as usize;
7219        let s2 = sa[iu + 2] as usize;
7220        let s3 = sa[iu + 3] as usize;
7221        sa[iu] = sa[base + s0];
7222        sa[iu + 1] = sa[base + s1];
7223        sa[iu + 2] = sa[base + s2];
7224        sa[iu + 3] = sa[base + s3];
7225        i += 4;
7226    }
7227
7228    j += prefetch_distance + 3;
7229    while i < j {
7230        let iu = i as usize;
7231        let s = sa[iu] as usize;
7232        sa[iu] = sa[base + s];
7233        i += 1;
7234    }
7235}
7236
7237/// Internal helper: reconstruct lms suffixes (OpenMP variant).
7238#[doc(hidden)]
7239pub fn reconstruct_lms_suffixes_omp(sa: &mut [SaSint], n: SaSint, m: SaSint, threads: SaSint) {
7240    let m_usize = usize::try_from(m).expect("m must be non-negative");
7241    let omp_num_threads = if threads > 1 && m >= 65_536 {
7242        usize::try_from(threads)
7243            .expect("threads must be non-negative")
7244            .max(1)
7245    } else {
7246        1
7247    };
7248    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
7249
7250    {
7251        let sa_ptr = SyncMutPtr::new(sa);
7252        run_rayon_with_threads(omp_num_threads, || {
7253            (0..omp_num_threads)
7254                .into_par_iter()
7255                .for_each(|omp_thread_num| {
7256                    let omp_block_start = omp_thread_num * omp_block_stride;
7257                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
7258                        omp_block_stride
7259                    } else {
7260                        m_usize - omp_block_start
7261                    };
7262                    let sa = unsafe { sa_ptr.as_slice() };
7263                    reconstruct_lms_suffixes(
7264                        sa,
7265                        n,
7266                        m,
7267                        omp_block_start as FastSint,
7268                        omp_block_size as FastSint,
7269                    );
7270                });
7271        });
7272    }
7273}
7274
7275/// Internal helper: place lms suffixes interval 8u.
7276#[doc(hidden)]
7277pub fn place_lms_suffixes_interval_8u(
7278    sa: &mut [SaSint],
7279    n: SaSint,
7280    mut m: SaSint,
7281    flags: SaSint,
7282    buckets: &mut [SaSint],
7283) {
7284    let bucket_end_base = 7 * ALPHABET_SIZE;
7285    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
7286        buckets[bucket_end_base] -= 1;
7287    }
7288
7289    let mut j = usize::try_from(n).expect("n must be non-negative");
7290    for c in (0..ALPHABET_SIZE - 1).rev() {
7291        let l = usize::try_from(
7292            buckets[buckets_index2(c, 1) + buckets_index2(1, 0)] - buckets[buckets_index2(c, 1)],
7293        )
7294        .expect("interval length must be non-negative");
7295        if l > 0 {
7296            let i = usize::try_from(buckets[bucket_end_base + c])
7297                .expect("bucket end must be non-negative");
7298            if j > i {
7299                sa[i..j].fill(0);
7300            }
7301
7302            let new_j = i - l;
7303            let src_end = usize::try_from(m).expect("m must be non-negative");
7304            let src_start = src_end - l;
7305            sa.copy_within(src_start..src_end, new_j);
7306            m -= l as SaSint;
7307            j = new_j;
7308        }
7309    }
7310
7311    sa[..j].fill(0);
7312
7313    if (flags & LIBSAIS_FLAGS_GSA) != 0 {
7314        buckets[bucket_end_base] += 1;
7315    }
7316}
7317
7318/// Internal helper: place lms suffixes interval 32s 4k.
7319#[doc(hidden)]
7320pub fn place_lms_suffixes_interval_32s_4k(
7321    sa: &mut [SaSint],
7322    n: SaSint,
7323    k: SaSint,
7324    mut m: SaSint,
7325    buckets: &[SaSint],
7326) {
7327    let k_usize = usize::try_from(k).expect("k must be non-negative");
7328    let bucket_end = &buckets[3 * k_usize..4 * k_usize];
7329
7330    let mut j = usize::try_from(n).expect("n must be non-negative");
7331    for c in (0..k_usize - 1).rev() {
7332        let l = usize::try_from(
7333            buckets[buckets_index2(c, 1) + buckets_index2(1, 0)] - buckets[buckets_index2(c, 1)],
7334        )
7335        .expect("interval length must be non-negative");
7336        if l > 0 {
7337            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7338            if j > i {
7339                sa[i..j].fill(0);
7340            }
7341
7342            let new_j = i - l;
7343            let src_end = usize::try_from(m).expect("m must be non-negative");
7344            let src_start = src_end - l;
7345            sa.copy_within(src_start..src_end, new_j);
7346            m -= l as SaSint;
7347            j = new_j;
7348        }
7349    }
7350
7351    sa[..j].fill(0);
7352}
7353
7354/// Internal helper: place lms suffixes interval 32s 2k.
7355#[doc(hidden)]
7356pub fn place_lms_suffixes_interval_32s_2k(
7357    sa: &mut [SaSint],
7358    n: SaSint,
7359    k: SaSint,
7360    mut m: SaSint,
7361    buckets: &[SaSint],
7362) {
7363    let k_usize = usize::try_from(k).expect("k must be non-negative");
7364    let mut j = usize::try_from(n).expect("n must be non-negative");
7365
7366    if k_usize > 1 {
7367        let mut c = buckets_index2(k_usize - 2, 0) as isize;
7368        while c >= buckets_index2(0, 0) as isize {
7369            let c_usize = c as usize;
7370            let l = usize::try_from(
7371                buckets[c_usize + buckets_index2(1, 1)] - buckets[c_usize + buckets_index2(0, 1)],
7372            )
7373            .expect("interval length must be non-negative");
7374            if l > 0 {
7375                let i =
7376                    usize::try_from(buckets[c_usize]).expect("bucket start must be non-negative");
7377                if j > i {
7378                    sa[i..j].fill(0);
7379                }
7380
7381                let new_j = i - l;
7382                let src_end = usize::try_from(m).expect("m must be non-negative");
7383                let src_start = src_end - l;
7384                sa.copy_within(src_start..src_end, new_j);
7385                m -= l as SaSint;
7386                j = new_j;
7387            }
7388            c -= buckets_index2(1, 0) as isize;
7389        }
7390    }
7391
7392    sa[..j].fill(0);
7393}
7394
7395/// Internal helper: place lms suffixes interval 32s 1k.
7396#[doc(hidden)]
7397pub fn place_lms_suffixes_interval_32s_1k(
7398    t: &[SaSint],
7399    sa: &mut [SaSint],
7400    k: SaSint,
7401    m: SaSint,
7402    buckets: &[SaSint],
7403) {
7404    let mut c = k - 1;
7405    let c_usize = usize::try_from(c).expect("k must be positive");
7406    let mut l = usize::try_from(buckets[c_usize]).expect("bucket end must be non-negative");
7407
7408    let m_usize = usize::try_from(m).expect("m must be non-negative");
7409    for i in (0..m_usize).rev() {
7410        let p = usize::try_from(sa[i]).expect("suffix index must be non-negative");
7411        let tp = t[p];
7412        if tp != c {
7413            c = tp;
7414            let bucket = usize::try_from(c).expect("bucket index must be non-negative");
7415            let bucket_pos =
7416                usize::try_from(buckets[bucket]).expect("bucket end must be non-negative");
7417            if l > bucket_pos {
7418                sa[bucket_pos..l].fill(0);
7419            }
7420            l = bucket_pos;
7421        }
7422        l -= 1;
7423        sa[l] = p as SaSint;
7424    }
7425
7426    sa[..l].fill(0);
7427}
7428
7429/// Internal helper: place lms suffixes histogram 32s 6k.
7430#[doc(hidden)]
7431pub fn place_lms_suffixes_histogram_32s_6k(
7432    sa: &mut [SaSint],
7433    n: SaSint,
7434    k: SaSint,
7435    mut m: SaSint,
7436    buckets: &[SaSint],
7437) {
7438    let k_usize = usize::try_from(k).expect("k must be non-negative");
7439    let bucket_end = &buckets[5 * k_usize..6 * k_usize];
7440
7441    let mut j = usize::try_from(n).expect("n must be non-negative");
7442    for c in (0..k_usize - 1).rev() {
7443        let l = usize::try_from(buckets[buckets_index4(c, 1)])
7444            .expect("histogram length must be non-negative");
7445        if l > 0 {
7446            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7447            if j > i {
7448                sa[i..j].fill(0);
7449            }
7450
7451            let new_j = i - l;
7452            let src_end = usize::try_from(m).expect("m must be non-negative");
7453            let src_start = src_end - l;
7454            sa.copy_within(src_start..src_end, new_j);
7455            m -= l as SaSint;
7456            j = new_j;
7457        }
7458    }
7459
7460    sa[..j].fill(0);
7461}
7462
7463/// Internal helper: place lms suffixes histogram 32s 4k.
7464#[doc(hidden)]
7465pub fn place_lms_suffixes_histogram_32s_4k(
7466    sa: &mut [SaSint],
7467    n: SaSint,
7468    k: SaSint,
7469    mut m: SaSint,
7470    buckets: &[SaSint],
7471) {
7472    let k_usize = usize::try_from(k).expect("k must be non-negative");
7473    let bucket_end = &buckets[3 * k_usize..4 * k_usize];
7474
7475    let mut j = usize::try_from(n).expect("n must be non-negative");
7476    for c in (0..k_usize - 1).rev() {
7477        let l = usize::try_from(buckets[buckets_index2(c, 1)])
7478            .expect("histogram length must be non-negative");
7479        if l > 0 {
7480            let i = usize::try_from(bucket_end[c]).expect("bucket end must be non-negative");
7481            if j > i {
7482                sa[i..j].fill(0);
7483            }
7484
7485            let new_j = i - l;
7486            let src_end = usize::try_from(m).expect("m must be non-negative");
7487            let src_start = src_end - l;
7488            sa.copy_within(src_start..src_end, new_j);
7489            m -= l as SaSint;
7490            j = new_j;
7491        }
7492    }
7493
7494    sa[..j].fill(0);
7495}
7496
7497/// Internal helper: place lms suffixes histogram 32s 2k.
7498#[doc(hidden)]
7499pub fn place_lms_suffixes_histogram_32s_2k(
7500    sa: &mut [SaSint],
7501    n: SaSint,
7502    k: SaSint,
7503    mut m: SaSint,
7504    buckets: &[SaSint],
7505) {
7506    let k_usize = usize::try_from(k).expect("k must be non-negative");
7507    let mut j = usize::try_from(n).expect("n must be non-negative");
7508
7509    if k_usize > 1 {
7510        let mut c = buckets_index2(k_usize - 2, 0) as isize;
7511        while c >= buckets_index2(0, 0) as isize {
7512            let c_usize = c as usize;
7513            let l = usize::try_from(buckets[c_usize + buckets_index2(0, 1)])
7514                .expect("histogram length must be non-negative");
7515            if l > 0 {
7516                let i =
7517                    usize::try_from(buckets[c_usize]).expect("bucket start must be non-negative");
7518                if j > i {
7519                    sa[i..j].fill(0);
7520                }
7521
7522                let new_j = i - l;
7523                let src_end = usize::try_from(m).expect("m must be non-negative");
7524                let src_start = src_end - l;
7525                sa.copy_within(src_start..src_end, new_j);
7526                m -= l as SaSint;
7527                j = new_j;
7528            }
7529            c -= buckets_index2(1, 0) as isize;
7530        }
7531    }
7532
7533    sa[..j].fill(0);
7534}
7535
7536/// Internal helper: final bwt scan left to right 8u.
7537#[doc(hidden)]
7538pub fn final_bwt_scan_left_to_right_8u(
7539    t: &[u8],
7540    sa: &mut [SaSint],
7541    induction_bucket: &mut [SaSint],
7542    omp_block_start: FastSint,
7543    omp_block_size: FastSint,
7544) {
7545    if omp_block_size <= 0 {
7546        return;
7547    }
7548
7549    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7550    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7551    for i in start..start + size {
7552        let mut p = sa[i];
7553        sa[i] = p & SAINT_MAX;
7554        if p > 0 {
7555            p -= 1;
7556            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7557            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
7558            let bucket = t[p_usize] as usize;
7559            let slot = usize::try_from(induction_bucket[bucket])
7560                .expect("bucket slot must be non-negative");
7561            sa[slot] = p
7562                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7563                    << (SAINT_BIT - 1));
7564            induction_bucket[bucket] += 1;
7565        }
7566    }
7567}
7568
7569/// Internal helper: final bwt aux scan left to right 8u.
7570#[doc(hidden)]
7571pub fn final_bwt_aux_scan_left_to_right_8u(
7572    t: &[u8],
7573    sa: &mut [SaSint],
7574    rm: SaSint,
7575    i_out: &mut [SaSint],
7576    induction_bucket: &mut [SaSint],
7577    omp_block_start: FastSint,
7578    omp_block_size: FastSint,
7579) {
7580    if omp_block_size <= 0 {
7581        return;
7582    }
7583
7584    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7585    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7586    for i in start..start + size {
7587        let mut p = sa[i];
7588        sa[i] = p & SAINT_MAX;
7589        if p > 0 {
7590            p -= 1;
7591            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7592            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
7593            let bucket = t[p_usize] as usize;
7594            let slot = usize::try_from(induction_bucket[bucket])
7595                .expect("bucket slot must be non-negative");
7596            sa[slot] = p
7597                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7598                    << (SAINT_BIT - 1));
7599            induction_bucket[bucket] += 1;
7600            if (p & rm) == 0 {
7601                let out_idx =
7602                    usize::try_from(p / (rm + 1)).expect("sample index must be non-negative");
7603                i_out[out_idx] = induction_bucket[bucket];
7604            }
7605        }
7606    }
7607}
7608
7609/// Internal helper: final sorting scan left to right 8u.
7610#[doc(hidden)]
7611pub fn final_sorting_scan_left_to_right_8u(
7612    t: &[u8],
7613    sa: &mut [SaSint],
7614    induction_bucket: &mut [SaSint],
7615    omp_block_start: FastSint,
7616    omp_block_size: FastSint,
7617) {
7618    if omp_block_size <= 0 {
7619        return;
7620    }
7621
7622    let prefetch_distance = 64usize;
7623    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7624    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7625
7626    let mut i = start;
7627    let mut j = if size > prefetch_distance + 1 {
7628        start + size - (prefetch_distance + 1)
7629    } else {
7630        start
7631    };
7632    while i < j {
7633        let mut p0 = sa[i];
7634        sa[i] = p0 ^ SAINT_MIN;
7635        if p0 > 0 {
7636            p0 -= 1;
7637            let p0_usize = p0 as usize;
7638            let bucket0 = t[p0_usize] as usize;
7639            let slot0 = induction_bucket[bucket0] as usize;
7640            sa[slot0] = p0
7641                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] < t[p0_usize]) as SaSint)
7642                    << (SAINT_BIT - 1));
7643            induction_bucket[bucket0] += 1;
7644        }
7645
7646        let mut p1 = sa[i + 1];
7647        sa[i + 1] = p1 ^ SAINT_MIN;
7648        if p1 > 0 {
7649            p1 -= 1;
7650            let p1_usize = p1 as usize;
7651            let bucket1 = t[p1_usize] as usize;
7652            let slot1 = induction_bucket[bucket1] as usize;
7653            sa[slot1] = p1
7654                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] < t[p1_usize]) as SaSint)
7655                    << (SAINT_BIT - 1));
7656            induction_bucket[bucket1] += 1;
7657        }
7658
7659        i += 2;
7660    }
7661
7662    j = start + size;
7663    while i < j {
7664        let mut p = sa[i];
7665        sa[i] = p ^ SAINT_MIN;
7666        if p > 0 {
7667            p -= 1;
7668            let p_usize = p as usize;
7669            let bucket = t[p_usize] as usize;
7670            let slot = induction_bucket[bucket] as usize;
7671            sa[slot] = p
7672                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7673                    << (SAINT_BIT - 1));
7674            induction_bucket[bucket] += 1;
7675        }
7676        i += 1;
7677    }
7678}
7679
7680/// Internal helper: final sorting scan left to right 32s.
7681#[doc(hidden)]
7682pub fn final_sorting_scan_left_to_right_32s(
7683    t: &[SaSint],
7684    sa: &mut [SaSint],
7685    induction_bucket: &mut [SaSint],
7686    omp_block_start: FastSint,
7687    omp_block_size: FastSint,
7688) {
7689    if omp_block_size <= 0 {
7690        return;
7691    }
7692
7693    let prefetch_distance: FastSint = 64;
7694    let mut i = omp_block_start;
7695    let mut j = omp_block_start + omp_block_size - 2 * prefetch_distance - 1;
7696
7697    while i < j {
7698        let i0 = i as usize;
7699        let mut p0 = sa[i0];
7700        sa[i0] = p0 ^ SAINT_MIN;
7701        if p0 > 0 {
7702            p0 -= 1;
7703            let p0u = p0 as usize;
7704            let bucket0 = t[p0u] as usize;
7705            let slot0 = induction_bucket[bucket0] as usize;
7706            sa[slot0] = p0
7707                | ((usize::from(t[p0u - usize::from(p0 > 0)] < t[p0u]) as SaSint)
7708                    << (SAINT_BIT - 1));
7709            induction_bucket[bucket0] += 1;
7710        }
7711
7712        let i1 = (i + 1) as usize;
7713        let mut p1 = sa[i1];
7714        sa[i1] = p1 ^ SAINT_MIN;
7715        if p1 > 0 {
7716            p1 -= 1;
7717            let p1u = p1 as usize;
7718            let bucket1 = t[p1u] as usize;
7719            let slot1 = induction_bucket[bucket1] as usize;
7720            sa[slot1] = p1
7721                | ((usize::from(t[p1u - usize::from(p1 > 0)] < t[p1u]) as SaSint)
7722                    << (SAINT_BIT - 1));
7723            induction_bucket[bucket1] += 1;
7724        }
7725        i += 2;
7726    }
7727
7728    j += 2 * prefetch_distance + 1;
7729    while i < j {
7730        let iu = i as usize;
7731        let mut p = sa[iu];
7732        sa[iu] = p ^ SAINT_MIN;
7733        if p > 0 {
7734            p -= 1;
7735            let pu = p as usize;
7736            let bucket = t[pu] as usize;
7737            let slot = induction_bucket[bucket] as usize;
7738            sa[slot] = p
7739                | ((usize::from(t[pu - usize::from(p > 0)] < t[pu]) as SaSint) << (SAINT_BIT - 1));
7740            induction_bucket[bucket] += 1;
7741        }
7742        i += 1;
7743    }
7744}
7745
7746/// Internal helper: final bwt scan left to right 8u block prepare.
7747#[doc(hidden)]
7748pub fn final_bwt_scan_left_to_right_8u_block_prepare(
7749    t: &[u8],
7750    sa: &mut [SaSint],
7751    k: SaSint,
7752    buckets: &mut [SaSint],
7753    cache: &mut [ThreadCache],
7754    omp_block_start: FastSint,
7755    omp_block_size: FastSint,
7756) -> FastSint {
7757    if omp_block_size <= 0 {
7758        return 0;
7759    }
7760
7761    let k_usize = usize::try_from(k).expect("k must be non-negative");
7762    buckets[..k_usize].fill(0);
7763
7764    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7765    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7766    let mut count = 0usize;
7767    for i in start..start + size {
7768        let mut p = sa[i];
7769        sa[i] = p & SAINT_MAX;
7770        if p > 0 {
7771            p -= 1;
7772            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7773            let symbol = t[p_usize] as usize;
7774            sa[i] = t[p_usize] as SaSint | SAINT_MIN;
7775            buckets[symbol] += 1;
7776            cache[count].symbol = symbol as SaSint;
7777            cache[count].index = p
7778                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7779                    << (SAINT_BIT - 1));
7780            count += 1;
7781        }
7782    }
7783
7784    count as FastSint
7785}
7786
7787/// Internal helper: final sorting scan left to right 8u block prepare.
7788#[doc(hidden)]
7789pub fn final_sorting_scan_left_to_right_8u_block_prepare(
7790    t: &[u8],
7791    sa: &mut [SaSint],
7792    k: SaSint,
7793    buckets: &mut [SaSint],
7794    cache: &mut [ThreadCache],
7795    omp_block_start: FastSint,
7796    omp_block_size: FastSint,
7797) -> FastSint {
7798    if omp_block_size <= 0 {
7799        return 0;
7800    }
7801
7802    let k_usize = usize::try_from(k).expect("k must be non-negative");
7803    buckets[..k_usize].fill(0);
7804
7805    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7806    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7807    let mut count = 0usize;
7808    for i in start..start + size {
7809        let mut p = sa[i];
7810        sa[i] = p ^ SAINT_MIN;
7811        if p > 0 {
7812            p -= 1;
7813            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
7814            let symbol = t[p_usize] as usize;
7815            buckets[symbol] += 1;
7816            cache[count].symbol = symbol as SaSint;
7817            cache[count].index = p
7818                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7819                    << (SAINT_BIT - 1));
7820            count += 1;
7821        }
7822    }
7823
7824    count as FastSint
7825}
7826
7827/// Internal helper: final order scan left to right 8u block place.
7828#[doc(hidden)]
7829pub fn final_order_scan_left_to_right_8u_block_place(
7830    sa: &mut [SaSint],
7831    buckets: &mut [SaSint],
7832    cache: &[ThreadCache],
7833    count: FastSint,
7834) {
7835    if count <= 0 {
7836        return;
7837    }
7838
7839    let count_usize = usize::try_from(count).expect("count must be non-negative");
7840    for entry in &cache[..count_usize] {
7841        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
7842        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
7843        sa[slot] = entry.index;
7844        buckets[symbol] += 1;
7845    }
7846}
7847
7848/// Internal helper: final bwt aux scan left to right 8u block place.
7849#[doc(hidden)]
7850pub fn final_bwt_aux_scan_left_to_right_8u_block_place(
7851    sa: &mut [SaSint],
7852    rm: SaSint,
7853    i_out: &mut [SaSint],
7854    buckets: &mut [SaSint],
7855    cache: &[ThreadCache],
7856    count: FastSint,
7857) {
7858    if count <= 0 {
7859        return;
7860    }
7861
7862    let count_usize = usize::try_from(count).expect("count must be non-negative");
7863    for entry in &cache[..count_usize] {
7864        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
7865        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
7866        sa[slot] = entry.index;
7867        buckets[symbol] += 1;
7868        if (entry.index & rm) == 0 {
7869            let sample_index = usize::try_from((entry.index & SAINT_MAX) / (rm + 1))
7870                .expect("sample index must be non-negative");
7871            i_out[sample_index] = buckets[symbol];
7872        }
7873    }
7874}
7875
7876/// Internal helper: final sorting scan left to right 32s block gather.
7877#[doc(hidden)]
7878pub fn final_sorting_scan_left_to_right_32s_block_gather(
7879    t: &[SaSint],
7880    sa: &mut [SaSint],
7881    cache: &mut [ThreadCache],
7882    omp_block_start: FastSint,
7883    omp_block_size: FastSint,
7884) {
7885    if omp_block_size <= 0 {
7886        return;
7887    }
7888    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
7889    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
7890    for offset in 0..size {
7891        let i = start + offset;
7892        let mut symbol = SAINT_MIN;
7893        let mut p = sa[i];
7894        sa[i] = p ^ SAINT_MIN;
7895        if p > 0 {
7896            p -= 1;
7897            let p_usize = p as usize;
7898            cache[offset].index = p
7899                | ((usize::from(t[p_usize - usize::from(p > 0)] < t[p_usize]) as SaSint)
7900                    << (SAINT_BIT - 1));
7901            symbol = t[p_usize];
7902        }
7903        cache[offset].symbol = symbol;
7904    }
7905}
7906
7907/// Internal helper: final sorting scan left to right 32s block sort.
7908#[doc(hidden)]
7909pub fn final_sorting_scan_left_to_right_32s_block_sort(
7910    t: &[SaSint],
7911    induction_bucket: &mut [SaSint],
7912    cache: &mut [ThreadCache],
7913    omp_block_start: FastSint,
7914    omp_block_size: FastSint,
7915) {
7916    if omp_block_size <= 0 {
7917        return;
7918    }
7919    let prefetch_distance = 64usize;
7920    let start = omp_block_start as usize;
7921    let block_end = start + omp_block_size as usize;
7922    let mut i = start;
7923    let mut j = start + (omp_block_size as usize).saturating_sub(prefetch_distance + 1);
7924
7925    while i < j {
7926        let ci = i - start;
7927        let v0 = cache[ci].symbol;
7928        if v0 >= 0 {
7929            let bucket_index0 = v0 as usize;
7930            cache[ci].symbol = induction_bucket[bucket_index0];
7931            induction_bucket[bucket_index0] += 1;
7932            if cache[ci].symbol < block_end as SaSint {
7933                let ni = cache[ci].symbol as usize;
7934                let cni = ni - start;
7935                let mut np = cache[ci].index;
7936                cache[ci].index = np ^ SAINT_MIN;
7937                if np > 0 {
7938                    np -= 1;
7939                    let np_usize = np as usize;
7940                    cache[cni].index = np
7941                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
7942                            as SaSint)
7943                            << (SAINT_BIT - 1));
7944                    cache[cni].symbol = t[np_usize];
7945                }
7946            }
7947        }
7948
7949        let i1 = i + 1;
7950        let ci1 = i1 - start;
7951        let v1 = cache[ci1].symbol;
7952        if v1 >= 0 {
7953            let bucket_index1 = v1 as usize;
7954            cache[ci1].symbol = induction_bucket[bucket_index1];
7955            induction_bucket[bucket_index1] += 1;
7956            if cache[ci1].symbol < block_end as SaSint {
7957                let ni = cache[ci1].symbol as usize;
7958                let cni = ni - start;
7959                let mut np = cache[ci1].index;
7960                cache[ci1].index = np ^ SAINT_MIN;
7961                if np > 0 {
7962                    np -= 1;
7963                    let np_usize = np as usize;
7964                    cache[cni].index = np
7965                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
7966                            as SaSint)
7967                            << (SAINT_BIT - 1));
7968                    cache[cni].symbol = t[np_usize];
7969                }
7970            }
7971        }
7972
7973        i += 2;
7974    }
7975
7976    j = block_end;
7977    while i < j {
7978        let ci = i - start;
7979        let v = cache[ci].symbol;
7980        if v >= 0 {
7981            let bucket_index = v as usize;
7982            cache[ci].symbol = induction_bucket[bucket_index];
7983            induction_bucket[bucket_index] += 1;
7984            if cache[ci].symbol < block_end as SaSint {
7985                let ni = cache[ci].symbol as usize;
7986                let cni = ni - start;
7987                let mut np = cache[ci].index;
7988                cache[ci].index = np ^ SAINT_MIN;
7989                if np > 0 {
7990                    np -= 1;
7991                    let np_usize = np as usize;
7992                    cache[cni].index = np
7993                        | ((usize::from(t[np_usize - usize::from(np > 0)] < t[np_usize])
7994                            as SaSint)
7995                            << (SAINT_BIT - 1));
7996                    cache[cni].symbol = t[np_usize];
7997                }
7998            }
7999        }
8000        i += 1;
8001    }
8002}
8003
8004/// Internal helper: final bwt scan left to right 8u block (OpenMP variant).
8005#[doc(hidden)]
8006pub fn final_bwt_scan_left_to_right_8u_block_omp(
8007    t: &[u8],
8008    sa: &mut [SaSint],
8009    k: SaSint,
8010    induction_bucket: &mut [SaSint],
8011    block_start: FastSint,
8012    block_size: FastSint,
8013    threads: SaSint,
8014    thread_state: &mut [ThreadState],
8015) {
8016    if block_size <= 0 {
8017        return;
8018    }
8019
8020    let k_usize = usize::try_from(k).expect("k must be non-negative");
8021    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8022    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8023        usize::try_from(threads)
8024            .expect("threads must be non-negative")
8025            .min(thread_state.len())
8026            .max(1)
8027    } else {
8028        1
8029    };
8030
8031    if omp_num_threads == 1 {
8032        final_bwt_scan_left_to_right_8u(t, sa, induction_bucket, block_start, block_size);
8033        return;
8034    }
8035
8036    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8037    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8038    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8039        let relative_start = thread_num * omp_block_stride;
8040        let size = if thread_num + 1 < omp_num_threads {
8041            omp_block_stride
8042        } else {
8043            block_size_usize - relative_start
8044        };
8045        state.count = final_bwt_scan_left_to_right_8u_block_prepare(
8046            t,
8047            sa,
8048            k,
8049            &mut state.buckets,
8050            &mut state.cache,
8051            (block_start_usize + relative_start) as FastSint,
8052            size as FastSint,
8053        );
8054    }
8055
8056    for state in thread_state.iter_mut().take(omp_num_threads) {
8057        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8058            let a = *bucket;
8059            let b = state.buckets[c];
8060            *bucket = a + b;
8061            state.buckets[c] = a;
8062        }
8063    }
8064
8065    for state in thread_state.iter_mut().take(omp_num_threads) {
8066        final_order_scan_left_to_right_8u_block_place(
8067            sa,
8068            &mut state.buckets,
8069            &state.cache,
8070            state.count,
8071        );
8072    }
8073}
8074
8075/// Internal helper: final bwt aux scan left to right 8u block (OpenMP variant).
8076#[doc(hidden)]
8077pub fn final_bwt_aux_scan_left_to_right_8u_block_omp(
8078    t: &[u8],
8079    sa: &mut [SaSint],
8080    k: SaSint,
8081    rm: SaSint,
8082    i_out: &mut [SaSint],
8083    induction_bucket: &mut [SaSint],
8084    block_start: FastSint,
8085    block_size: FastSint,
8086    threads: SaSint,
8087    thread_state: &mut [ThreadState],
8088) {
8089    if block_size <= 0 {
8090        return;
8091    }
8092
8093    let k_usize = usize::try_from(k).expect("k must be non-negative");
8094    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8095    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8096        usize::try_from(threads)
8097            .expect("threads must be non-negative")
8098            .min(thread_state.len())
8099            .max(1)
8100    } else {
8101        1
8102    };
8103
8104    if omp_num_threads == 1 {
8105        final_bwt_aux_scan_left_to_right_8u(
8106            t,
8107            sa,
8108            rm,
8109            i_out,
8110            induction_bucket,
8111            block_start,
8112            block_size,
8113        );
8114        return;
8115    }
8116
8117    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8118    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8119    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8120        let relative_start = thread_num * omp_block_stride;
8121        let size = if thread_num + 1 < omp_num_threads {
8122            omp_block_stride
8123        } else {
8124            block_size_usize - relative_start
8125        };
8126        state.count = final_bwt_scan_left_to_right_8u_block_prepare(
8127            t,
8128            sa,
8129            k,
8130            &mut state.buckets,
8131            &mut state.cache,
8132            (block_start_usize + relative_start) as FastSint,
8133            size as FastSint,
8134        );
8135    }
8136
8137    for state in thread_state.iter_mut().take(omp_num_threads) {
8138        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8139            let a = *bucket;
8140            let b = state.buckets[c];
8141            *bucket = a + b;
8142            state.buckets[c] = a;
8143        }
8144    }
8145
8146    for state in thread_state.iter_mut().take(omp_num_threads) {
8147        final_bwt_aux_scan_left_to_right_8u_block_place(
8148            sa,
8149            rm,
8150            i_out,
8151            &mut state.buckets,
8152            &state.cache,
8153            state.count,
8154        );
8155    }
8156}
8157
8158/// Internal helper: final sorting scan left to right 8u block (OpenMP variant).
8159#[doc(hidden)]
8160pub fn final_sorting_scan_left_to_right_8u_block_omp(
8161    t: &[u8],
8162    sa: &mut [SaSint],
8163    k: SaSint,
8164    induction_bucket: &mut [SaSint],
8165    block_start: FastSint,
8166    block_size: FastSint,
8167    threads: SaSint,
8168    thread_state: &mut [ThreadState],
8169) {
8170    if block_size <= 0 {
8171        return;
8172    }
8173
8174    let k_usize = usize::try_from(k).expect("k must be non-negative");
8175    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8176    let omp_num_threads = if threads > 1 && block_size_usize >= 64 * k_usize.max(256) {
8177        usize::try_from(threads)
8178            .expect("threads must be non-negative")
8179            .min(thread_state.len())
8180            .max(1)
8181    } else {
8182        1
8183    };
8184
8185    if omp_num_threads == 1 {
8186        final_sorting_scan_left_to_right_8u(t, sa, induction_bucket, block_start, block_size);
8187        return;
8188    }
8189
8190    let block_start_usize = usize::try_from(block_start).expect("block_start must be non-negative");
8191    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8192    for (thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
8193        let relative_start = thread_num * omp_block_stride;
8194        let size = if thread_num + 1 < omp_num_threads {
8195            omp_block_stride
8196        } else {
8197            block_size_usize - relative_start
8198        };
8199        state.count = final_sorting_scan_left_to_right_8u_block_prepare(
8200            t,
8201            sa,
8202            k,
8203            &mut state.buckets,
8204            &mut state.cache,
8205            (block_start_usize + relative_start) as FastSint,
8206            size as FastSint,
8207        );
8208    }
8209
8210    for state in thread_state.iter_mut().take(omp_num_threads) {
8211        for (c, bucket) in induction_bucket.iter_mut().take(k_usize).enumerate() {
8212            let a = *bucket;
8213            let b = state.buckets[c];
8214            *bucket = a + b;
8215            state.buckets[c] = a;
8216        }
8217    }
8218
8219    for state in thread_state.iter_mut().take(omp_num_threads) {
8220        final_order_scan_left_to_right_8u_block_place(
8221            sa,
8222            &mut state.buckets,
8223            &state.cache,
8224            state.count,
8225        );
8226    }
8227}
8228
8229/// Internal helper: final sorting scan left to right 32s block (OpenMP variant).
8230#[doc(hidden)]
8231pub fn final_sorting_scan_left_to_right_32s_block_omp(
8232    t: &[SaSint],
8233    sa: &mut [SaSint],
8234    buckets: &mut [SaSint],
8235    cache: &mut [ThreadCache],
8236    block_start: FastSint,
8237    block_size: FastSint,
8238    threads: SaSint,
8239) {
8240    if threads <= 1 || block_size < 16_384 {
8241        final_sorting_scan_left_to_right_32s(t, sa, buckets, block_start, block_size);
8242        return;
8243    }
8244
8245    final_sorting_scan_left_to_right_32s_block_gather(t, sa, cache, block_start, block_size);
8246    final_sorting_scan_left_to_right_32s_block_sort(t, buckets, cache, block_start, block_size);
8247    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
8248    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
8249    let omp_num_threads = threads_usize.min(block_size_usize);
8250    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
8251    {
8252        let sa_ptr = SyncMutPtr::new(sa);
8253        let cache_ptr = SyncMutPtr::new(cache);
8254        run_rayon_with_threads(omp_num_threads, || {
8255            (0..omp_num_threads)
8256                .into_par_iter()
8257                .for_each(|omp_thread_num| {
8258                    let omp_block_start = omp_thread_num * omp_block_stride;
8259                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
8260                        omp_block_stride
8261                    } else {
8262                        block_size_usize - omp_block_start
8263                    };
8264                    let sa = unsafe { sa_ptr.as_slice() };
8265                    let cache = unsafe { cache_ptr.as_slice() };
8266                    compact_and_place_cached_suffixes(
8267                        sa,
8268                        cache,
8269                        omp_block_start as FastSint,
8270                        omp_block_size as FastSint,
8271                    );
8272                });
8273        });
8274    }
8275}
8276
8277/// Internal helper: final bwt scan left to right 8u (OpenMP variant).
8278#[doc(hidden)]
8279pub fn final_bwt_scan_left_to_right_8u_omp(
8280    t: &[u8],
8281    sa: &mut [SaSint],
8282    n: FastSint,
8283    k: SaSint,
8284    induction_bucket: &mut [SaSint],
8285    threads: SaSint,
8286    thread_state: &mut [ThreadState],
8287) {
8288    let n_usize = usize::try_from(n).expect("n must be non-negative");
8289    let last = n_usize - 1;
8290    let bucket = t[last] as usize;
8291    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8292    sa[slot] =
8293        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8294    induction_bucket[bucket] += 1;
8295
8296    if threads == 1 || n < 65_536 {
8297        final_bwt_scan_left_to_right_8u(t, sa, induction_bucket, 0, n);
8298        return;
8299    }
8300
8301    let mut block_start = 0usize;
8302    while block_start < n_usize {
8303        if sa[block_start] == 0 {
8304            block_start += 1;
8305        } else {
8306            let threads_usize = usize::try_from(threads)
8307                .expect("threads must be non-negative")
8308                .min(thread_state.len())
8309                .max(1);
8310            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8311            let block_max_end = (block_start + max_span).min(n_usize);
8312            let mut block_end = block_start + 1;
8313            while block_end < block_max_end && sa[block_end] != 0 {
8314                block_end += 1;
8315            }
8316            let size = block_end - block_start;
8317
8318            if size < 32 {
8319                final_bwt_scan_left_to_right_8u(
8320                    t,
8321                    sa,
8322                    induction_bucket,
8323                    block_start as FastSint,
8324                    size as FastSint,
8325                );
8326            } else {
8327                final_bwt_scan_left_to_right_8u_block_omp(
8328                    t,
8329                    sa,
8330                    k,
8331                    induction_bucket,
8332                    block_start as FastSint,
8333                    size as FastSint,
8334                    threads,
8335                    thread_state,
8336                );
8337            }
8338            block_start = block_end;
8339        }
8340    }
8341}
8342
8343/// Internal helper: final bwt aux scan left to right 8u (OpenMP variant).
8344#[doc(hidden)]
8345pub fn final_bwt_aux_scan_left_to_right_8u_omp(
8346    t: &[u8],
8347    sa: &mut [SaSint],
8348    n: FastSint,
8349    k: SaSint,
8350    rm: SaSint,
8351    i_out: &mut [SaSint],
8352    induction_bucket: &mut [SaSint],
8353    threads: SaSint,
8354    thread_state: &mut [ThreadState],
8355) {
8356    let n_usize = usize::try_from(n).expect("n must be non-negative");
8357    let last = n_usize - 1;
8358    let bucket = t[last] as usize;
8359    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8360    sa[slot] =
8361        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8362    induction_bucket[bucket] += 1;
8363    if (((n as SaSint) - 1) & rm) == 0 {
8364        i_out[last / usize::try_from(rm + 1).expect("rm must allow positive step")] =
8365            induction_bucket[bucket];
8366    }
8367
8368    if threads == 1 || n < 65_536 {
8369        final_bwt_aux_scan_left_to_right_8u(t, sa, rm, i_out, induction_bucket, 0, n);
8370        return;
8371    }
8372
8373    let mut block_start = 0usize;
8374    while block_start < n_usize {
8375        if sa[block_start] == 0 {
8376            block_start += 1;
8377        } else {
8378            let threads_usize = usize::try_from(threads)
8379                .expect("threads must be non-negative")
8380                .min(thread_state.len())
8381                .max(1);
8382            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8383            let block_max_end = (block_start + max_span).min(n_usize);
8384            let mut block_end = block_start + 1;
8385            while block_end < block_max_end && sa[block_end] != 0 {
8386                block_end += 1;
8387            }
8388            let size = block_end - block_start;
8389
8390            if size < 32 {
8391                final_bwt_aux_scan_left_to_right_8u(
8392                    t,
8393                    sa,
8394                    rm,
8395                    i_out,
8396                    induction_bucket,
8397                    block_start as FastSint,
8398                    size as FastSint,
8399                );
8400            } else {
8401                final_bwt_aux_scan_left_to_right_8u_block_omp(
8402                    t,
8403                    sa,
8404                    k,
8405                    rm,
8406                    i_out,
8407                    induction_bucket,
8408                    block_start as FastSint,
8409                    size as FastSint,
8410                    threads,
8411                    thread_state,
8412                );
8413            }
8414            block_start = block_end;
8415        }
8416    }
8417}
8418
8419/// Internal helper: final sorting scan left to right 8u (OpenMP variant).
8420#[doc(hidden)]
8421pub fn final_sorting_scan_left_to_right_8u_omp(
8422    t: &[u8],
8423    sa: &mut [SaSint],
8424    n: FastSint,
8425    k: SaSint,
8426    induction_bucket: &mut [SaSint],
8427    threads: SaSint,
8428    thread_state: &mut [ThreadState],
8429) {
8430    let n_usize = usize::try_from(n).expect("n must be non-negative");
8431    let last = n_usize - 1;
8432    let bucket = t[last] as usize;
8433    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8434    sa[slot] =
8435        (n as SaSint - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8436    induction_bucket[bucket] += 1;
8437
8438    if threads == 1 || n < 65_536 {
8439        final_sorting_scan_left_to_right_8u(t, sa, induction_bucket, 0, n);
8440        return;
8441    }
8442
8443    let mut block_start = 0usize;
8444    while block_start < n_usize {
8445        if sa[block_start] == 0 {
8446            block_start += 1;
8447        } else {
8448            let threads_usize = usize::try_from(threads)
8449                .expect("threads must be non-negative")
8450                .min(thread_state.len())
8451                .max(1);
8452            let max_span = threads_usize * (LIBSAIS_PER_THREAD_CACHE_SIZE - 16 * threads_usize);
8453            let block_max_end = (block_start + max_span).min(n_usize);
8454            let mut block_end = block_start + 1;
8455            while block_end < block_max_end && sa[block_end] != 0 {
8456                block_end += 1;
8457            }
8458            let size = block_end - block_start;
8459
8460            if size < 32 {
8461                final_sorting_scan_left_to_right_8u(
8462                    t,
8463                    sa,
8464                    induction_bucket,
8465                    block_start as FastSint,
8466                    size as FastSint,
8467                );
8468            } else {
8469                final_sorting_scan_left_to_right_8u_block_omp(
8470                    t,
8471                    sa,
8472                    k,
8473                    induction_bucket,
8474                    block_start as FastSint,
8475                    size as FastSint,
8476                    threads,
8477                    thread_state,
8478                );
8479            }
8480            block_start = block_end;
8481        }
8482    }
8483}
8484
8485/// Internal helper: final sorting scan left to right 32s (OpenMP variant).
8486#[doc(hidden)]
8487pub fn final_sorting_scan_left_to_right_32s_omp(
8488    t: &[SaSint],
8489    sa: &mut [SaSint],
8490    n: SaSint,
8491    induction_bucket: &mut [SaSint],
8492    threads: SaSint,
8493    thread_state: &mut [ThreadState],
8494) {
8495    let n_usize = usize::try_from(n).expect("n must be non-negative");
8496    let last = n_usize - 1;
8497    let bucket = usize::try_from(t[last]).expect("bucket symbol must be non-negative");
8498    let slot = usize::try_from(induction_bucket[bucket]).expect("bucket slot must be non-negative");
8499    sa[slot] = (n - 1) | ((usize::from(t[last - 1] < t[last]) as SaSint) << (SAINT_BIT - 1));
8500    induction_bucket[bucket] += 1;
8501
8502    if threads == 1 || n < 65_536 {
8503        final_sorting_scan_left_to_right_32s(t, sa, induction_bucket, 0, n as FastSint);
8504        return;
8505    }
8506
8507    if thread_state.is_empty() {
8508        final_sorting_scan_left_to_right_32s(t, sa, induction_bucket, 0, n as FastSint);
8509        return;
8510    }
8511
8512    let threads_usize = usize::try_from(threads)
8513        .expect("threads must be non-negative")
8514        .max(1);
8515    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
8516    let mut block_start = 0usize;
8517    while block_start < n_usize {
8518        let block_end = (block_start + threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE).min(n_usize);
8519        final_sorting_scan_left_to_right_32s_block_omp(
8520            t,
8521            sa,
8522            induction_bucket,
8523            &mut cache,
8524            block_start as FastSint,
8525            (block_end - block_start) as FastSint,
8526            threads,
8527        );
8528        block_start = block_end;
8529    }
8530}
8531
8532/// Internal helper: final bwt scan right to left 8u.
8533#[doc(hidden)]
8534pub fn final_bwt_scan_right_to_left_8u(
8535    t: &[u8],
8536    sa: &mut [SaSint],
8537    induction_bucket: &mut [SaSint],
8538    omp_block_start: FastSint,
8539    omp_block_size: FastSint,
8540) -> SaSint {
8541    if omp_block_size <= 0 {
8542        return -1;
8543    }
8544
8545    let mut index = -1;
8546
8547    let start =
8548        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
8549    let mut i = omp_block_start + omp_block_size - 1;
8550    let mut j = start + 1;
8551    while i >= j {
8552        let i0 = usize::try_from(i).expect("loop index must be non-negative");
8553        let i1 = usize::try_from(i - 1).expect("loop index must be non-negative");
8554
8555        let mut p0 = sa[i0];
8556        if p0 == 0 {
8557            index = i0 as SaSint;
8558        }
8559        sa[i0] = p0 & SAINT_MAX;
8560        if p0 > 0 {
8561            p0 -= 1;
8562            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
8563            let c0 = t[p0_usize - usize::from(p0 > 0)] as SaSint;
8564            let c1 = t[p0_usize] as SaSint;
8565            sa[i0] = c1;
8566            induction_bucket[c1 as usize] -= 1;
8567            let slot = usize::try_from(induction_bucket[c1 as usize])
8568                .expect("bucket slot must be non-negative");
8569            let marked = c0 | SAINT_MIN;
8570            sa[slot] = if c0 <= c1 { p0 } else { marked };
8571        }
8572
8573        let mut p1 = sa[i1];
8574        if p1 == 0 {
8575            index = i1 as SaSint;
8576        }
8577        sa[i1] = p1 & SAINT_MAX;
8578        if p1 > 0 {
8579            p1 -= 1;
8580            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
8581            let c0 = t[p1_usize - usize::from(p1 > 0)] as SaSint;
8582            let c1 = t[p1_usize] as SaSint;
8583            sa[i1] = c1;
8584            induction_bucket[c1 as usize] -= 1;
8585            let slot = usize::try_from(induction_bucket[c1 as usize])
8586                .expect("bucket slot must be non-negative");
8587            let marked = c0 | SAINT_MIN;
8588            sa[slot] = if c0 <= c1 { p1 } else { marked };
8589        }
8590
8591        i -= 2;
8592    }
8593
8594    j -= 1;
8595    while i >= j {
8596        let idx = usize::try_from(i).expect("loop index must be non-negative");
8597        let mut p = sa[idx];
8598        if p == 0 {
8599            index = idx as SaSint;
8600        }
8601        sa[idx] = p & SAINT_MAX;
8602        if p > 0 {
8603            p -= 1;
8604            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8605            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8606            let c1 = t[p_usize] as SaSint;
8607            sa[idx] = c1;
8608            induction_bucket[c1 as usize] -= 1;
8609            let slot = usize::try_from(induction_bucket[c1 as usize])
8610                .expect("bucket slot must be non-negative");
8611            let marked = c0 | SAINT_MIN;
8612            sa[slot] = if c0 <= c1 { p } else { marked };
8613        }
8614
8615        i -= 1;
8616    }
8617
8618    index
8619}
8620
8621/// Internal helper: final bwt aux scan right to left 8u.
8622#[doc(hidden)]
8623pub fn final_bwt_aux_scan_right_to_left_8u(
8624    t: &[u8],
8625    sa: &mut [SaSint],
8626    rm: SaSint,
8627    i_out: &mut [SaSint],
8628    induction_bucket: &mut [SaSint],
8629    omp_block_start: FastSint,
8630    omp_block_size: FastSint,
8631) {
8632    if omp_block_size <= 0 {
8633        return;
8634    }
8635
8636    let start =
8637        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
8638    let mut i = omp_block_start + omp_block_size - 1;
8639    let mut j = start + 1;
8640    while i >= j {
8641        let i0 = usize::try_from(i).expect("loop index must be non-negative");
8642        let i1 = usize::try_from(i - 1).expect("loop index must be non-negative");
8643
8644        let mut p0 = sa[i0];
8645        sa[i0] = p0 & SAINT_MAX;
8646        if p0 > 0 {
8647            p0 -= 1;
8648            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
8649            let c0 = t[p0_usize - usize::from(p0 > 0)] as SaSint;
8650            let c1 = t[p0_usize] as SaSint;
8651            sa[i0] = c1;
8652            induction_bucket[c1 as usize] -= 1;
8653            let slot = usize::try_from(induction_bucket[c1 as usize])
8654                .expect("bucket slot must be non-negative");
8655            let marked = c0 | SAINT_MIN;
8656            sa[slot] = if c0 <= c1 { p0 } else { marked };
8657            if (p0 & rm) == 0 {
8658                let out_idx =
8659                    usize::try_from(p0 / (rm + 1)).expect("sample index must be non-negative");
8660                i_out[out_idx] = induction_bucket[t[p0_usize] as usize] + 1;
8661            }
8662        }
8663
8664        let mut p1 = sa[i1];
8665        sa[i1] = p1 & SAINT_MAX;
8666        if p1 > 0 {
8667            p1 -= 1;
8668            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
8669            let c0 = t[p1_usize - usize::from(p1 > 0)] as SaSint;
8670            let c1 = t[p1_usize] as SaSint;
8671            sa[i1] = c1;
8672            induction_bucket[c1 as usize] -= 1;
8673            let slot = usize::try_from(induction_bucket[c1 as usize])
8674                .expect("bucket slot must be non-negative");
8675            let marked = c0 | SAINT_MIN;
8676            sa[slot] = if c0 <= c1 { p1 } else { marked };
8677            if (p1 & rm) == 0 {
8678                let out_idx =
8679                    usize::try_from(p1 / (rm + 1)).expect("sample index must be non-negative");
8680                i_out[out_idx] = induction_bucket[t[p1_usize] as usize] + 1;
8681            }
8682        }
8683
8684        i -= 2;
8685    }
8686
8687    j -= 1;
8688    while i >= j {
8689        let idx = usize::try_from(i).expect("loop index must be non-negative");
8690        let mut p = sa[idx];
8691        sa[idx] = p & SAINT_MAX;
8692        if p > 0 {
8693            p -= 1;
8694            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8695            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8696            let c1 = t[p_usize] as SaSint;
8697            sa[idx] = c1;
8698            induction_bucket[c1 as usize] -= 1;
8699            let slot = usize::try_from(induction_bucket[c1 as usize])
8700                .expect("bucket slot must be non-negative");
8701            let marked = c0 | SAINT_MIN;
8702            sa[slot] = if c0 <= c1 { p } else { marked };
8703            if (p & rm) == 0 {
8704                let out_idx =
8705                    usize::try_from(p / (rm + 1)).expect("sample index must be non-negative");
8706                i_out[out_idx] = induction_bucket[t[p_usize] as usize] + 1;
8707            }
8708        }
8709
8710        i -= 1;
8711    }
8712}
8713
8714/// Internal helper: final sorting scan right to left 8u.
8715#[doc(hidden)]
8716pub fn final_sorting_scan_right_to_left_8u(
8717    t: &[u8],
8718    sa: &mut [SaSint],
8719    induction_bucket: &mut [SaSint],
8720    omp_block_start: FastSint,
8721    omp_block_size: FastSint,
8722) {
8723    if omp_block_size <= 0 {
8724        return;
8725    }
8726
8727    let prefetch_distance = 64usize;
8728    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8729    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8730    let mut i = start + size - 1;
8731    let mut j = start + prefetch_distance + 1;
8732
8733    while i >= j {
8734        let mut p0 = sa[i];
8735        sa[i] = p0 & SAINT_MAX;
8736        if p0 > 0 {
8737            p0 -= 1;
8738            let p0_usize = p0 as usize;
8739            let bucket0 = t[p0_usize] as usize;
8740            induction_bucket[bucket0] -= 1;
8741            let slot0 = induction_bucket[bucket0] as usize;
8742            sa[slot0] = p0
8743                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] > t[p0_usize]) as SaSint)
8744                    << (SAINT_BIT - 1));
8745        }
8746
8747        let mut p1 = sa[i - 1];
8748        sa[i - 1] = p1 & SAINT_MAX;
8749        if p1 > 0 {
8750            p1 -= 1;
8751            let p1_usize = p1 as usize;
8752            let bucket1 = t[p1_usize] as usize;
8753            induction_bucket[bucket1] -= 1;
8754            let slot1 = induction_bucket[bucket1] as usize;
8755            sa[slot1] = p1
8756                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] > t[p1_usize]) as SaSint)
8757                    << (SAINT_BIT - 1));
8758        }
8759
8760        i -= 2;
8761    }
8762
8763    j -= prefetch_distance + 1;
8764    while i >= j {
8765        let mut p = sa[i];
8766        sa[i] = p & SAINT_MAX;
8767        if p > 0 {
8768            p -= 1;
8769            let p_usize = p as usize;
8770            let bucket = t[p_usize] as usize;
8771            induction_bucket[bucket] -= 1;
8772            let slot = induction_bucket[bucket] as usize;
8773            sa[slot] = p
8774                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
8775                    << (SAINT_BIT - 1));
8776        }
8777
8778        if i == 0 {
8779            break;
8780        }
8781        i -= 1;
8782    }
8783}
8784
8785/// Internal helper: final gsa scan right to left 8u.
8786#[doc(hidden)]
8787pub fn final_gsa_scan_right_to_left_8u(
8788    t: &[u8],
8789    sa: &mut [SaSint],
8790    induction_bucket: &mut [SaSint],
8791    omp_block_start: FastSint,
8792    omp_block_size: FastSint,
8793) {
8794    if omp_block_size <= 0 {
8795        return;
8796    }
8797
8798    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8799    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8800    let mut i = start + size;
8801    while i > start {
8802        i -= 1;
8803        let mut p = sa[i];
8804        sa[i] = p & SAINT_MAX;
8805        if p > 0 {
8806            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8807            if t[p_usize - 1] > 0 {
8808                p -= 1;
8809                let bucket =
8810                    t[usize::try_from(p).expect("suffix index must be non-negative")] as usize;
8811                induction_bucket[bucket] -= 1;
8812                let slot = usize::try_from(induction_bucket[bucket])
8813                    .expect("bucket slot must be non-negative");
8814                let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8815                sa[slot] = p
8816                    | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
8817                        << (SAINT_BIT - 1));
8818            }
8819        }
8820    }
8821}
8822
8823/// Internal helper: final sorting scan right to left 32s.
8824#[doc(hidden)]
8825pub fn final_sorting_scan_right_to_left_32s(
8826    t: &[SaSint],
8827    sa: &mut [SaSint],
8828    induction_bucket: &mut [SaSint],
8829    omp_block_start: FastSint,
8830    omp_block_size: FastSint,
8831) {
8832    if omp_block_size <= 0 {
8833        return;
8834    }
8835
8836    let prefetch_distance: FastSint = 64;
8837    let mut i = omp_block_start + omp_block_size - 1;
8838    let mut j = omp_block_start + 2 * prefetch_distance + 1;
8839
8840    while i >= j {
8841        let i0 = i as usize;
8842        let mut p0 = sa[i0];
8843        sa[i0] = p0 & SAINT_MAX;
8844        if p0 > 0 {
8845            p0 -= 1;
8846            let p0u = p0 as usize;
8847            let bucket0 = t[p0u] as usize;
8848            induction_bucket[bucket0] -= 1;
8849            let slot0 = induction_bucket[bucket0] as usize;
8850            sa[slot0] = p0
8851                | ((usize::from(t[p0u - usize::from(p0 > 0)] > t[p0u]) as SaSint)
8852                    << (SAINT_BIT - 1));
8853        }
8854
8855        let i1 = (i - 1) as usize;
8856        let mut p1 = sa[i1];
8857        sa[i1] = p1 & SAINT_MAX;
8858        if p1 > 0 {
8859            p1 -= 1;
8860            let p1u = p1 as usize;
8861            let bucket1 = t[p1u] as usize;
8862            induction_bucket[bucket1] -= 1;
8863            let slot1 = induction_bucket[bucket1] as usize;
8864            sa[slot1] = p1
8865                | ((usize::from(t[p1u - usize::from(p1 > 0)] > t[p1u]) as SaSint)
8866                    << (SAINT_BIT - 1));
8867        }
8868        i -= 2;
8869    }
8870
8871    j -= 2 * prefetch_distance + 1;
8872    while i >= j {
8873        let iu = i as usize;
8874        let mut p = sa[iu];
8875        sa[iu] = p & SAINT_MAX;
8876        if p > 0 {
8877            p -= 1;
8878            let pu = p as usize;
8879            let bucket = t[pu] as usize;
8880            induction_bucket[bucket] -= 1;
8881            let slot = induction_bucket[bucket] as usize;
8882            sa[slot] = p
8883                | ((usize::from(t[pu - usize::from(p > 0)] > t[pu]) as SaSint) << (SAINT_BIT - 1));
8884        }
8885        i -= 1;
8886    }
8887}
8888
8889/// Internal helper: final bwt scan right to left 8u block prepare.
8890#[doc(hidden)]
8891pub fn final_bwt_scan_right_to_left_8u_block_prepare(
8892    t: &[u8],
8893    sa: &mut [SaSint],
8894    k: SaSint,
8895    buckets: &mut [SaSint],
8896    cache: &mut [ThreadCache],
8897    omp_block_start: FastSint,
8898    omp_block_size: FastSint,
8899) -> FastSint {
8900    if omp_block_size <= 0 {
8901        return 0;
8902    }
8903    let k_usize = usize::try_from(k).expect("k must be non-negative");
8904    buckets[..k_usize].fill(0);
8905    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8906    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8907    let mut count = 0usize;
8908    let mut i = start + size;
8909    while i > start {
8910        i -= 1;
8911        let mut p = sa[i];
8912        sa[i] = p & SAINT_MAX;
8913        if p > 0 {
8914            p -= 1;
8915            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8916            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8917            let c1 = t[p_usize] as SaSint;
8918            sa[i] = c1;
8919            buckets[c1 as usize] += 1;
8920            cache[count].symbol = c1;
8921            cache[count].index = if c0 <= c1 { p } else { c0 | SAINT_MIN };
8922            count += 1;
8923        }
8924    }
8925    count as FastSint
8926}
8927
8928/// Internal helper: final bwt aux scan right to left 8u block prepare.
8929#[doc(hidden)]
8930pub fn final_bwt_aux_scan_right_to_left_8u_block_prepare(
8931    t: &[u8],
8932    sa: &mut [SaSint],
8933    k: SaSint,
8934    buckets: &mut [SaSint],
8935    cache: &mut [ThreadCache],
8936    omp_block_start: FastSint,
8937    omp_block_size: FastSint,
8938) -> FastSint {
8939    if omp_block_size <= 0 {
8940        return 0;
8941    }
8942    let k_usize = usize::try_from(k).expect("k must be non-negative");
8943    buckets[..k_usize].fill(0);
8944    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
8945    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
8946    let mut count = 0usize;
8947    let mut i = start + size;
8948    while i > start {
8949        i -= 1;
8950        let mut p = sa[i];
8951        sa[i] = p & SAINT_MAX;
8952        if p > 0 {
8953            p -= 1;
8954            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
8955            let c0 = t[p_usize - usize::from(p > 0)] as SaSint;
8956            let c1 = t[p_usize] as SaSint;
8957            sa[i] = c1;
8958            buckets[c1 as usize] += 1;
8959            cache[count].symbol = c1;
8960            cache[count].index = if c0 <= c1 { p } else { c0 | SAINT_MIN };
8961            cache[count + 1].index = p;
8962            count += 2;
8963        }
8964    }
8965    count as FastSint
8966}
8967
8968/// Internal helper: final sorting scan right to left 8u block prepare.
8969#[doc(hidden)]
8970pub fn final_sorting_scan_right_to_left_8u_block_prepare(
8971    t: &[u8],
8972    sa: &mut [SaSint],
8973    k: SaSint,
8974    buckets: &mut [SaSint],
8975    cache: &mut [ThreadCache],
8976    omp_block_start: FastSint,
8977    omp_block_size: FastSint,
8978) -> FastSint {
8979    if omp_block_size <= 0 {
8980        return 0;
8981    }
8982
8983    let k_usize = usize::try_from(k).expect("k must be non-negative");
8984    buckets[..k_usize].fill(0);
8985
8986    let start =
8987        usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") as FastSint;
8988    let mut i = omp_block_start + omp_block_size - 1;
8989    let mut j = start + 1;
8990    let mut count = 0usize;
8991
8992    while i >= j {
8993        let i0 = usize::try_from(i).expect("loop index must be non-negative");
8994        let i1 = usize::try_from(i - 1).expect("loop index must be non-negative");
8995
8996        let mut p0 = sa[i0];
8997        sa[i0] = p0 & SAINT_MAX;
8998        if p0 > 0 {
8999            p0 -= 1;
9000            let p0_usize = usize::try_from(p0).expect("suffix index must be non-negative");
9001            let c0 = t[p0_usize] as SaSint;
9002            buckets[c0 as usize] += 1;
9003            cache[count].symbol = c0;
9004            cache[count].index = p0
9005                | ((usize::from(t[p0_usize - usize::from(p0 > 0)] > t[p0_usize]) as SaSint)
9006                    << (SAINT_BIT - 1));
9007            count += 1;
9008        }
9009
9010        let mut p1 = sa[i1];
9011        sa[i1] = p1 & SAINT_MAX;
9012        if p1 > 0 {
9013            p1 -= 1;
9014            let p1_usize = usize::try_from(p1).expect("suffix index must be non-negative");
9015            let c1 = t[p1_usize] as SaSint;
9016            buckets[c1 as usize] += 1;
9017            cache[count].symbol = c1;
9018            cache[count].index = p1
9019                | ((usize::from(t[p1_usize - usize::from(p1 > 0)] > t[p1_usize]) as SaSint)
9020                    << (SAINT_BIT - 1));
9021            count += 1;
9022        }
9023
9024        i -= 2;
9025    }
9026
9027    j -= 1;
9028    while i >= j {
9029        let idx = usize::try_from(i).expect("loop index must be non-negative");
9030        let mut p = sa[idx];
9031        sa[idx] = p & SAINT_MAX;
9032        if p > 0 {
9033            p -= 1;
9034            let p_usize = usize::try_from(p).expect("suffix index must be non-negative");
9035            let c = t[p_usize] as SaSint;
9036            buckets[c as usize] += 1;
9037            cache[count].symbol = c;
9038            cache[count].index = p
9039                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9040                    << (SAINT_BIT - 1));
9041            count += 1;
9042        }
9043
9044        i -= 1;
9045    }
9046
9047    count as FastSint
9048}
9049
9050/// Internal helper: final order scan right to left 8u block place.
9051#[doc(hidden)]
9052pub fn final_order_scan_right_to_left_8u_block_place(
9053    sa: &mut [SaSint],
9054    buckets: &mut [SaSint],
9055    cache: &[ThreadCache],
9056    count: FastSint,
9057) {
9058    if count <= 0 {
9059        return;
9060    }
9061    let count_usize = usize::try_from(count).expect("count must be non-negative");
9062    for entry in &cache[..count_usize] {
9063        let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
9064        buckets[symbol] -= 1;
9065        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9066        sa[slot] = entry.index;
9067    }
9068}
9069
9070/// Internal helper: final gsa scan right to left 8u block place.
9071#[doc(hidden)]
9072pub fn final_gsa_scan_right_to_left_8u_block_place(
9073    sa: &mut [SaSint],
9074    buckets: &mut [SaSint],
9075    cache: &[ThreadCache],
9076    count: FastSint,
9077) {
9078    if count <= 0 {
9079        return;
9080    }
9081    let count_usize = usize::try_from(count).expect("count must be non-negative");
9082    for entry in &cache[..count_usize] {
9083        if entry.symbol > 0 {
9084            let symbol = usize::try_from(entry.symbol).expect("cache symbol must be non-negative");
9085            buckets[symbol] -= 1;
9086            let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9087            sa[slot] = entry.index;
9088        }
9089    }
9090}
9091
9092/// Internal helper: final bwt aux scan right to left 8u block place.
9093#[doc(hidden)]
9094pub fn final_bwt_aux_scan_right_to_left_8u_block_place(
9095    sa: &mut [SaSint],
9096    rm: SaSint,
9097    i_out: &mut [SaSint],
9098    buckets: &mut [SaSint],
9099    cache: &[ThreadCache],
9100    count: FastSint,
9101) {
9102    if count <= 0 {
9103        return;
9104    }
9105    let count_usize = usize::try_from(count).expect("count must be non-negative");
9106    let mut i = 0usize;
9107    while i < count_usize {
9108        let symbol = usize::try_from(cache[i].symbol).expect("cache symbol must be non-negative");
9109        buckets[symbol] -= 1;
9110        let slot = usize::try_from(buckets[symbol]).expect("bucket slot must be non-negative");
9111        sa[slot] = cache[i].index;
9112        if (cache[i + 1].index & rm) == 0 {
9113            let sample_index = usize::try_from((cache[i + 1].index & SAINT_MAX) / (rm + 1))
9114                .expect("sample index must be non-negative");
9115            i_out[sample_index] = buckets[symbol] + 1;
9116        }
9117        i += 2;
9118    }
9119}
9120
9121/// Internal helper: final sorting scan right to left 32s block gather.
9122#[doc(hidden)]
9123pub fn final_sorting_scan_right_to_left_32s_block_gather(
9124    t: &[SaSint],
9125    sa: &mut [SaSint],
9126    cache: &mut [ThreadCache],
9127    omp_block_start: FastSint,
9128    omp_block_size: FastSint,
9129) {
9130    if omp_block_size <= 0 {
9131        return;
9132    }
9133    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9134    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9135    for offset in 0..size {
9136        let i = start + offset;
9137        let mut symbol = SAINT_MIN;
9138        let mut p = sa[i];
9139        sa[i] = p & SAINT_MAX;
9140        if p > 0 {
9141            p -= 1;
9142            let p_usize = p as usize;
9143            cache[offset].index = p
9144                | ((usize::from(t[p_usize - usize::from(p > 0)] > t[p_usize]) as SaSint)
9145                    << (SAINT_BIT - 1));
9146            symbol = t[p_usize];
9147        }
9148        cache[offset].symbol = symbol;
9149    }
9150}
9151
9152/// Internal helper: final sorting scan right to left 32s block sort.
9153#[doc(hidden)]
9154pub fn final_sorting_scan_right_to_left_32s_block_sort(
9155    t: &[SaSint],
9156    induction_bucket: &mut [SaSint],
9157    cache: &mut [ThreadCache],
9158    omp_block_start: FastSint,
9159    omp_block_size: FastSint,
9160) {
9161    if omp_block_size <= 0 {
9162        return;
9163    }
9164    let prefetch_distance = 64usize;
9165    let start = omp_block_start as usize;
9166    let mut i = start + omp_block_size as usize - 1;
9167    let mut j = start + prefetch_distance + 1;
9168
9169    while i >= j {
9170        let ci = i - start;
9171        let v0 = cache[ci].symbol;
9172        if v0 >= 0 {
9173            let bucket_index0 = v0 as usize;
9174            induction_bucket[bucket_index0] -= 1;
9175            cache[ci].symbol = induction_bucket[bucket_index0];
9176            if cache[ci].symbol >= omp_block_start as SaSint {
9177                let ni = cache[ci].symbol as usize;
9178                let cni = ni - start;
9179                let mut np = cache[ci].index;
9180                cache[ci].index = np & SAINT_MAX;
9181                if np > 0 {
9182                    np -= 1;
9183                    let np_usize = np as usize;
9184                    cache[cni].index = np
9185                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9186                            as SaSint)
9187                            << (SAINT_BIT - 1));
9188                    cache[cni].symbol = t[np_usize];
9189                }
9190            }
9191        }
9192
9193        let i1 = i - 1;
9194        let ci1 = i1 - start;
9195        let v1 = cache[ci1].symbol;
9196        if v1 >= 0 {
9197            let bucket_index1 = v1 as usize;
9198            induction_bucket[bucket_index1] -= 1;
9199            cache[ci1].symbol = induction_bucket[bucket_index1];
9200            if cache[ci1].symbol >= omp_block_start as SaSint {
9201                let ni = cache[ci1].symbol as usize;
9202                let cni = ni - start;
9203                let mut np = cache[ci1].index;
9204                cache[ci1].index = np & SAINT_MAX;
9205                if np > 0 {
9206                    np -= 1;
9207                    let np_usize = np as usize;
9208                    cache[cni].index = np
9209                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9210                            as SaSint)
9211                            << (SAINT_BIT - 1));
9212                    cache[cni].symbol = t[np_usize];
9213                }
9214            }
9215        }
9216
9217        i -= 2;
9218    }
9219
9220    j -= prefetch_distance + 1;
9221    while i >= j {
9222        let ci = i - start;
9223        let v = cache[ci].symbol;
9224        if v >= 0 {
9225            let bucket_index = v as usize;
9226            induction_bucket[bucket_index] -= 1;
9227            cache[ci].symbol = induction_bucket[bucket_index];
9228            if cache[ci].symbol >= omp_block_start as SaSint {
9229                let ni = cache[ci].symbol as usize;
9230                let cni = ni - start;
9231                let mut np = cache[ci].index;
9232                cache[ci].index = np & SAINT_MAX;
9233                if np > 0 {
9234                    np -= 1;
9235                    let np_usize = np as usize;
9236                    cache[cni].index = np
9237                        | ((usize::from(t[np_usize - usize::from(np > 0)] > t[np_usize])
9238                            as SaSint)
9239                            << (SAINT_BIT - 1));
9240                    cache[cni].symbol = t[np_usize];
9241                }
9242            }
9243        }
9244
9245        if i == 0 {
9246            break;
9247        }
9248        i -= 1;
9249    }
9250}
9251
9252/// Internal helper: final bwt scan right to left 8u block (OpenMP variant).
9253#[doc(hidden)]
9254pub fn final_bwt_scan_right_to_left_8u_block_omp(
9255    t: &[u8],
9256    sa: &mut [SaSint],
9257    k: SaSint,
9258    induction_bucket: &mut [SaSint],
9259    block_start: FastSint,
9260    block_size: FastSint,
9261    threads: SaSint,
9262    thread_state: &mut [ThreadState],
9263) {
9264    if block_size <= 0 {
9265        return;
9266    }
9267    let k_usize = usize::try_from(k).expect("k must be non-negative");
9268    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9269    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9270    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9271    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9272        let _ = final_bwt_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9273        return;
9274    }
9275
9276    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9277    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9278        let omp_block_start = omp_thread_num * omp_block_stride;
9279        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9280            omp_block_stride
9281        } else {
9282            block_size_usize - omp_block_start
9283        };
9284        state.count = final_bwt_scan_right_to_left_8u_block_prepare(
9285            t,
9286            sa,
9287            k,
9288            &mut state.buckets,
9289            &mut state.cache,
9290            block_start + omp_block_start as FastSint,
9291            omp_block_size as FastSint,
9292        );
9293    }
9294    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9295        for c in 0..k_usize {
9296            let a = induction_bucket[c];
9297            let b = state.buckets[c];
9298            induction_bucket[c] = a - b;
9299            state.buckets[c] = a;
9300        }
9301    }
9302    for state in thread_state.iter_mut().take(omp_num_threads) {
9303        final_order_scan_right_to_left_8u_block_place(
9304            sa,
9305            &mut state.buckets,
9306            &state.cache,
9307            state.count,
9308        );
9309    }
9310}
9311
9312/// Internal helper: final bwt aux scan right to left 8u block (OpenMP variant).
9313#[doc(hidden)]
9314pub fn final_bwt_aux_scan_right_to_left_8u_block_omp(
9315    t: &[u8],
9316    sa: &mut [SaSint],
9317    k: SaSint,
9318    rm: SaSint,
9319    i_out: &mut [SaSint],
9320    induction_bucket: &mut [SaSint],
9321    block_start: FastSint,
9322    block_size: FastSint,
9323    threads: SaSint,
9324    thread_state: &mut [ThreadState],
9325) {
9326    if block_size <= 0 {
9327        return;
9328    }
9329    let k_usize = usize::try_from(k).expect("k must be non-negative");
9330    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9331    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9332    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9333    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9334        final_bwt_aux_scan_right_to_left_8u(
9335            t,
9336            sa,
9337            rm,
9338            i_out,
9339            induction_bucket,
9340            block_start,
9341            block_size,
9342        );
9343        return;
9344    }
9345
9346    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9347    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9348        let omp_block_start = omp_thread_num * omp_block_stride;
9349        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9350            omp_block_stride
9351        } else {
9352            block_size_usize - omp_block_start
9353        };
9354        state.count = final_bwt_aux_scan_right_to_left_8u_block_prepare(
9355            t,
9356            sa,
9357            k,
9358            &mut state.buckets,
9359            &mut state.cache,
9360            block_start + omp_block_start as FastSint,
9361            omp_block_size as FastSint,
9362        );
9363    }
9364    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9365        for c in 0..k_usize {
9366            let a = induction_bucket[c];
9367            let b = state.buckets[c];
9368            induction_bucket[c] = a - b;
9369            state.buckets[c] = a;
9370        }
9371    }
9372    for state in thread_state.iter_mut().take(omp_num_threads) {
9373        final_bwt_aux_scan_right_to_left_8u_block_place(
9374            sa,
9375            rm,
9376            i_out,
9377            &mut state.buckets,
9378            &state.cache,
9379            state.count,
9380        );
9381    }
9382}
9383
9384/// Internal helper: final sorting scan right to left 8u block (OpenMP variant).
9385#[doc(hidden)]
9386pub fn final_sorting_scan_right_to_left_8u_block_omp(
9387    t: &[u8],
9388    sa: &mut [SaSint],
9389    k: SaSint,
9390    induction_bucket: &mut [SaSint],
9391    block_start: FastSint,
9392    block_size: FastSint,
9393    threads: SaSint,
9394    thread_state: &mut [ThreadState],
9395) {
9396    if block_size <= 0 {
9397        return;
9398    }
9399    let k_usize = usize::try_from(k).expect("k must be non-negative");
9400    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9401    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9402    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9403    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9404        final_sorting_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9405        return;
9406    }
9407
9408    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9409    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9410        let omp_block_start = omp_thread_num * omp_block_stride;
9411        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9412            omp_block_stride
9413        } else {
9414            block_size_usize - omp_block_start
9415        };
9416        state.count = final_sorting_scan_right_to_left_8u_block_prepare(
9417            t,
9418            sa,
9419            k,
9420            &mut state.buckets,
9421            &mut state.cache,
9422            block_start + omp_block_start as FastSint,
9423            omp_block_size as FastSint,
9424        );
9425    }
9426    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9427        for c in 0..k_usize {
9428            let a = induction_bucket[c];
9429            let b = state.buckets[c];
9430            induction_bucket[c] = a - b;
9431            state.buckets[c] = a;
9432        }
9433    }
9434    for state in thread_state.iter_mut().take(omp_num_threads) {
9435        final_order_scan_right_to_left_8u_block_place(
9436            sa,
9437            &mut state.buckets,
9438            &state.cache,
9439            state.count,
9440        );
9441    }
9442}
9443
9444/// Internal helper: final gsa scan right to left 8u block (OpenMP variant).
9445#[doc(hidden)]
9446pub fn final_gsa_scan_right_to_left_8u_block_omp(
9447    t: &[u8],
9448    sa: &mut [SaSint],
9449    k: SaSint,
9450    induction_bucket: &mut [SaSint],
9451    block_start: FastSint,
9452    block_size: FastSint,
9453    threads: SaSint,
9454    thread_state: &mut [ThreadState],
9455) {
9456    if block_size <= 0 {
9457        return;
9458    }
9459    let k_usize = usize::try_from(k).expect("k must be non-negative");
9460    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9461    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9462    let omp_num_threads = threads_usize.min(thread_state.len()).min(block_size_usize);
9463    if omp_num_threads <= 1 || block_size < 64 * k.max(256) as FastSint {
9464        final_gsa_scan_right_to_left_8u(t, sa, induction_bucket, block_start, block_size);
9465        return;
9466    }
9467
9468    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9469    for (omp_thread_num, state) in thread_state.iter_mut().take(omp_num_threads).enumerate() {
9470        let omp_block_start = omp_thread_num * omp_block_stride;
9471        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9472            omp_block_stride
9473        } else {
9474            block_size_usize - omp_block_start
9475        };
9476        state.count = final_sorting_scan_right_to_left_8u_block_prepare(
9477            t,
9478            sa,
9479            k,
9480            &mut state.buckets,
9481            &mut state.cache,
9482            block_start + omp_block_start as FastSint,
9483            omp_block_size as FastSint,
9484        );
9485    }
9486    for state in thread_state.iter_mut().take(omp_num_threads).rev() {
9487        for c in 0..k_usize {
9488            let a = induction_bucket[c];
9489            let b = state.buckets[c];
9490            induction_bucket[c] = a - b;
9491            state.buckets[c] = a;
9492        }
9493    }
9494    for state in thread_state.iter_mut().take(omp_num_threads) {
9495        final_gsa_scan_right_to_left_8u_block_place(
9496            sa,
9497            &mut state.buckets,
9498            &state.cache,
9499            state.count,
9500        );
9501    }
9502}
9503
9504/// Internal helper: final sorting scan right to left 32s block (OpenMP variant).
9505#[doc(hidden)]
9506pub fn final_sorting_scan_right_to_left_32s_block_omp(
9507    t: &[SaSint],
9508    sa: &mut [SaSint],
9509    buckets: &mut [SaSint],
9510    cache: &mut [ThreadCache],
9511    block_start: FastSint,
9512    block_size: FastSint,
9513    threads: SaSint,
9514) {
9515    if threads <= 1 || block_size < 16_384 {
9516        final_sorting_scan_right_to_left_32s(t, sa, buckets, block_start, block_size);
9517        return;
9518    }
9519
9520    final_sorting_scan_right_to_left_32s_block_gather(t, sa, cache, block_start, block_size);
9521    final_sorting_scan_right_to_left_32s_block_sort(t, buckets, cache, block_start, block_size);
9522    let block_size_usize = usize::try_from(block_size).expect("block_size must be non-negative");
9523    let threads_usize = usize::try_from(threads.max(1)).expect("threads must be positive");
9524    let omp_num_threads = threads_usize.min(block_size_usize);
9525    let omp_block_stride = (block_size_usize / omp_num_threads) & !15usize;
9526    {
9527        let sa_ptr = SyncMutPtr::new(sa);
9528        let cache_ptr = SyncMutPtr::new(cache);
9529        run_rayon_with_threads(omp_num_threads, || {
9530            (0..omp_num_threads)
9531                .into_par_iter()
9532                .for_each(|omp_thread_num| {
9533                    let omp_block_start = omp_thread_num * omp_block_stride;
9534                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
9535                        omp_block_stride
9536                    } else {
9537                        block_size_usize - omp_block_start
9538                    };
9539                    let sa = unsafe { sa_ptr.as_slice() };
9540                    let cache = unsafe { cache_ptr.as_slice() };
9541                    compact_and_place_cached_suffixes(
9542                        sa,
9543                        cache,
9544                        omp_block_start as FastSint,
9545                        omp_block_size as FastSint,
9546                    );
9547                });
9548        });
9549    }
9550}
9551
9552/// Internal helper: final bwt scan right to left 8u (OpenMP variant).
9553#[doc(hidden)]
9554pub fn final_bwt_scan_right_to_left_8u_omp(
9555    t: &[u8],
9556    sa: &mut [SaSint],
9557    n: SaSint,
9558    k: SaSint,
9559    induction_bucket: &mut [SaSint],
9560    threads: SaSint,
9561    thread_state: &mut [ThreadState],
9562) -> SaSint {
9563    if threads == 1 || n < 65_536 {
9564        return final_bwt_scan_right_to_left_8u(t, sa, induction_bucket, 0, n as FastSint);
9565    }
9566    let mut index = -1;
9567    let mut block_start = usize::try_from(n).expect("n must be non-negative");
9568    while block_start > 0 {
9569        block_start -= 1;
9570        if sa[block_start] == 0 {
9571            index = block_start as SaSint;
9572        } else {
9573            let threads_usize = usize::try_from(threads)
9574                .expect("threads must be non-negative")
9575                .min(thread_state.len())
9576                .max(1);
9577            let max_back =
9578                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
9579            let block_max_end = block_start.saturating_sub(max_back);
9580            let mut block_end = block_start;
9581            while block_end > block_max_end && sa[block_end - 1] != 0 {
9582                block_end -= 1;
9583            }
9584            let size = block_start - block_end + 1;
9585            if size < 32 {
9586                let res = final_bwt_scan_right_to_left_8u(
9587                    t,
9588                    sa,
9589                    induction_bucket,
9590                    block_end as FastSint,
9591                    size as FastSint,
9592                );
9593                if res >= 0 {
9594                    index = res;
9595                }
9596            } else {
9597                final_bwt_scan_right_to_left_8u_block_omp(
9598                    t,
9599                    sa,
9600                    k,
9601                    induction_bucket,
9602                    block_end as FastSint,
9603                    size as FastSint,
9604                    threads,
9605                    thread_state,
9606                );
9607            }
9608            block_start = block_end;
9609        }
9610    }
9611    index
9612}
9613
9614/// Internal helper: final bwt aux scan right to left 8u (OpenMP variant).
9615#[doc(hidden)]
9616pub fn final_bwt_aux_scan_right_to_left_8u_omp(
9617    t: &[u8],
9618    sa: &mut [SaSint],
9619    n: SaSint,
9620    k: SaSint,
9621    rm: SaSint,
9622    i_out: &mut [SaSint],
9623    induction_bucket: &mut [SaSint],
9624    threads: SaSint,
9625    thread_state: &mut [ThreadState],
9626) {
9627    if threads == 1 || n < 65_536 {
9628        final_bwt_aux_scan_right_to_left_8u(t, sa, rm, i_out, induction_bucket, 0, n as FastSint);
9629        return;
9630    }
9631    let mut block_start = usize::try_from(n).expect("n must be non-negative");
9632    while block_start > 0 {
9633        block_start -= 1;
9634        if sa[block_start] != 0 {
9635            let threads_usize = usize::try_from(threads)
9636                .expect("threads must be non-negative")
9637                .min(thread_state.len())
9638                .max(1);
9639            let max_back = threads_usize
9640                * (LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize) / 2);
9641            let block_max_end = block_start.saturating_sub(max_back);
9642            let mut block_end = block_start;
9643            while block_end > block_max_end && sa[block_end - 1] != 0 {
9644                block_end -= 1;
9645            }
9646            let size = block_start - block_end + 1;
9647            if size < 32 {
9648                final_bwt_aux_scan_right_to_left_8u(
9649                    t,
9650                    sa,
9651                    rm,
9652                    i_out,
9653                    induction_bucket,
9654                    block_end as FastSint,
9655                    size as FastSint,
9656                );
9657            } else {
9658                final_bwt_aux_scan_right_to_left_8u_block_omp(
9659                    t,
9660                    sa,
9661                    k,
9662                    rm,
9663                    i_out,
9664                    induction_bucket,
9665                    block_end as FastSint,
9666                    size as FastSint,
9667                    threads,
9668                    thread_state,
9669                );
9670            }
9671            block_start = block_end;
9672        }
9673    }
9674}
9675
9676/// Internal helper: final sorting scan right to left 8u (OpenMP variant).
9677#[doc(hidden)]
9678pub fn final_sorting_scan_right_to_left_8u_omp(
9679    t: &[u8],
9680    sa: &mut [SaSint],
9681    omp_block_start: FastSint,
9682    omp_block_size: FastSint,
9683    k: SaSint,
9684    induction_bucket: &mut [SaSint],
9685    threads: SaSint,
9686    thread_state: &mut [ThreadState],
9687) {
9688    if threads == 1 || omp_block_size < 65_536 {
9689        final_sorting_scan_right_to_left_8u(
9690            t,
9691            sa,
9692            induction_bucket,
9693            omp_block_start,
9694            omp_block_size,
9695        );
9696        return;
9697    }
9698    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9699    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9700    let mut block_start = start + size;
9701    while block_start > start {
9702        block_start -= 1;
9703        if sa[block_start] != 0 {
9704            let threads_usize = usize::try_from(threads)
9705                .expect("threads must be non-negative")
9706                .min(thread_state.len())
9707                .max(1);
9708            let max_back =
9709                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
9710            let block_max_end = block_start.saturating_sub(max_back).max(start);
9711            let mut block_end = block_start;
9712            while block_end > block_max_end && sa[block_end - 1] != 0 {
9713                block_end -= 1;
9714            }
9715            let span = block_start - block_end + 1;
9716            if span < 32 {
9717                final_sorting_scan_right_to_left_8u(
9718                    t,
9719                    sa,
9720                    induction_bucket,
9721                    block_end as FastSint,
9722                    span as FastSint,
9723                );
9724            } else {
9725                final_sorting_scan_right_to_left_8u_block_omp(
9726                    t,
9727                    sa,
9728                    k,
9729                    induction_bucket,
9730                    block_end as FastSint,
9731                    span as FastSint,
9732                    threads,
9733                    thread_state,
9734                );
9735            }
9736            block_start = block_end;
9737        }
9738    }
9739}
9740
9741/// Internal helper: final gsa scan right to left 8u (OpenMP variant).
9742#[doc(hidden)]
9743pub fn final_gsa_scan_right_to_left_8u_omp(
9744    t: &[u8],
9745    sa: &mut [SaSint],
9746    omp_block_start: FastSint,
9747    omp_block_size: FastSint,
9748    k: SaSint,
9749    induction_bucket: &mut [SaSint],
9750    threads: SaSint,
9751    thread_state: &mut [ThreadState],
9752) {
9753    if threads == 1 || omp_block_size < 65_536 {
9754        final_gsa_scan_right_to_left_8u(t, sa, induction_bucket, omp_block_start, omp_block_size);
9755        return;
9756    }
9757    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
9758    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
9759    let mut block_start = start + size;
9760    while block_start > start {
9761        block_start -= 1;
9762        if sa[block_start] != 0 {
9763            let threads_usize = usize::try_from(threads)
9764                .expect("threads must be non-negative")
9765                .min(thread_state.len())
9766                .max(1);
9767            let max_back =
9768                threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE.saturating_sub(16 * threads_usize);
9769            let block_max_end = block_start.saturating_sub(max_back).max(start);
9770            let mut block_end = block_start;
9771            while block_end > block_max_end && sa[block_end - 1] != 0 {
9772                block_end -= 1;
9773            }
9774            let span = block_start - block_end + 1;
9775            if span < 32 {
9776                final_gsa_scan_right_to_left_8u(
9777                    t,
9778                    sa,
9779                    induction_bucket,
9780                    block_end as FastSint,
9781                    span as FastSint,
9782                );
9783            } else {
9784                final_gsa_scan_right_to_left_8u_block_omp(
9785                    t,
9786                    sa,
9787                    k,
9788                    induction_bucket,
9789                    block_end as FastSint,
9790                    span as FastSint,
9791                    threads,
9792                    thread_state,
9793                );
9794            }
9795            block_start = block_end;
9796        }
9797    }
9798}
9799
9800/// Internal helper: final sorting scan right to left 32s (OpenMP variant).
9801#[doc(hidden)]
9802pub fn final_sorting_scan_right_to_left_32s_omp(
9803    t: &[SaSint],
9804    sa: &mut [SaSint],
9805    n: SaSint,
9806    induction_bucket: &mut [SaSint],
9807    threads: SaSint,
9808    thread_state: &mut [ThreadState],
9809) {
9810    if threads == 1 || n < 65_536 {
9811        final_sorting_scan_right_to_left_32s(t, sa, induction_bucket, 0, n as FastSint);
9812        return;
9813    }
9814    if thread_state.is_empty() {
9815        final_sorting_scan_right_to_left_32s(t, sa, induction_bucket, 0, n as FastSint);
9816        return;
9817    }
9818    let threads_usize = usize::try_from(threads)
9819        .expect("threads must be non-negative")
9820        .max(1);
9821    let mut cache = vec![ThreadCache::default(); threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE];
9822    let mut block_start = isize::try_from(n).expect("n must fit isize") - 1;
9823    while block_start >= 0 {
9824        let block_end = (block_start
9825            - isize::try_from(threads_usize * LIBSAIS_PER_THREAD_CACHE_SIZE)
9826                .expect("block span must fit isize"))
9827        .max(-1);
9828        final_sorting_scan_right_to_left_32s_block_omp(
9829            t,
9830            sa,
9831            induction_bucket,
9832            &mut cache,
9833            (block_end + 1) as FastSint,
9834            (block_start - block_end) as FastSint,
9835            threads,
9836        );
9837        block_start = block_end;
9838    }
9839}
9840
9841/// Internal helper: clear lms suffixes (OpenMP variant).
9842#[doc(hidden)]
9843pub fn clear_lms_suffixes_omp(
9844    sa: &mut [SaSint],
9845    n: SaSint,
9846    k: SaSint,
9847    bucket_start: &[SaSint],
9848    bucket_end: &[SaSint],
9849    threads: SaSint,
9850) {
9851    let k_usize = usize::try_from(k).expect("k must be non-negative");
9852    let thread_count = if threads > 1 && n >= 65536 {
9853        usize::try_from(threads).expect("threads must be positive")
9854    } else {
9855        1
9856    };
9857    {
9858        let sa_ptr = SyncMutPtr::new(sa);
9859        let bucket_start_ref: &[SaSint] = bucket_start;
9860        let bucket_end_ref: &[SaSint] = bucket_end;
9861        run_rayon_with_threads(thread_count, || {
9862            (0..thread_count).into_par_iter().for_each(|t| {
9863                let mut c = t;
9864                let sa = unsafe { sa_ptr.as_slice() };
9865                while c < k_usize {
9866                    if bucket_end_ref[c] > bucket_start_ref[c] {
9867                        let start = usize::try_from(bucket_start_ref[c])
9868                            .expect("bucket start must be non-negative");
9869                        let end = usize::try_from(bucket_end_ref[c])
9870                            .expect("bucket end must be non-negative");
9871                        sa[start..end].fill(0);
9872                    }
9873                    c += thread_count;
9874                }
9875            });
9876        });
9877    }
9878}
9879
9880/// Internal helper: induce final order 8u (OpenMP variant).
9881#[doc(hidden)]
9882pub fn induce_final_order_8u_omp(
9883    t: &[u8],
9884    sa: &mut [SaSint],
9885    n: SaSint,
9886    k: SaSint,
9887    flags: SaSint,
9888    r: SaSint,
9889    i_out: Option<&mut [SaSint]>,
9890    buckets: &mut [SaSint],
9891    threads: SaSint,
9892    thread_state: &mut [ThreadState],
9893) -> SaSint {
9894    if (flags & LIBSAIS_FLAGS_BWT) == 0 {
9895        if (flags & LIBSAIS_FLAGS_GSA) != 0 {
9896            buckets[6 * ALPHABET_SIZE] = buckets[7 * ALPHABET_SIZE] - 1;
9897        }
9898
9899        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
9900        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
9901        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
9902
9903        final_sorting_scan_left_to_right_8u_omp(
9904            t,
9905            sa,
9906            n as FastSint,
9907            k,
9908            bucket_start,
9909            threads,
9910            thread_state,
9911        );
9912        if threads > 1 && n >= 65_536 {
9913            clear_lms_suffixes_omp(
9914                sa,
9915                n,
9916                ALPHABET_SIZE as SaSint,
9917                bucket_start,
9918                bucket_end,
9919                threads,
9920            );
9921        }
9922
9923        if (flags & LIBSAIS_FLAGS_GSA) != 0 {
9924            flip_suffix_markers_omp(sa, bucket_end[0], threads);
9925            final_gsa_scan_right_to_left_8u_omp(
9926                t,
9927                sa,
9928                bucket_end[0] as FastSint,
9929                n as FastSint - bucket_end[0] as FastSint,
9930                k,
9931                bucket_end,
9932                threads,
9933                thread_state,
9934            );
9935        } else {
9936            final_sorting_scan_right_to_left_8u_omp(
9937                t,
9938                sa,
9939                0,
9940                n as FastSint,
9941                k,
9942                bucket_end,
9943                threads,
9944                thread_state,
9945            );
9946        }
9947
9948        0
9949    } else if let Some(i_out) = i_out {
9950        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
9951        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
9952        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
9953
9954        final_bwt_aux_scan_left_to_right_8u_omp(
9955            t,
9956            sa,
9957            n as FastSint,
9958            k,
9959            r - 1,
9960            i_out,
9961            bucket_start,
9962            threads,
9963            thread_state,
9964        );
9965        if threads > 1 && n >= 65_536 {
9966            clear_lms_suffixes_omp(
9967                sa,
9968                n,
9969                ALPHABET_SIZE as SaSint,
9970                bucket_start,
9971                bucket_end,
9972                threads,
9973            );
9974        }
9975        final_bwt_aux_scan_right_to_left_8u_omp(
9976            t,
9977            sa,
9978            n,
9979            k,
9980            r - 1,
9981            i_out,
9982            bucket_end,
9983            threads,
9984            thread_state,
9985        );
9986        0
9987    } else {
9988        let (left_buckets, right_tail) = buckets.split_at_mut(7 * ALPHABET_SIZE);
9989        let bucket_start = &mut left_buckets[6 * ALPHABET_SIZE..7 * ALPHABET_SIZE];
9990        let bucket_end = &mut right_tail[..ALPHABET_SIZE];
9991
9992        final_bwt_scan_left_to_right_8u_omp(
9993            t,
9994            sa,
9995            n as FastSint,
9996            k,
9997            bucket_start,
9998            threads,
9999            thread_state,
10000        );
10001        if threads > 1 && n >= 65_536 {
10002            clear_lms_suffixes_omp(
10003                sa,
10004                n,
10005                ALPHABET_SIZE as SaSint,
10006                bucket_start,
10007                bucket_end,
10008                threads,
10009            );
10010        }
10011        final_bwt_scan_right_to_left_8u_omp(t, sa, n, k, bucket_end, threads, thread_state)
10012    }
10013}
10014
10015/// Internal helper: induce final order 32s 6k.
10016#[doc(hidden)]
10017pub fn induce_final_order_32s_6k(
10018    t: &[SaSint],
10019    sa: &mut [SaSint],
10020    n: SaSint,
10021    k: SaSint,
10022    buckets: &mut [SaSint],
10023    threads: SaSint,
10024    thread_state: &mut [ThreadState],
10025) {
10026    let k_usize = usize::try_from(k).expect("k must be non-negative");
10027    let (_head, tail) = buckets.split_at_mut(4 * k_usize);
10028    let (left, right) = tail.split_at_mut(k_usize);
10029    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10030    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10031}
10032
10033/// Internal helper: induce final order 32s 4k.
10034#[doc(hidden)]
10035pub fn induce_final_order_32s_4k(
10036    t: &[SaSint],
10037    sa: &mut [SaSint],
10038    n: SaSint,
10039    k: SaSint,
10040    buckets: &mut [SaSint],
10041    threads: SaSint,
10042    thread_state: &mut [ThreadState],
10043) {
10044    let k_usize = usize::try_from(k).expect("k must be non-negative");
10045    let (_head, tail) = buckets.split_at_mut(2 * k_usize);
10046    let (left, right) = tail.split_at_mut(k_usize);
10047    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10048    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10049}
10050
10051/// Internal helper: induce final order 32s 2k.
10052#[doc(hidden)]
10053pub fn induce_final_order_32s_2k(
10054    t: &[SaSint],
10055    sa: &mut [SaSint],
10056    n: SaSint,
10057    k: SaSint,
10058    buckets: &mut [SaSint],
10059    threads: SaSint,
10060    thread_state: &mut [ThreadState],
10061) {
10062    let k_usize = usize::try_from(k).expect("k must be non-negative");
10063    let (right, left) = buckets.split_at_mut(k_usize);
10064    final_sorting_scan_left_to_right_32s_omp(t, sa, n, left, threads, thread_state);
10065    final_sorting_scan_right_to_left_32s_omp(t, sa, n, right, threads, thread_state);
10066}
10067
10068/// Internal helper: induce final order 32s 1k.
10069#[doc(hidden)]
10070pub fn induce_final_order_32s_1k(
10071    t: &[SaSint],
10072    sa: &mut [SaSint],
10073    n: SaSint,
10074    k: SaSint,
10075    buckets: &mut [SaSint],
10076    threads: SaSint,
10077    thread_state: &mut [ThreadState],
10078) {
10079    count_suffixes_32s(t, n, k, buckets);
10080    initialize_buckets_start_32s_1k(k, buckets);
10081    final_sorting_scan_left_to_right_32s_omp(t, sa, n, buckets, threads, thread_state);
10082
10083    count_suffixes_32s(t, n, k, buckets);
10084    initialize_buckets_end_32s_1k(k, buckets);
10085    final_sorting_scan_right_to_left_32s_omp(t, sa, n, buckets, threads, thread_state);
10086}
10087
10088/// Internal helper: renumber unique and nonunique lms suffixes 32s.
10089#[doc(hidden)]
10090pub fn renumber_unique_and_nonunique_lms_suffixes_32s(
10091    t: &mut [SaSint],
10092    sa: &mut [SaSint],
10093    m: SaSint,
10094    mut f: SaSint,
10095    omp_block_start: FastSint,
10096    omp_block_size: FastSint,
10097) -> SaSint {
10098    if omp_block_size <= 0 {
10099        return f;
10100    }
10101
10102    let prefetch_distance = 64 as SaSint;
10103    let m_usize = usize::try_from(m).expect("m must be non-negative");
10104    let (sa_head, sam) = sa.split_at_mut(m_usize);
10105    let mut i = omp_block_start as SaSint;
10106    let mut j = omp_block_start as SaSint + omp_block_size as SaSint - 2 * prefetch_distance - 3;
10107
10108    while i < j {
10109        let p0 = sa_head[i as usize] as SaUint;
10110        let p0_half = (p0 >> 1) as usize;
10111        let mut s0 = sam[p0_half];
10112        if s0 < 0 {
10113            t[p0 as usize] |= SAINT_MIN;
10114            f += 1;
10115            s0 = i + SAINT_MIN + f;
10116        }
10117        sam[p0_half] = s0 - f;
10118
10119        let p1 = sa_head[(i + 1) as usize] as SaUint;
10120        let p1_half = (p1 >> 1) as usize;
10121        let mut s1 = sam[p1_half];
10122        if s1 < 0 {
10123            t[p1 as usize] |= SAINT_MIN;
10124            f += 1;
10125            s1 = i + 1 + SAINT_MIN + f;
10126        }
10127        sam[p1_half] = s1 - f;
10128
10129        let p2 = sa_head[(i + 2) as usize] as SaUint;
10130        let p2_half = (p2 >> 1) as usize;
10131        let mut s2 = sam[p2_half];
10132        if s2 < 0 {
10133            t[p2 as usize] |= SAINT_MIN;
10134            f += 1;
10135            s2 = i + 2 + SAINT_MIN + f;
10136        }
10137        sam[p2_half] = s2 - f;
10138
10139        let p3 = sa_head[(i + 3) as usize] as SaUint;
10140        let p3_half = (p3 >> 1) as usize;
10141        let mut s3 = sam[p3_half];
10142        if s3 < 0 {
10143            t[p3 as usize] |= SAINT_MIN;
10144            f += 1;
10145            s3 = i + 3 + SAINT_MIN + f;
10146        }
10147        sam[p3_half] = s3 - f;
10148
10149        i += 4;
10150    }
10151
10152    j += 2 * prefetch_distance + 3;
10153    while i < j {
10154        let p = sa_head[i as usize] as SaUint;
10155        let p_half = (p >> 1) as usize;
10156        let mut s = sam[p_half];
10157        if s < 0 {
10158            t[p as usize] |= SAINT_MIN;
10159            f += 1;
10160            s = i + SAINT_MIN + f;
10161        }
10162        sam[p_half] = s - f;
10163        i += 1;
10164    }
10165
10166    f
10167}
10168
10169/// Internal helper: compact unique and nonunique lms suffixes 32s.
10170#[doc(hidden)]
10171pub fn compact_unique_and_nonunique_lms_suffixes_32s(
10172    sa: &mut [SaSint],
10173    m: SaSint,
10174    pl: &mut FastSint,
10175    pr: &mut FastSint,
10176    omp_block_start: FastSint,
10177    omp_block_size: FastSint,
10178) {
10179    if omp_block_size <= 0 {
10180        return;
10181    }
10182
10183    let m_usize = usize::try_from(m).expect("m must be non-negative");
10184    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10185    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10186
10187    let source: Vec<SaSint> = sa[m_usize + start..m_usize + start + size].to_vec();
10188    let mut l = usize::try_from(*pl - 1).expect("left position must be positive");
10189    let mut r = usize::try_from(*pr - 1).expect("right position must be positive");
10190
10191    for &p in source.iter().rev() {
10192        let pu = p as SaUint;
10193        sa[l] = (pu & SAINT_MAX as SaUint) as SaSint;
10194        l = l.saturating_sub(usize::from((pu as SaSint) < 0));
10195
10196        sa[r] = pu.wrapping_sub(1) as SaSint;
10197        r = r.saturating_sub(usize::from((pu as SaSint) > 0));
10198    }
10199
10200    *pl = l as FastSint + 1;
10201    *pr = r as FastSint + 1;
10202}
10203
10204/// Internal helper: count unique suffixes.
10205#[doc(hidden)]
10206pub fn count_unique_suffixes(
10207    sa: &[SaSint],
10208    m: SaSint,
10209    omp_block_start: FastSint,
10210    omp_block_size: FastSint,
10211) -> SaSint {
10212    if omp_block_size <= 0 {
10213        return 0;
10214    }
10215
10216    let m_usize = usize::try_from(m).expect("m must be non-negative");
10217    let sam = &sa[m_usize..];
10218    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10219    let block_end =
10220        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10221    let j = block_end.saturating_sub(67);
10222    let mut f0 = 0;
10223    let mut f1 = 0;
10224    let mut f2 = 0;
10225    let mut f3 = 0;
10226
10227    while i < j {
10228        f0 += SaSint::from(
10229            sam[usize::try_from((sa[i] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10230        );
10231        f1 += SaSint::from(
10232            sam[usize::try_from((sa[i + 1] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10233        );
10234        f2 += SaSint::from(
10235            sam[usize::try_from((sa[i + 2] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10236        );
10237        f3 += SaSint::from(
10238            sam[usize::try_from((sa[i + 3] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10239        );
10240        i += 4;
10241    }
10242
10243    while i < block_end {
10244        f0 += SaSint::from(
10245            sam[usize::try_from((sa[i] as SaUint) >> 1).expect("name slot must fit usize")] < 0,
10246        );
10247        i += 1;
10248    }
10249
10250    f0 + f1 + f2 + f3
10251}
10252
10253/// Internal helper: renumber unique and nonunique lms suffixes 32s (OpenMP variant).
10254#[doc(hidden)]
10255pub fn renumber_unique_and_nonunique_lms_suffixes_32s_omp(
10256    t: &mut [SaSint],
10257    sa: &mut [SaSint],
10258    m: SaSint,
10259    threads: SaSint,
10260    thread_state: &mut [ThreadState],
10261) -> SaSint {
10262    let f = if threads == 1 || m < 65_536 {
10263        renumber_unique_and_nonunique_lms_suffixes_32s(t, sa, m, 0, 0, m as FastSint)
10264    } else {
10265        let threads_usize = usize::try_from(threads)
10266            .expect("threads must be non-negative")
10267            .max(1);
10268        let m_usize = usize::try_from(m).expect("m must be non-negative");
10269        let omp_num_threads = threads_usize.min(m_usize.max(1));
10270        let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
10271
10272        {
10273            let sa_ro: &[SaSint] = sa;
10274            run_rayon_with_threads(omp_num_threads, || {
10275                thread_state[..omp_num_threads]
10276                    .par_iter_mut()
10277                    .enumerate()
10278                    .for_each(|(omp_thread_num, state)| {
10279                        let omp_block_start = omp_thread_num * omp_block_stride;
10280                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10281                            omp_block_stride
10282                        } else {
10283                            m_usize - omp_block_start
10284                        };
10285                        state.count = count_unique_suffixes(
10286                            sa_ro,
10287                            m,
10288                            omp_block_start as FastSint,
10289                            omp_block_size as FastSint,
10290                        ) as FastSint;
10291                    });
10292            });
10293        }
10294
10295        let counts: Vec<FastSint> = thread_state[..omp_num_threads]
10296            .iter()
10297            .map(|s| s.count)
10298            .collect();
10299        let f = counts.iter().sum::<FastSint>() as SaSint;
10300
10301        {
10302            let sa_ptr = SyncMutPtr::new(sa);
10303            let t_ptr = SyncMutPtr::new(t);
10304            let counts_ref: &[FastSint] = &counts;
10305            run_rayon_with_threads(omp_num_threads, || {
10306                (0..omp_num_threads)
10307                    .into_par_iter()
10308                    .for_each(|omp_thread_num| {
10309                        let omp_block_start = omp_thread_num * omp_block_stride;
10310                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10311                            omp_block_stride
10312                        } else {
10313                            m_usize - omp_block_start
10314                        };
10315
10316                        let mut count: FastSint = 0;
10317                        for tt in 0..omp_thread_num {
10318                            count += counts_ref[tt];
10319                        }
10320
10321                        let sa = unsafe { sa_ptr.as_slice() };
10322                        let t = unsafe { t_ptr.as_slice() };
10323                        renumber_unique_and_nonunique_lms_suffixes_32s(
10324                            t,
10325                            sa,
10326                            m,
10327                            count as SaSint,
10328                            omp_block_start as FastSint,
10329                            omp_block_size as FastSint,
10330                        );
10331                    });
10332            });
10333        }
10334        f
10335    };
10336
10337    f
10338}
10339
10340/// Internal helper: compact unique and nonunique lms suffixes 32s (OpenMP variant).
10341#[doc(hidden)]
10342pub fn compact_unique_and_nonunique_lms_suffixes_32s_omp(
10343    sa: &mut [SaSint],
10344    n: SaSint,
10345    m: SaSint,
10346    fs: SaSint,
10347    f: SaSint,
10348    threads: SaSint,
10349    thread_state: &mut [ThreadState],
10350) {
10351    let half_n = (n as FastSint) >> 1;
10352    if threads == 1 || n < 131_072 || m >= fs {
10353        let mut l = m as FastSint;
10354        let mut r = n as FastSint + fs as FastSint;
10355        compact_unique_and_nonunique_lms_suffixes_32s(sa, m, &mut l, &mut r, 0, half_n);
10356    } else {
10357        let threads_usize = usize::try_from(threads)
10358            .expect("threads must be non-negative")
10359            .max(1);
10360        let half_n_usize = usize::try_from(half_n).expect("half_n must be non-negative");
10361        let omp_num_threads = threads_usize.min(half_n_usize.max(1));
10362        let omp_block_stride = (half_n_usize / omp_num_threads) & !15usize;
10363
10364        {
10365            let sa_ptr = SyncMutPtr::new(sa);
10366            run_rayon_with_threads(omp_num_threads, || {
10367                thread_state[..omp_num_threads]
10368                    .par_iter_mut()
10369                    .enumerate()
10370                    .for_each(|(omp_thread_num, state)| {
10371                        let omp_block_start = omp_thread_num * omp_block_stride;
10372                        let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10373                            omp_block_stride
10374                        } else {
10375                            half_n_usize - omp_block_start
10376                        };
10377
10378                        let mut position = m as FastSint
10379                            + half_n
10380                            + omp_block_start as FastSint
10381                            + omp_block_size as FastSint;
10382                        let mut count = m as FastSint
10383                            + omp_block_start as FastSint
10384                            + omp_block_size as FastSint;
10385
10386                        let sa = unsafe { sa_ptr.as_slice() };
10387                        compact_unique_and_nonunique_lms_suffixes_32s(
10388                            sa,
10389                            m,
10390                            &mut position,
10391                            &mut count,
10392                            omp_block_start as FastSint,
10393                            omp_block_size as FastSint,
10394                        );
10395                        state.position = position;
10396                        state.count = count;
10397                    });
10398            });
10399        }
10400
10401        let mut position = m as FastSint;
10402        for t in (0..omp_num_threads).rev() {
10403            let omp_block_end = if t + 1 < omp_num_threads {
10404                omp_block_stride * (t + 1)
10405            } else {
10406                half_n_usize
10407            };
10408            let count =
10409                m as FastSint + half_n + omp_block_end as FastSint - thread_state[t].position;
10410            if count > 0 {
10411                position -= count;
10412                let dst = usize::try_from(position).expect("destination must be non-negative");
10413                let src =
10414                    usize::try_from(thread_state[t].position).expect("source must be non-negative");
10415                let len = usize::try_from(count).expect("length must be non-negative");
10416                sa.copy_within(src..src + len, dst);
10417            }
10418        }
10419
10420        let mut position = n as FastSint + fs as FastSint;
10421        for t in (0..omp_num_threads).rev() {
10422            let omp_block_end = if t + 1 < omp_num_threads {
10423                omp_block_stride * (t + 1)
10424            } else {
10425                half_n_usize
10426            };
10427            let count = m as FastSint + omp_block_end as FastSint - thread_state[t].count;
10428            if count > 0 {
10429                position -= count;
10430                let dst = usize::try_from(position).expect("destination must be non-negative");
10431                let src =
10432                    usize::try_from(thread_state[t].count).expect("source must be non-negative");
10433                let len = usize::try_from(count).expect("length must be non-negative");
10434                sa.copy_within(src..src + len, dst);
10435            }
10436        }
10437    }
10438
10439    let copy_dst = usize::try_from(n + fs - m).expect("copy destination must be non-negative");
10440    let copy_src = usize::try_from(m - f).expect("copy source must be non-negative");
10441    let copy_len = usize::try_from(f).expect("copy length must be non-negative");
10442    sa.copy_within(copy_src..copy_src + copy_len, copy_dst);
10443}
10444
10445/// Internal helper: compact lms suffixes 32s (OpenMP variant).
10446#[doc(hidden)]
10447pub fn compact_lms_suffixes_32s_omp(
10448    t: &mut [SaSint],
10449    sa: &mut [SaSint],
10450    n: SaSint,
10451    m: SaSint,
10452    fs: SaSint,
10453    threads: SaSint,
10454    thread_state: &mut [ThreadState],
10455) -> SaSint {
10456    let f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(t, sa, m, threads, thread_state);
10457    compact_unique_and_nonunique_lms_suffixes_32s_omp(sa, n, m, fs, f, threads, thread_state);
10458    f
10459}
10460
10461/// Internal helper: merge unique lms suffixes 32s.
10462#[doc(hidden)]
10463pub fn merge_unique_lms_suffixes_32s(
10464    t: &mut [SaSint],
10465    sa: &mut [SaSint],
10466    n: SaSint,
10467    m: SaSint,
10468    l: FastSint,
10469    omp_block_start: FastSint,
10470    omp_block_size: FastSint,
10471) {
10472    if omp_block_size <= 0 {
10473        return;
10474    }
10475
10476    let n_usize = usize::try_from(n).expect("n must be non-negative");
10477    let m_usize = usize::try_from(m).expect("m must be non-negative");
10478    let mut src_index = n_usize - m_usize - 1 + usize::try_from(l).expect("l must be non-negative");
10479    let mut tmp = sa[src_index] as FastSint;
10480    src_index += 1;
10481
10482    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10483    let block_end =
10484        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10485    let j = block_end.saturating_sub(6);
10486    while i < j {
10487        let c0 = t[i];
10488        if c0 < 0 {
10489            t[i] = c0 & SAINT_MAX;
10490            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint;
10491            i += 1;
10492            tmp = sa[src_index] as FastSint;
10493            src_index += 1;
10494        }
10495
10496        let c1 = t[i + 1];
10497        if c1 < 0 {
10498            t[i + 1] = c1 & SAINT_MAX;
10499            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 1;
10500            i += 1;
10501            tmp = sa[src_index] as FastSint;
10502            src_index += 1;
10503        }
10504
10505        let c2 = t[i + 2];
10506        if c2 < 0 {
10507            t[i + 2] = c2 & SAINT_MAX;
10508            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 2;
10509            i += 1;
10510            tmp = sa[src_index] as FastSint;
10511            src_index += 1;
10512        }
10513
10514        let c3 = t[i + 3];
10515        if c3 < 0 {
10516            t[i + 3] = c3 & SAINT_MAX;
10517            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint + 3;
10518            i += 1;
10519            tmp = sa[src_index] as FastSint;
10520            src_index += 1;
10521        }
10522
10523        i += 4;
10524    }
10525
10526    while i < block_end {
10527        let c = t[i];
10528        if c < 0 {
10529            t[i] = c & SAINT_MAX;
10530            sa[usize::try_from(tmp).expect("target slot must be non-negative")] = i as SaSint;
10531            i += 1;
10532            tmp = sa[src_index] as FastSint;
10533            src_index += 1;
10534        }
10535        i += 1;
10536    }
10537}
10538
10539/// Internal helper: merge nonunique lms suffixes 32s.
10540#[doc(hidden)]
10541pub fn merge_nonunique_lms_suffixes_32s(
10542    sa: &mut [SaSint],
10543    n: SaSint,
10544    m: SaSint,
10545    l: FastSint,
10546    omp_block_start: FastSint,
10547    omp_block_size: FastSint,
10548) {
10549    if omp_block_size <= 0 {
10550        return;
10551    }
10552
10553    let n_usize = usize::try_from(n).expect("n must be non-negative");
10554    let m_usize = usize::try_from(m).expect("m must be non-negative");
10555    let mut src_index = n_usize - m_usize - 1 + usize::try_from(l).expect("l must be non-negative");
10556    let mut tmp = sa[src_index];
10557    src_index += 1;
10558
10559    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
10560    let block_end =
10561        i + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
10562    let j = block_end.saturating_sub(3);
10563    while i < j {
10564        if sa[i] == 0 {
10565            sa[i] = tmp;
10566            tmp = sa[src_index];
10567            src_index += 1;
10568        }
10569        if sa[i + 1] == 0 {
10570            sa[i + 1] = tmp;
10571            tmp = sa[src_index];
10572            src_index += 1;
10573        }
10574        if sa[i + 2] == 0 {
10575            sa[i + 2] = tmp;
10576            tmp = sa[src_index];
10577            src_index += 1;
10578        }
10579        if sa[i + 3] == 0 {
10580            sa[i + 3] = tmp;
10581            tmp = sa[src_index];
10582            src_index += 1;
10583        }
10584        i += 4;
10585    }
10586
10587    while i < block_end {
10588        if sa[i] == 0 {
10589            sa[i] = tmp;
10590            tmp = sa[src_index];
10591            src_index += 1;
10592        }
10593        i += 1;
10594    }
10595}
10596
10597/// Internal helper: merge unique lms suffixes 32s (OpenMP variant).
10598#[doc(hidden)]
10599pub fn merge_unique_lms_suffixes_32s_omp(
10600    t: &mut [SaSint],
10601    sa: &mut [SaSint],
10602    n: SaSint,
10603    m: SaSint,
10604    threads: SaSint,
10605    thread_state: &mut [ThreadState],
10606) {
10607    if threads == 1 || n < 65_536 {
10608        merge_unique_lms_suffixes_32s(t, sa, n, m, 0, 0, n as FastSint);
10609        return;
10610    }
10611
10612    let threads_usize = usize::try_from(threads)
10613        .expect("threads must be non-negative")
10614        .max(1);
10615    let n_usize = usize::try_from(n).expect("n must be non-negative");
10616    let omp_num_threads = threads_usize.min(n_usize.max(1));
10617    let omp_block_stride = (n_usize / omp_num_threads) & !15usize;
10618
10619    {
10620        let t_ro: &[SaSint] = t;
10621        run_rayon_with_threads(omp_num_threads, || {
10622            thread_state[..omp_num_threads]
10623                .par_iter_mut()
10624                .enumerate()
10625                .for_each(|(omp_thread_num, state)| {
10626                    let omp_block_start = omp_thread_num * omp_block_stride;
10627                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10628                        omp_block_stride
10629                    } else {
10630                        n_usize - omp_block_start
10631                    };
10632                    state.count = count_negative_marked_suffixes(
10633                        t_ro,
10634                        omp_block_start as FastSint,
10635                        omp_block_size as FastSint,
10636                    ) as FastSint;
10637                });
10638        });
10639    }
10640
10641    let counts: Vec<FastSint> = thread_state[..omp_num_threads]
10642        .iter()
10643        .map(|s| s.count)
10644        .collect();
10645
10646    {
10647        let sa_ptr = SyncMutPtr::new(sa);
10648        let t_ptr = SyncMutPtr::new(t);
10649        let counts_ref: &[FastSint] = &counts;
10650        run_rayon_with_threads(omp_num_threads, || {
10651            (0..omp_num_threads)
10652                .into_par_iter()
10653                .for_each(|omp_thread_num| {
10654                    let omp_block_start = omp_thread_num * omp_block_stride;
10655                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10656                        omp_block_stride
10657                    } else {
10658                        n_usize - omp_block_start
10659                    };
10660
10661                    let mut count: FastSint = 0;
10662                    for tt in 0..omp_thread_num {
10663                        count += counts_ref[tt];
10664                    }
10665
10666                    let sa = unsafe { sa_ptr.as_slice() };
10667                    let t = unsafe { t_ptr.as_slice() };
10668                    merge_unique_lms_suffixes_32s(
10669                        t,
10670                        sa,
10671                        n,
10672                        m,
10673                        count,
10674                        omp_block_start as FastSint,
10675                        omp_block_size as FastSint,
10676                    );
10677                });
10678        });
10679    }
10680}
10681
10682/// Internal helper: merge nonunique lms suffixes 32s (OpenMP variant).
10683#[doc(hidden)]
10684pub fn merge_nonunique_lms_suffixes_32s_omp(
10685    sa: &mut [SaSint],
10686    n: SaSint,
10687    m: SaSint,
10688    f: SaSint,
10689    threads: SaSint,
10690    thread_state: &mut [ThreadState],
10691) {
10692    if threads == 1 || m < 65_536 {
10693        merge_nonunique_lms_suffixes_32s(sa, n, m, f as FastSint, 0, m as FastSint);
10694        return;
10695    }
10696
10697    let threads_usize = usize::try_from(threads)
10698        .expect("threads must be non-negative")
10699        .max(1);
10700    let m_usize = usize::try_from(m).expect("m must be non-negative");
10701    let omp_num_threads = threads_usize.min(m_usize.max(1));
10702    let omp_block_stride = (m_usize / omp_num_threads) & !15usize;
10703
10704    {
10705        let sa_ro: &[SaSint] = sa;
10706        run_rayon_with_threads(omp_num_threads, || {
10707            thread_state[..omp_num_threads]
10708                .par_iter_mut()
10709                .enumerate()
10710                .for_each(|(omp_thread_num, state)| {
10711                    let omp_block_start = omp_thread_num * omp_block_stride;
10712                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10713                        omp_block_stride
10714                    } else {
10715                        m_usize - omp_block_start
10716                    };
10717                    state.count = count_zero_marked_suffixes(
10718                        sa_ro,
10719                        omp_block_start as FastSint,
10720                        omp_block_size as FastSint,
10721                    ) as FastSint;
10722                });
10723        });
10724    }
10725
10726    let counts: Vec<FastSint> = thread_state[..omp_num_threads]
10727        .iter()
10728        .map(|s| s.count)
10729        .collect();
10730
10731    {
10732        let sa_ptr = SyncMutPtr::new(sa);
10733        let counts_ref: &[FastSint] = &counts;
10734        run_rayon_with_threads(omp_num_threads, || {
10735            (0..omp_num_threads)
10736                .into_par_iter()
10737                .for_each(|omp_thread_num| {
10738                    let omp_block_start = omp_thread_num * omp_block_stride;
10739                    let omp_block_size = if omp_thread_num + 1 < omp_num_threads {
10740                        omp_block_stride
10741                    } else {
10742                        m_usize - omp_block_start
10743                    };
10744
10745                    let mut count: FastSint = f as FastSint;
10746                    for tt in 0..omp_thread_num {
10747                        count += counts_ref[tt];
10748                    }
10749
10750                    let sa = unsafe { sa_ptr.as_slice() };
10751                    merge_nonunique_lms_suffixes_32s(
10752                        sa,
10753                        n,
10754                        m,
10755                        count,
10756                        omp_block_start as FastSint,
10757                        omp_block_size as FastSint,
10758                    );
10759                });
10760        });
10761    }
10762}
10763
10764/// Internal helper: merge compacted lms suffixes 32s (OpenMP variant).
10765#[doc(hidden)]
10766pub fn merge_compacted_lms_suffixes_32s_omp(
10767    t: &mut [SaSint],
10768    sa: &mut [SaSint],
10769    n: SaSint,
10770    m: SaSint,
10771    f: SaSint,
10772    threads: SaSint,
10773    thread_state: &mut [ThreadState],
10774) {
10775    merge_unique_lms_suffixes_32s_omp(t, sa, n, m, threads, thread_state);
10776    merge_nonunique_lms_suffixes_32s_omp(sa, n, m, f, threads, thread_state);
10777}
10778
10779/// Internal helper: reconstruct compacted lms suffixes 32s 2k (OpenMP variant).
10780#[doc(hidden)]
10781pub fn reconstruct_compacted_lms_suffixes_32s_2k_omp(
10782    t: &mut [SaSint],
10783    sa: &mut [SaSint],
10784    n: SaSint,
10785    k: SaSint,
10786    m: SaSint,
10787    fs: SaSint,
10788    f: SaSint,
10789    buckets: &mut [SaSint],
10790    local_buckets: SaSint,
10791    threads: SaSint,
10792    thread_state: &mut [ThreadState],
10793) {
10794    if f > 0 {
10795        let dst = usize::try_from(n - m - 1).expect("destination must be non-negative");
10796        let src = usize::try_from(n + fs - m).expect("source must be non-negative");
10797        let len = usize::try_from(f).expect("length must be non-negative");
10798        sa.copy_within(src..src + len, dst);
10799
10800        let _ = count_and_gather_compacted_lms_suffixes_32s_2k_omp(
10801            t,
10802            sa,
10803            n,
10804            k,
10805            buckets,
10806            local_buckets,
10807            threads,
10808            thread_state,
10809        );
10810        reconstruct_lms_suffixes_omp(sa, n, m - f, threads);
10811
10812        let src_copy = 0usize;
10813        let dst_copy = usize::try_from(n - m - 1 + f).expect("destination must be non-negative");
10814        let copy_len = usize::try_from(m - f).expect("copy length must be non-negative");
10815        sa.copy_within(src_copy..src_copy + copy_len, dst_copy);
10816        sa[..usize::try_from(m).expect("m must be non-negative")].fill(0);
10817
10818        merge_compacted_lms_suffixes_32s_omp(t, sa, n, m, f, threads, thread_state);
10819    } else {
10820        let _ = count_and_gather_lms_suffixes_32s_2k(t, sa, n, k, buckets, 0, n as FastSint);
10821        reconstruct_lms_suffixes_omp(sa, n, m, threads);
10822    }
10823}
10824
10825/// Internal helper: reconstruct compacted lms suffixes 32s 1k (OpenMP variant).
10826#[doc(hidden)]
10827pub fn reconstruct_compacted_lms_suffixes_32s_1k_omp(
10828    t: &mut [SaSint],
10829    sa: &mut [SaSint],
10830    n: SaSint,
10831    m: SaSint,
10832    fs: SaSint,
10833    f: SaSint,
10834    threads: SaSint,
10835    thread_state: &mut [ThreadState],
10836) {
10837    if f > 0 {
10838        let dst = usize::try_from(n - m - 1).expect("destination must be non-negative");
10839        let src = usize::try_from(n + fs - m).expect("source must be non-negative");
10840        let len = usize::try_from(f).expect("length must be non-negative");
10841        sa.copy_within(src..src + len, dst);
10842
10843        let _ = gather_compacted_lms_suffixes_32s(t, sa, n);
10844        reconstruct_lms_suffixes_omp(sa, n, m - f, threads);
10845
10846        let dst_copy = usize::try_from(n - m - 1 + f).expect("destination must be non-negative");
10847        let copy_len = usize::try_from(m - f).expect("copy length must be non-negative");
10848        sa.copy_within(0..copy_len, dst_copy);
10849        sa[..usize::try_from(m).expect("m must be non-negative")].fill(0);
10850
10851        merge_compacted_lms_suffixes_32s_omp(t, sa, n, m, f, threads, thread_state);
10852    } else {
10853        let _ = gather_lms_suffixes_32s(t, sa, n);
10854        reconstruct_lms_suffixes_omp(sa, n, m, threads);
10855    }
10856}
10857
10858fn normalize_omp_threads(threads: SaSint) -> SaSint {
10859    if threads > 0 {
10860        threads
10861    } else {
10862        std::thread::available_parallelism()
10863            .map(|value| value.get() as SaSint)
10864            .unwrap_or(1)
10865            .max(1)
10866    }
10867}
10868
10869fn libsais64_main_32s_recursion(
10870    t_ptr: *mut SaSint,
10871    sa_ptr: *mut SaSint,
10872    sa_capacity: usize,
10873    n: SaSint,
10874    k: SaSint,
10875    fs: SaSint,
10876    threads: SaSint,
10877    thread_state: &mut [ThreadState],
10878    _local_buffer: &mut [SaSint],
10879) -> SaSint {
10880    let fs = fs.min(SAINT_MAX - n);
10881    let local_buffer_size = SaSint::try_from(LIBSAIS_LOCAL_BUFFER_SIZE).expect("fits");
10882    let n_usize = usize::try_from(n).expect("n must be non-negative");
10883    let fs_usize = usize::try_from(fs).expect("fs must be non-negative");
10884    let total_len = n_usize + fs_usize;
10885    assert!(total_len <= sa_capacity);
10886
10887    if k > 0 && n <= i32::MAX as SaSint {
10888        let int32_max = i32::MAX as SaSint;
10889        let expanded_space = fs as i128 + fs as i128 + n as i128 + n as i128;
10890        let new_fs = if expanded_space <= int32_max as i128 {
10891            fs + fs + n
10892        } else {
10893            int32_max - n
10894        };
10895
10896        if (new_fs / k >= 6)
10897            || (new_fs / k >= 4 && n <= int32_max / 2)
10898            || (new_fs / k < 4 && new_fs >= fs)
10899        {
10900            let mut t32 = unsafe { std::slice::from_raw_parts(t_ptr, n_usize) }
10901                .iter()
10902                .map(|&value| (value as u64 as u32) as i32)
10903                .collect::<Vec<_>>();
10904            let mut sa32 = vec![0i32; n_usize + new_fs as usize];
10905
10906            let index = crate::libsais_int_omp(
10907                &mut t32,
10908                &mut sa32,
10909                k as i32,
10910                new_fs as i32,
10911                threads as i32,
10912            );
10913            if index >= 0 {
10914                unsafe {
10915                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
10916                    for (dst, src) in t.iter_mut().zip(t32.iter()) {
10917                        *dst = (*src as u32) as SaSint;
10918                    }
10919
10920                    let sa = std::slice::from_raw_parts_mut(sa_ptr, n_usize);
10921                    for (dst, src) in sa.iter_mut().zip(sa32.iter()) {
10922                        *dst = (*src as u32) as SaSint;
10923                    }
10924                }
10925            }
10926
10927            return index as SaSint;
10928        }
10929    }
10930
10931    if k > 0 && ((fs / k) >= 6 || (local_buffer_size / k) >= 6) {
10932        let k_usize = usize::try_from(k).expect("k must be non-negative");
10933        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 6 {
10934            1024usize
10935        } else {
10936            16usize
10937        };
10938        let need = 6 * k_usize;
10939        let use_local_buffer = local_buffer_size > fs;
10940        let buckets_ptr = if use_local_buffer {
10941            _local_buffer.as_mut_ptr()
10942        } else {
10943            unsafe {
10944                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
10945                let start =
10946                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 6 {
10947                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
10948                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
10949                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
10950                    } else {
10951                        total_len - need
10952                    };
10953                sa[start..].as_mut_ptr()
10954            }
10955        };
10956
10957        let m = unsafe {
10958            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
10959            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
10960            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
10961            count_and_gather_lms_suffixes_32s_4k_omp(
10962                t,
10963                sa,
10964                n,
10965                k,
10966                buckets,
10967                SaSint::from(use_local_buffer),
10968                threads,
10969                thread_state,
10970            )
10971        };
10972        if m > 1 {
10973            let m_usize = usize::try_from(m).expect("m must be non-negative");
10974            unsafe {
10975                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
10976                sa[..n_usize - m_usize].fill(0);
10977            }
10978
10979            let first_lms_suffix = unsafe {
10980                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
10981                sa[n_usize - m_usize]
10982            };
10983            let left_suffixes_count = unsafe {
10984                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
10985                initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(
10986                    std::slice::from_raw_parts_mut(t_ptr, n_usize),
10987                    k,
10988                    buckets,
10989                    first_lms_suffix,
10990                )
10991            };
10992
10993            unsafe {
10994                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
10995                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
10996                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
10997                let (_, induction_bucket) = buckets.split_at_mut(4 * k_usize);
10998                radix_sort_lms_suffixes_32s_6k_omp(
10999                    t,
11000                    sa,
11001                    n,
11002                    m,
11003                    induction_bucket,
11004                    threads,
11005                    thread_state,
11006                );
11007                if (n / 8192) < k {
11008                    radix_sort_set_markers_32s_6k_omp(sa, k, induction_bucket, threads);
11009                }
11010                if threads > 1 && n >= 65_536 {
11011                    sa[n_usize - m_usize..n_usize].fill(0);
11012                }
11013                initialize_buckets_for_partial_sorting_32s_6k(
11014                    t,
11015                    k,
11016                    buckets,
11017                    first_lms_suffix,
11018                    left_suffixes_count,
11019                );
11020                induce_partial_order_32s_6k_omp(
11021                    t,
11022                    sa,
11023                    n,
11024                    k,
11025                    buckets,
11026                    first_lms_suffix,
11027                    left_suffixes_count,
11028                    threads,
11029                    thread_state,
11030                );
11031            }
11032
11033            let names = unsafe {
11034                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11035                if (n / 8192) < k {
11036                    renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
11037                        sa,
11038                        n,
11039                        m,
11040                        threads,
11041                        thread_state,
11042                    )
11043                } else {
11044                    renumber_and_gather_lms_suffixes_omp(sa, n, m, fs, threads, thread_state)
11045                }
11046            };
11047
11048            if names < m {
11049                let f = if (n / 8192) < k {
11050                    unsafe {
11051                        let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11052                        let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11053                        compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11054                    }
11055                } else {
11056                    0
11057                };
11058
11059                let new_t_start =
11060                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11061                if libsais64_main_32s_recursion(
11062                    unsafe {
11063                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11064                            .as_mut_ptr()
11065                    },
11066                    sa_ptr,
11067                    sa_capacity,
11068                    m - f,
11069                    names - f,
11070                    fs + n - 2 * m + f,
11071                    threads,
11072                    thread_state,
11073                    _local_buffer,
11074                ) != 0
11075                {
11076                    return -2;
11077                }
11078
11079                unsafe {
11080                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11081                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11082                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11083                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11084                        t,
11085                        sa,
11086                        n,
11087                        k,
11088                        m,
11089                        fs,
11090                        f,
11091                        buckets,
11092                        SaSint::from(use_local_buffer),
11093                        threads,
11094                        thread_state,
11095                    );
11096                }
11097            } else {
11098                unsafe {
11099                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11100                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11101                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11102                }
11103            }
11104
11105            unsafe {
11106                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11107                initialize_buckets_start_and_end_32s_4k(k, buckets);
11108                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11109                place_lms_suffixes_histogram_32s_4k(sa, n, k, m, buckets);
11110                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11111                induce_final_order_32s_4k(t, sa, n, k, buckets, threads, thread_state);
11112            }
11113        } else {
11114            unsafe {
11115                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11116                sa[0] = sa[n_usize - 1];
11117            }
11118
11119            unsafe {
11120                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11121                initialize_buckets_start_and_end_32s_6k(k, buckets);
11122                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11123                place_lms_suffixes_histogram_32s_6k(sa, n, k, m, buckets);
11124                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11125                induce_final_order_32s_6k(t, sa, n, k, buckets, threads, thread_state);
11126            }
11127        }
11128
11129        return 0;
11130    } else if k > 0 && n <= SAINT_MAX / 2 && ((fs / k) >= 4 || (local_buffer_size / k) >= 4) {
11131        let k_usize = usize::try_from(k).expect("k must be non-negative");
11132        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 4 {
11133            1024usize
11134        } else {
11135            16usize
11136        };
11137        let need = 4 * k_usize;
11138        let use_local_buffer = local_buffer_size > fs;
11139        let buckets_ptr = if use_local_buffer {
11140            _local_buffer.as_mut_ptr()
11141        } else {
11142            unsafe {
11143                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11144                let start =
11145                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 4 {
11146                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
11147                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11148                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11149                    } else {
11150                        total_len - need
11151                    };
11152                sa[start..].as_mut_ptr()
11153            }
11154        };
11155
11156        let m = unsafe {
11157            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11158            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11159            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11160            count_and_gather_lms_suffixes_32s_2k_omp(
11161                t,
11162                sa,
11163                n,
11164                k,
11165                buckets,
11166                SaSint::from(use_local_buffer),
11167                threads,
11168                thread_state,
11169            )
11170        };
11171        if m > 1 {
11172            let m_usize = usize::try_from(m).expect("m must be non-negative");
11173            unsafe {
11174                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11175                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11176                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11177                initialize_buckets_for_radix_and_partial_sorting_32s_4k(
11178                    t,
11179                    k,
11180                    buckets,
11181                    sa[n_usize - m_usize],
11182                );
11183                let (_, induction_bucket) = buckets.split_at_mut(1);
11184                radix_sort_lms_suffixes_32s_2k_omp(
11185                    t,
11186                    sa,
11187                    n,
11188                    m,
11189                    induction_bucket,
11190                    threads,
11191                    thread_state,
11192                );
11193                radix_sort_set_markers_32s_4k_omp(sa, k, induction_bucket, threads);
11194                place_lms_suffixes_interval_32s_4k(sa, n, k, m - 1, buckets);
11195                induce_partial_order_32s_4k_omp(t, sa, n, k, buckets, threads, thread_state);
11196            }
11197
11198            let names = unsafe {
11199                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11200                renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(sa, n, m, threads, thread_state)
11201            };
11202            if names < m {
11203                let f = unsafe {
11204                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11205                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11206                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11207                };
11208
11209                let new_t_start =
11210                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11211                if libsais64_main_32s_recursion(
11212                    unsafe {
11213                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11214                            .as_mut_ptr()
11215                    },
11216                    sa_ptr,
11217                    sa_capacity,
11218                    m - f,
11219                    names - f,
11220                    fs + n - 2 * m + f,
11221                    threads,
11222                    thread_state,
11223                    _local_buffer,
11224                ) != 0
11225                {
11226                    return -2;
11227                }
11228
11229                unsafe {
11230                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11231                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11232                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11233                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11234                        t,
11235                        sa,
11236                        n,
11237                        k,
11238                        m,
11239                        fs,
11240                        f,
11241                        buckets,
11242                        SaSint::from(use_local_buffer),
11243                        threads,
11244                        thread_state,
11245                    );
11246                }
11247            } else {
11248                unsafe {
11249                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11250                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11251                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11252                }
11253            }
11254        } else {
11255            unsafe {
11256                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11257                sa[0] = sa[n_usize - 1];
11258            }
11259        }
11260
11261        unsafe {
11262            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11263            initialize_buckets_start_and_end_32s_4k(k, buckets);
11264            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11265            place_lms_suffixes_histogram_32s_4k(sa, n, k, m, buckets);
11266            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11267            induce_final_order_32s_4k(t, sa, n, k, buckets, threads, thread_state);
11268        }
11269
11270        return 0;
11271    } else if k > 0 && ((fs / k) >= 2 || (local_buffer_size / k) >= 2) {
11272        let k_usize = usize::try_from(k).expect("k must be non-negative");
11273        let alignment = if fs >= 1024 && ((fs - 1024) / k) >= 2 {
11274            1024usize
11275        } else {
11276            16usize
11277        };
11278        let need = 2 * k_usize;
11279        let use_local_buffer = local_buffer_size > fs;
11280        let buckets_ptr = if use_local_buffer {
11281            _local_buffer.as_mut_ptr()
11282        } else {
11283            unsafe {
11284                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11285                let start =
11286                    if fs_usize >= need + alignment && ((fs_usize - alignment) / k_usize) >= 2 {
11287                        let byte_ptr = sa[total_len - need - alignment..].as_mut_ptr() as usize;
11288                        let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11289                        (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11290                    } else {
11291                        total_len - need
11292                    };
11293                sa[start..].as_mut_ptr()
11294            }
11295        };
11296
11297        let m = unsafe {
11298            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11299            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11300            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11301            count_and_gather_lms_suffixes_32s_2k_omp(
11302                t,
11303                sa,
11304                n,
11305                k,
11306                buckets,
11307                SaSint::from(use_local_buffer),
11308                threads,
11309                thread_state,
11310            )
11311        };
11312        if m > 1 {
11313            let m_usize = usize::try_from(m).expect("m must be non-negative");
11314            unsafe {
11315                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11316                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11317                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11318                initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(
11319                    t,
11320                    k,
11321                    buckets,
11322                    sa[n_usize - m_usize],
11323                );
11324                let (_, induction_bucket) = buckets.split_at_mut(1);
11325                radix_sort_lms_suffixes_32s_2k_omp(
11326                    t,
11327                    sa,
11328                    n,
11329                    m,
11330                    induction_bucket,
11331                    threads,
11332                    thread_state,
11333                );
11334                place_lms_suffixes_interval_32s_2k(sa, n, k, m - 1, buckets);
11335            }
11336
11337            unsafe {
11338                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11339                initialize_buckets_start_and_end_32s_2k(k, buckets);
11340                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11341                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11342                induce_partial_order_32s_2k_omp(t, sa, n, k, buckets, threads, thread_state);
11343            }
11344
11345            let names = unsafe {
11346                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11347                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11348                renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(t, sa, n, m, threads)
11349            };
11350            if names < m {
11351                let f = unsafe {
11352                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11353                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11354                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11355                };
11356
11357                let new_t_start =
11358                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11359                if libsais64_main_32s_recursion(
11360                    unsafe {
11361                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11362                            .as_mut_ptr()
11363                    },
11364                    sa_ptr,
11365                    sa_capacity,
11366                    m - f,
11367                    names - f,
11368                    fs + n - 2 * m + f,
11369                    threads,
11370                    thread_state,
11371                    _local_buffer,
11372                ) != 0
11373                {
11374                    return -2;
11375                }
11376
11377                unsafe {
11378                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11379                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11380                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11381                    reconstruct_compacted_lms_suffixes_32s_2k_omp(
11382                        t,
11383                        sa,
11384                        n,
11385                        k,
11386                        m,
11387                        fs,
11388                        f,
11389                        buckets,
11390                        SaSint::from(use_local_buffer),
11391                        threads,
11392                        thread_state,
11393                    );
11394                }
11395            } else {
11396                unsafe {
11397                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11398                    let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11399                    count_lms_suffixes_32s_2k(t, n, k, buckets);
11400                }
11401            }
11402        } else {
11403            unsafe {
11404                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11405                sa[0] = sa[n_usize - 1];
11406            }
11407        }
11408
11409        unsafe {
11410            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11411            initialize_buckets_end_32s_2k(k, buckets);
11412            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11413            place_lms_suffixes_histogram_32s_2k(sa, n, k, m, buckets);
11414        }
11415
11416        unsafe {
11417            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, need);
11418            initialize_buckets_start_and_end_32s_2k(k, buckets);
11419            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11420            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11421            induce_final_order_32s_2k(t, sa, n, k, buckets, threads, thread_state);
11422        }
11423
11424        return 0;
11425    } else {
11426        let k_usize = usize::try_from(k).expect("k must be non-negative");
11427        let mut heap_buckets = if fs < k { Some(vec![0; k_usize]) } else { None };
11428        let alignment = if fs >= 1024 && (fs - 1024) >= k {
11429            1024usize
11430        } else {
11431            16usize
11432        };
11433        let mut buckets_ptr = if let Some(ref mut heap) = heap_buckets {
11434            heap.as_mut_ptr()
11435        } else {
11436            unsafe {
11437                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11438                let start = if fs_usize >= k_usize + alignment {
11439                    let byte_ptr = sa[total_len - k_usize - alignment..].as_mut_ptr() as usize;
11440                    let aligned = align_up(byte_ptr, alignment * mem::size_of::<SaSint>());
11441                    (aligned - sa_ptr as usize) / mem::size_of::<SaSint>()
11442                } else {
11443                    total_len - k_usize
11444                };
11445                sa[start..].as_mut_ptr()
11446            }
11447        };
11448
11449        if buckets_ptr.is_null() {
11450            return -2;
11451        }
11452
11453        unsafe {
11454            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11455            sa[..n_usize].fill(0);
11456        }
11457
11458        unsafe {
11459            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11460            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11461            count_suffixes_32s(t, n, k, buckets);
11462        }
11463        unsafe {
11464            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11465            initialize_buckets_end_32s_1k(k, buckets);
11466        }
11467
11468        let m = unsafe {
11469            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11470            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11471            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11472            radix_sort_lms_suffixes_32s_1k(t, sa, n, buckets)
11473        };
11474        if m > 1 {
11475            unsafe {
11476                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11477                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11478                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11479                induce_partial_order_32s_1k_omp(t, sa, n, k, buckets, threads, thread_state);
11480            }
11481
11482            let names = unsafe {
11483                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11484                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11485                renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(t, sa, n, m, threads)
11486            };
11487            if names < m {
11488                if heap_buckets.is_some() {
11489                    let _ = heap_buckets.take();
11490                    buckets_ptr = std::ptr::null_mut();
11491                }
11492
11493                let f = unsafe {
11494                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11495                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11496                    compact_lms_suffixes_32s_omp(t, sa, n, m, fs, threads, thread_state)
11497                };
11498
11499                let new_t_start =
11500                    total_len - usize::try_from(m - f).expect("m - f must be non-negative");
11501                if libsais64_main_32s_recursion(
11502                    unsafe {
11503                        std::slice::from_raw_parts_mut(sa_ptr, total_len)[new_t_start..]
11504                            .as_mut_ptr()
11505                    },
11506                    sa_ptr,
11507                    sa_capacity,
11508                    m - f,
11509                    names - f,
11510                    fs + n - 2 * m + f,
11511                    threads,
11512                    thread_state,
11513                    _local_buffer,
11514                ) != 0
11515                {
11516                    return -2;
11517                }
11518
11519                unsafe {
11520                    let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11521                    let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11522                    reconstruct_compacted_lms_suffixes_32s_1k_omp(
11523                        t,
11524                        sa,
11525                        n,
11526                        m,
11527                        fs,
11528                        f,
11529                        threads,
11530                        thread_state,
11531                    );
11532                }
11533
11534                if buckets_ptr.is_null() {
11535                    heap_buckets = Some(vec![0; k_usize]);
11536                    buckets_ptr = heap_buckets
11537                        .as_mut()
11538                        .expect("heap buckets must exist")
11539                        .as_mut_ptr();
11540                    if buckets_ptr.is_null() {
11541                        return -2;
11542                    }
11543                }
11544            }
11545
11546            unsafe {
11547                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11548                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11549                count_suffixes_32s(t, n, k, buckets);
11550            }
11551            unsafe {
11552                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11553                initialize_buckets_end_32s_1k(k, buckets);
11554            }
11555            unsafe {
11556                let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11557                let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11558                let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11559                place_lms_suffixes_interval_32s_1k(t, sa, k, m, buckets);
11560            }
11561        }
11562
11563        unsafe {
11564            let t = std::slice::from_raw_parts_mut(t_ptr, n_usize);
11565            let sa = std::slice::from_raw_parts_mut(sa_ptr, total_len);
11566            let buckets = std::slice::from_raw_parts_mut(buckets_ptr, k_usize);
11567            induce_final_order_32s_1k(t, sa, n, k, buckets, threads, thread_state);
11568        }
11569
11570        0
11571    }
11572}
11573
11574fn libsais64_main_32s_entry(
11575    t: &mut [SaSint],
11576    sa: &mut [SaSint],
11577    n: SaSint,
11578    k: SaSint,
11579    fs: SaSint,
11580    threads: SaSint,
11581    thread_state: &mut [ThreadState],
11582) -> SaSint {
11583    let mut local_buffer = [0; 2 * LIBSAIS_LOCAL_BUFFER_SIZE];
11584    libsais64_main_32s_recursion(
11585        t.as_mut_ptr(),
11586        sa.as_mut_ptr(),
11587        sa.len(),
11588        n,
11589        k,
11590        fs,
11591        threads,
11592        thread_state,
11593        &mut local_buffer[LIBSAIS_LOCAL_BUFFER_SIZE..],
11594    )
11595}
11596
11597fn libsais64_main_8u(
11598    t: &[u8],
11599    sa: &mut [SaSint],
11600    buckets: &mut [SaSint],
11601    flags: SaSint,
11602    r: SaSint,
11603    i: Option<&mut [SaSint]>,
11604    fs: SaSint,
11605    freq: Option<&mut [SaSint]>,
11606    threads: SaSint,
11607    thread_state: &mut [ThreadState],
11608) -> SaSint {
11609    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
11610    let n_usize = usize::try_from(n).expect("n must be non-negative");
11611    let fs = fs.min(SAINT_MAX - n);
11612
11613    let m = count_and_gather_lms_suffixes_8u_omp(t, sa, n, buckets, threads, thread_state);
11614    let k = initialize_buckets_start_and_end_8u(buckets, freq);
11615
11616    if (flags & LIBSAIS_FLAGS_GSA) != 0 && (buckets[0] != 0 || buckets[2] != 0 || buckets[3] != 1) {
11617        return -1;
11618    }
11619
11620    if m > 0 {
11621        let m_usize = usize::try_from(m).expect("m must be non-negative");
11622        let first_lms_suffix = sa[n_usize - m_usize];
11623        let left_suffixes_count =
11624            initialize_buckets_for_lms_suffixes_radix_sort_8u(t, buckets, first_lms_suffix);
11625
11626        if threads > 1 && n >= 65_536 {
11627            sa[..n_usize - m_usize].fill(0);
11628        }
11629        radix_sort_lms_suffixes_8u_omp(t, sa, n, m, flags, buckets, threads, thread_state);
11630        if threads > 1 && n >= 65_536 {
11631            sa[n_usize - m_usize..n_usize].fill(0);
11632        }
11633
11634        initialize_buckets_for_partial_sorting_8u(
11635            t,
11636            buckets,
11637            first_lms_suffix,
11638            left_suffixes_count,
11639        );
11640        induce_partial_order_8u_omp(
11641            t,
11642            sa,
11643            n,
11644            k,
11645            flags,
11646            buckets,
11647            first_lms_suffix,
11648            left_suffixes_count,
11649            threads,
11650            thread_state,
11651        );
11652
11653        let names = renumber_and_gather_lms_suffixes_omp(sa, n, m, fs, threads, thread_state);
11654        if names < m {
11655            if libsais64_main_32s_entry(
11656                unsafe {
11657                    std::slice::from_raw_parts_mut(
11658                        sa[n_usize + usize::try_from(fs).expect("fs must be non-negative")
11659                            - m_usize..]
11660                            .as_mut_ptr(),
11661                        m_usize,
11662                    )
11663                },
11664                sa,
11665                m,
11666                names,
11667                fs + n - 2 * m,
11668                threads,
11669                thread_state,
11670            ) != 0
11671            {
11672                return -2;
11673            }
11674
11675            gather_lms_suffixes_8u_omp(t, sa, n, threads, thread_state);
11676            reconstruct_lms_suffixes_omp(sa, n, m, threads);
11677        }
11678
11679        place_lms_suffixes_interval_8u(sa, n, m, flags, buckets);
11680    } else {
11681        sa[..n_usize].fill(0);
11682    }
11683
11684    induce_final_order_8u_omp(t, sa, n, k, flags, r, i, buckets, threads, thread_state)
11685}
11686
11687fn libsais64_main(
11688    t: &[u8],
11689    sa: &mut [SaSint],
11690    flags: SaSint,
11691    r: SaSint,
11692    i: Option<&mut [SaSint]>,
11693    fs: SaSint,
11694    freq: Option<&mut [SaSint]>,
11695    threads: SaSint,
11696) -> SaSint {
11697    let threads = normalize_omp_threads(threads);
11698    if threads > 1 {
11699        let mut thread_state = match alloc_thread_state(threads) {
11700            Some(thread_state) => thread_state,
11701            None => return -2,
11702        };
11703        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
11704
11705        libsais64_main_8u(
11706            t,
11707            sa,
11708            &mut buckets,
11709            flags,
11710            r,
11711            i,
11712            fs,
11713            freq,
11714            threads,
11715            &mut thread_state,
11716        )
11717    } else {
11718        let mut thread_state = [];
11719        let mut buckets = [0; 8 * ALPHABET_SIZE];
11720
11721        libsais64_main_8u(
11722            t,
11723            sa,
11724            &mut buckets,
11725            flags,
11726            r,
11727            i,
11728            fs,
11729            freq,
11730            threads,
11731            &mut thread_state,
11732        )
11733    }
11734}
11735
11736fn libsais64_main_int(
11737    t: &mut [SaSint],
11738    sa: &mut [SaSint],
11739    k: SaSint,
11740    fs: SaSint,
11741    threads: SaSint,
11742) -> SaSint {
11743    let threads = normalize_omp_threads(threads);
11744    let mut thread_state = if threads > 1 {
11745        match alloc_thread_state(threads) {
11746            Some(thread_state) => thread_state,
11747            None => return -2,
11748        }
11749    } else {
11750        Vec::new()
11751    };
11752
11753    libsais64_main_32s_entry(
11754        t,
11755        sa,
11756        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
11757        k,
11758        fs,
11759        threads,
11760        &mut thread_state,
11761    )
11762}
11763
11764#[allow(dead_code)]
11765fn convert_32u_to_64u(s: &[u32], d: &mut [u64], block_start: usize, block_size: usize) {
11766    for i in block_start..block_start + block_size {
11767        d[i] = s[i] as u64;
11768    }
11769}
11770
11771#[allow(dead_code)]
11772fn convert_inplace_32u_to_64u(v: &mut [u32], block_start: usize, block_size: usize) {
11773    for i in (block_start..block_start + block_size).rev() {
11774        let value = v[i];
11775        v[2 * i] = value;
11776        v[2 * i + 1] = 0;
11777    }
11778}
11779
11780#[allow(dead_code)]
11781fn convert_inplace_64u_to_32u(v: &mut [u32], block_start: usize, block_size: usize) {
11782    for i in block_start..block_start + block_size {
11783        v[i] = v[2 * i];
11784    }
11785}
11786
11787#[allow(dead_code)]
11788fn convert_inplace_32u_to_64u_omp(v: &mut [u32], n: SaSint, threads: SaSint) {
11789    let mut n = usize::try_from(n).expect("n must be non-negative");
11790    let threads = usize::try_from(threads.max(1)).expect("threads must be non-negative");
11791
11792    while n >= 65_536 {
11793        let block_size = n >> 1;
11794        n -= block_size;
11795
11796        let omp_block_stride = (block_size / threads) & !15usize;
11797        for thread in 0..threads {
11798            let block_start = thread * omp_block_stride;
11799            let size = if thread + 1 < threads {
11800                omp_block_stride
11801            } else {
11802                block_size - block_start
11803            };
11804            convert_inplace_32u_to_64u(v, n + block_start, size);
11805        }
11806    }
11807
11808    convert_inplace_32u_to_64u(v, 0, n);
11809}
11810
11811fn libsais64_main_ctx(
11812    ctx: &mut Context,
11813    t: &[u8],
11814    sa: &mut [SaSint],
11815    flags: SaSint,
11816    r: SaSint,
11817    i: Option<&mut [SaSint]>,
11818    fs: SaSint,
11819    freq: Option<&mut [SaSint]>,
11820) -> SaSint {
11821    if ctx.threads <= 0 || ctx.buckets.len() != 8 * ALPHABET_SIZE {
11822        return -2;
11823    }
11824
11825    let mut empty_thread_state = [];
11826    let thread_state = if ctx.threads > 1 {
11827        match ctx.thread_state.as_deref_mut() {
11828            Some(thread_state) if thread_state.len() >= ctx.threads as usize => thread_state,
11829            None => return -2,
11830            Some(_) => return -2,
11831        }
11832    } else {
11833        &mut empty_thread_state
11834    };
11835
11836    libsais64_main_8u(
11837        t,
11838        sa,
11839        &mut ctx.buckets,
11840        flags,
11841        r,
11842        i,
11843        fs,
11844        freq,
11845        ctx.threads as SaSint,
11846        thread_state,
11847    )
11848}
11849
11850/// Constructs the suffix array of a given string.
11851///
11852/// # Arguments
11853/// - `t` (`[0..n-1]`): The input string.
11854/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
11855/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
11856/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
11857///
11858/// # Returns
11859/// 0 if no error occurred, -1 or -2 otherwise.
11860pub fn libsais64(t: &[u8], sa: &mut [SaSint], fs: SaSint, freq: Option<&mut [SaSint]>) -> SaSint {
11861    if fs < 0
11862        || sa.len()
11863            < t.len()
11864                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
11865    {
11866        return -1;
11867    }
11868    if let Some(freq) = freq.as_ref() {
11869        if freq.len() < ALPHABET_SIZE {
11870            return -1;
11871        }
11872    }
11873
11874    let n = t.len();
11875    if n <= 1 {
11876        if let Some(freq) = freq {
11877            freq[..ALPHABET_SIZE].fill(0);
11878            if n == 1 {
11879                freq[t[0] as usize] += 1;
11880            }
11881        }
11882        if n == 1 {
11883            sa[0] = 0;
11884        }
11885        return 0;
11886    }
11887
11888    if n <= i32::MAX as usize {
11889        return libsais64_run_32bit_omp(t, sa, fs, freq, 1, false)
11890            .expect("n <= INT32_MAX must have 32-bit workspace");
11891    }
11892
11893    libsais64_main(t, sa, LIBSAIS_FLAGS_NONE, 0, None, fs, freq, 1)
11894}
11895
11896#[cfg(feature = "upstream-c")]
11897unsafe extern "C" {
11898    fn probe_public_libsais64_omp_freq(
11899        t: *const u8,
11900        sa: *mut SaSint,
11901        n: SaSint,
11902        fs: SaSint,
11903        freq: *mut SaSint,
11904        threads: SaSint,
11905    ) -> SaSint;
11906}
11907
11908/// Wrapper around the bundled upstream C `libsais64_omp` implementation.
11909///
11910/// Available only with the `upstream-c` feature. Same semantics as the Rust [`libsais64_omp`] function but defers all work to the C library; intended for the differential test suite and benchmarks.
11911///
11912/// - `t` (`[0..n-1]`): the input string.
11913/// - `sa` (`[0..n-1+fs]`): the output array of suffixes.
11914/// - `fs`: extra space available at the end of `sa`.
11915/// - `freq` (`[0..255]`): optional output symbol frequency table.
11916/// - `threads`: number of worker threads (can be 0 for the implementation default).
11917///
11918/// Returns 0 on success, -1 or -2 on error.
11919#[cfg(feature = "upstream-c")]
11920pub fn libsais64_upstream_c_omp(
11921    t: &[u8],
11922    sa: &mut [SaSint],
11923    fs: SaSint,
11924    freq: Option<&mut [SaSint]>,
11925    threads: SaSint,
11926) -> SaSint {
11927    if threads < 0 {
11928        return -1;
11929    }
11930    if fs < 0
11931        || t.len() > SaSint::MAX as usize
11932        || sa.len()
11933            < t.len()
11934                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
11935    {
11936        return -1;
11937    }
11938    if let Some(freq) = freq.as_ref() {
11939        if freq.len() < ALPHABET_SIZE {
11940            return -1;
11941        }
11942    }
11943
11944    let n = t.len() as SaSint;
11945    let freq_ptr = freq.map_or(std::ptr::null_mut(), |freq| freq.as_mut_ptr());
11946    unsafe {
11947        probe_public_libsais64_omp_freq(
11948            t.as_ptr(),
11949            sa.as_mut_ptr(),
11950            n,
11951            fs,
11952            freq_ptr,
11953            threads.max(1),
11954        )
11955    }
11956}
11957
11958/// `MaybeUninit` variant of [`libsais64_upstream_c_omp`].
11959///
11960/// Available only with the `upstream-c` feature. Identical semantics, except that the `sa` output may be uninitialised on entry — the C implementation always writes every required slot, so the caller may treat the slice as initialised on success.
11961#[cfg(feature = "upstream-c")]
11962pub fn libsais64_upstream_c_omp_uninit(
11963    t: &[u8],
11964    sa: &mut [MaybeUninit<SaSint>],
11965    fs: SaSint,
11966    freq: Option<&mut [SaSint]>,
11967    threads: SaSint,
11968) -> SaSint {
11969    if threads < 0 {
11970        return -1;
11971    }
11972    if fs < 0
11973        || t.len() > SaSint::MAX as usize
11974        || sa.len()
11975            < t.len()
11976                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
11977    {
11978        return -1;
11979    }
11980    if let Some(freq) = freq.as_ref() {
11981        if freq.len() < ALPHABET_SIZE {
11982            return -1;
11983        }
11984    }
11985
11986    let n = t.len() as SaSint;
11987    let freq_ptr = freq.map_or(std::ptr::null_mut(), |freq| freq.as_mut_ptr());
11988    unsafe {
11989        probe_public_libsais64_omp_freq(
11990            t.as_ptr(),
11991            sa.as_mut_ptr().cast::<SaSint>(),
11992            n,
11993            fs,
11994            freq_ptr,
11995            threads.max(1),
11996        )
11997    }
11998}
11999
12000/// Constructs the generalized suffix array (GSA) of given string set.
12001///
12002/// # Arguments
12003/// - `t` (`[0..n-1]`): The input string set using 0 as separators (`t[n-1]` must be 0).
12004/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
12005/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12006/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12007///
12008/// # Returns
12009/// 0 if no error occurred, -1 or -2 otherwise.
12010pub fn libsais64_gsa(
12011    t: &[u8],
12012    sa: &mut [SaSint],
12013    fs: SaSint,
12014    freq: Option<&mut [SaSint]>,
12015) -> SaSint {
12016    if fs < 0
12017        || sa.len()
12018            < t.len()
12019                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12020    {
12021        return -1;
12022    }
12023    if let Some(freq) = freq.as_ref() {
12024        if freq.len() < ALPHABET_SIZE {
12025            return -1;
12026        }
12027    }
12028
12029    let n = t.len();
12030    if n > 0 && t[n - 1] != 0 {
12031        return -1;
12032    }
12033
12034    if n <= 1 {
12035        if let Some(freq) = freq {
12036            freq[..ALPHABET_SIZE].fill(0);
12037            if n == 1 {
12038                freq[t[0] as usize] += 1;
12039            }
12040        }
12041        if n == 1 {
12042            sa[0] = 0;
12043        }
12044        return 0;
12045    }
12046
12047    if n <= i32::MAX as usize {
12048        return libsais64_run_32bit_omp(t, sa, fs, freq, 1, true)
12049            .expect("n <= INT32_MAX must have 32-bit workspace");
12050    }
12051
12052    libsais64_main(t, sa, LIBSAIS_FLAGS_GSA, 0, None, fs, freq, 1)
12053}
12054
12055/// Alias for `libsais64_long`. See its documentation.
12056pub fn libsais64_int(t: &mut [SaSint], sa: &mut [SaSint], k: SaSint, fs: SaSint) -> SaSint {
12057    if fs < 0
12058        || sa.len()
12059            < t.len()
12060                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12061    {
12062        return -1;
12063    }
12064
12065    if t.len() <= 1 {
12066        if t.len() == 1 {
12067            sa[0] = 0;
12068        }
12069        return 0;
12070    }
12071
12072    libsais64_main_int(t, sa, k, fs, 1)
12073}
12074
12075/// Constructs the suffix array of a given integer array.
12076///
12077/// Note, during construction input array will be modified, but restored at the end if no errors occurred.
12078///
12079/// # Arguments
12080/// - `t` (`[0..n-1]`): The input integer array.
12081/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
12082/// - `k`: The alphabet size of the input integer array.
12083/// - `fs`: Extra space available at the end of SA array (can be 0, but 4k or better 6k is recommended for optimal performance).
12084///
12085/// # Returns
12086/// 0 if no error occurred, -1 or -2 otherwise.
12087pub fn libsais64_long(t: &mut [SaSint], sa: &mut [SaSint], k: SaSint, fs: SaSint) -> SaSint {
12088    libsais64_int(t, sa, k, fs)
12089}
12090
12091/// Constructs the suffix array of a given string using a libsais64 context.
12092///
12093/// - `ctx`: the libsais64 context.
12094/// - `t` (`[0..n-1]`): the input string.
12095/// - `sa` (`[0..n-1+fs]`): the output array of suffixes.
12096/// - `fs`: extra space available at the end of `sa` (0 should be enough for most cases).
12097/// - `freq` (`[0..255]`): optional output symbol frequency table.
12098///
12099/// Returns 0 on success, -1 or -2 on error.
12100pub fn libsais64_ctx(
12101    ctx: &mut Context,
12102    t: &[u8],
12103    sa: &mut [SaSint],
12104    fs: SaSint,
12105    freq: Option<&mut [SaSint]>,
12106) -> SaSint {
12107    if fs < 0
12108        || sa.len()
12109            < t.len()
12110                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12111    {
12112        return -1;
12113    }
12114    if let Some(freq) = freq.as_ref() {
12115        if freq.len() < ALPHABET_SIZE {
12116            return -1;
12117        }
12118    }
12119
12120    let n = t.len();
12121    if n <= 1 {
12122        if let Some(freq) = freq {
12123            freq[..ALPHABET_SIZE].fill(0);
12124            if n == 1 {
12125                freq[t[0] as usize] += 1;
12126            }
12127        }
12128        if n == 1 {
12129            sa[0] = 0;
12130        }
12131        return 0;
12132    }
12133
12134    libsais64_main_ctx(ctx, t, sa, LIBSAIS_FLAGS_NONE, 0, None, fs, freq)
12135}
12136
12137/// Constructs the generalized suffix array (GSA) of a given string set using a libsais64 context.
12138///
12139/// - `ctx`: the libsais64 context.
12140/// - `t` (`[0..n-1]`): the input string set using 0 as separators (`t[n-1]` must be 0).
12141/// - `sa` (`[0..n-1+fs]`): the output array of suffixes.
12142/// - `fs`: extra space available at the end of `sa` (0 should be enough for most cases).
12143/// - `freq` (`[0..255]`): optional output symbol frequency table.
12144///
12145/// Returns 0 on success, -1 or -2 on error.
12146pub fn libsais64_gsa_ctx(
12147    ctx: &mut Context,
12148    t: &[u8],
12149    sa: &mut [SaSint],
12150    fs: SaSint,
12151    freq: Option<&mut [SaSint]>,
12152) -> SaSint {
12153    if fs < 0
12154        || sa.len()
12155            < t.len()
12156                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12157    {
12158        return -1;
12159    }
12160    if let Some(freq) = freq.as_ref() {
12161        if freq.len() < ALPHABET_SIZE {
12162            return -1;
12163        }
12164    }
12165
12166    let n = t.len();
12167    if n > 0 && t[n - 1] != 0 {
12168        return -1;
12169    }
12170
12171    if n <= 1 {
12172        if let Some(freq) = freq {
12173            freq[..ALPHABET_SIZE].fill(0);
12174            if n == 1 {
12175                freq[t[0] as usize] += 1;
12176            }
12177        }
12178        if n == 1 {
12179            sa[0] = 0;
12180        }
12181        return 0;
12182    }
12183
12184    libsais64_main_ctx(ctx, t, sa, LIBSAIS_FLAGS_GSA, 0, None, fs, freq)
12185}
12186
12187/// Constructs the burrows-wheeler transformed string (BWT) of a given string.
12188///
12189/// # Arguments
12190/// - `t` (`[0..n-1]`): The input string.
12191/// - `u` (`[0..n-1]`): The output string (can be `t`).
12192/// - `a` (`[0..n-1+fs]`): The temporary array.
12193/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12194/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12195///
12196/// # Returns
12197/// The primary index if no error occurred, -1 or -2 otherwise.
12198pub fn libsais64_bwt(
12199    t: &[u8],
12200    u: &mut [u8],
12201    a: &mut [SaSint],
12202    fs: SaSint,
12203    freq: Option<&mut [SaSint]>,
12204) -> SaSint {
12205    if fs < 0
12206        || u.len() < t.len()
12207        || a.len()
12208            < t.len()
12209                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12210    {
12211        return -1;
12212    }
12213    if let Some(freq) = freq.as_ref() {
12214        if freq.len() < ALPHABET_SIZE {
12215            return -1;
12216        }
12217    }
12218
12219    let n = t.len();
12220    if n <= 1 {
12221        if let Some(freq) = freq {
12222            freq[..ALPHABET_SIZE].fill(0);
12223            if n == 1 {
12224                u[0] = t[0];
12225                freq[t[0] as usize] += 1;
12226            }
12227        } else if n == 1 {
12228            u[0] = t[0];
12229        }
12230        return n as SaSint;
12231    }
12232
12233    if n <= i32::MAX as usize {
12234        return libsais64_bwt_run_32bit_omp(t, u, fs, freq, 1)
12235            .expect("n <= INT32_MAX must have 32-bit workspace");
12236    }
12237
12238    let mut index = libsais64_main(t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq, 1);
12239    if index >= 0 {
12240        index += 1;
12241        let split = usize::try_from(index).expect("index must be non-negative");
12242        u[0] = t[n - 1];
12243        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], index - 1, 1);
12244        bwt_copy_8u_omp(
12245            &mut u[split..n],
12246            &a[split..n],
12247            SaSint::try_from(n - split).expect("fits"),
12248            1,
12249        );
12250    }
12251    index
12252}
12253
12254/// Constructs the burrows-wheeler transformed string (BWT) of a given string with auxiliary indexes.
12255///
12256/// # Arguments
12257/// - `t` (`[0..n-1]`): The input string.
12258/// - `u` (`[0..n-1]`): The output string (can be `t`).
12259/// - `a` (`[0..n-1+fs]`): The temporary array.
12260/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12261/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12262/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
12263/// - `i` (`[0..(n-1)/r]`): The output auxiliary indexes.
12264///
12265/// # Returns
12266/// 0 if no error occurred, -1 or -2 otherwise.
12267pub fn libsais64_bwt_aux(
12268    t: &[u8],
12269    u: &mut [u8],
12270    a: &mut [SaSint],
12271    fs: SaSint,
12272    freq: Option<&mut [SaSint]>,
12273    r: SaSint,
12274    i: &mut [SaSint],
12275) -> SaSint {
12276    let n = t.len();
12277    if fs < 0
12278        || r < 2
12279        || (r & (r - 1)) != 0
12280        || u.len() < n
12281        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12282        || freq.as_ref().is_some_and(|freq| freq.len() < ALPHABET_SIZE)
12283    {
12284        return -1;
12285    }
12286    let sample_count = if n == 0 {
12287        1
12288    } else {
12289        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
12290            .expect("sample count must be non-negative")
12291            + 1
12292    };
12293    if i.len() < sample_count {
12294        return -1;
12295    }
12296
12297    if n <= 1 {
12298        if let Some(freq) = freq {
12299            freq[..ALPHABET_SIZE].fill(0);
12300            if n == 1 {
12301                u[0] = t[0];
12302                freq[t[0] as usize] += 1;
12303            }
12304        } else if n == 1 {
12305            u[0] = t[0];
12306        }
12307        i[0] = n as SaSint;
12308        return 0;
12309    }
12310
12311    if n <= i32::MAX as usize && r <= i32::MAX as SaSint {
12312        return libsais64_bwt_aux_run_32bit_omp(t, u, fs, freq, r, i, 1)
12313            .expect("n/r <= INT32_MAX must have 32-bit workspace");
12314    }
12315
12316    let index = libsais64_main(t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq, 1);
12317    if index == 0 {
12318        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
12319        u[0] = t[n - 1];
12320        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], i[0] - 1, 1);
12321        bwt_copy_8u_omp(
12322            &mut u[split..n],
12323            &a[split..n],
12324            SaSint::try_from(n - split).expect("fits"),
12325            1,
12326        );
12327    }
12328    index
12329}
12330
12331/// Constructs the Burrows-Wheeler transformed string (BWT) of a given string using a libsais64 context.
12332///
12333/// - `ctx`: the libsais64 context.
12334/// - `t` (`[0..n-1]`): the input string.
12335/// - `u` (`[0..n-1]`): the output string (can alias `t`).
12336/// - `a` (`[0..n-1+fs]`): the temporary array.
12337/// - `fs`: extra space available at the end of `a` (0 should be enough for most cases).
12338/// - `freq` (`[0..255]`): optional output symbol frequency table.
12339///
12340/// Returns the primary index on success, -1 or -2 on error.
12341pub fn libsais64_bwt_ctx(
12342    ctx: &mut Context,
12343    t: &[u8],
12344    u: &mut [u8],
12345    a: &mut [SaSint],
12346    fs: SaSint,
12347    freq: Option<&mut [SaSint]>,
12348) -> SaSint {
12349    if fs < 0
12350        || u.len() < t.len()
12351        || a.len()
12352            < t.len()
12353                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12354    {
12355        return -1;
12356    }
12357    if let Some(freq) = freq.as_ref() {
12358        if freq.len() < ALPHABET_SIZE {
12359            return -1;
12360        }
12361    }
12362
12363    let n = t.len();
12364    if n <= 1 {
12365        if let Some(freq) = freq {
12366            freq[..ALPHABET_SIZE].fill(0);
12367            if n == 1 {
12368                u[0] = t[0];
12369                freq[t[0] as usize] += 1;
12370            }
12371        } else if n == 1 {
12372            u[0] = t[0];
12373        }
12374        return n as SaSint;
12375    }
12376
12377    let mut index = libsais64_main_ctx(ctx, t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq);
12378    if index >= 0 {
12379        index += 1;
12380        let split = usize::try_from(index).expect("index must be non-negative");
12381        u[0] = t[n - 1];
12382        bwt_copy_8u_omp(
12383            &mut u[1..split],
12384            &a[..split - 1],
12385            index - 1,
12386            ctx.threads as SaSint,
12387        );
12388        bwt_copy_8u_omp(
12389            &mut u[split..n],
12390            &a[split..n],
12391            SaSint::try_from(n - split).expect("fits"),
12392            ctx.threads as SaSint,
12393        );
12394    }
12395    index
12396}
12397
12398/// Constructs the BWT of a given string with auxiliary indexes using a libsais64 context.
12399///
12400/// - `ctx`: the libsais64 context.
12401/// - `t` (`[0..n-1]`): the input string.
12402/// - `u` (`[0..n-1]`): the output string (can alias `t`).
12403/// - `a` (`[0..n-1+fs]`): the temporary array.
12404/// - `fs`: extra space available at the end of `a` (0 should be enough for most cases).
12405/// - `freq` (`[0..255]`): optional output symbol frequency table.
12406/// - `r`: sampling rate for the auxiliary indexes (must be a power of two).
12407/// - `i` (`[0..(n-1)/r]`): output auxiliary indexes.
12408///
12409/// Returns 0 on success, -1 or -2 on error.
12410pub fn libsais64_bwt_aux_ctx(
12411    ctx: &mut Context,
12412    t: &[u8],
12413    u: &mut [u8],
12414    a: &mut [SaSint],
12415    fs: SaSint,
12416    freq: Option<&mut [SaSint]>,
12417    r: SaSint,
12418    i: &mut [SaSint],
12419) -> SaSint {
12420    let n = t.len();
12421    if fs < 0
12422        || r < 2
12423        || (r & (r - 1)) != 0
12424        || u.len() < n
12425        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12426    {
12427        return -1;
12428    }
12429    if let Some(freq) = freq.as_ref() {
12430        if freq.len() < ALPHABET_SIZE {
12431            return -1;
12432        }
12433    }
12434    let sample_count = if n == 0 {
12435        1
12436    } else {
12437        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
12438            .expect("sample count must be non-negative")
12439            + 1
12440    };
12441    if i.len() < sample_count {
12442        return -1;
12443    }
12444
12445    if n <= 1 {
12446        if let Some(freq) = freq {
12447            freq[..ALPHABET_SIZE].fill(0);
12448            if n == 1 {
12449                u[0] = t[0];
12450                freq[t[0] as usize] += 1;
12451            }
12452        } else if n == 1 {
12453            u[0] = t[0];
12454        }
12455        i[0] = n as SaSint;
12456        return 0;
12457    }
12458
12459    let index = libsais64_main_ctx(ctx, t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq);
12460    if index == 0 {
12461        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
12462        u[0] = t[n - 1];
12463        bwt_copy_8u_omp(
12464            &mut u[1..split],
12465            &a[..split - 1],
12466            i[0] - 1,
12467            ctx.threads as SaSint,
12468        );
12469        bwt_copy_8u_omp(
12470            &mut u[split..n],
12471            &a[split..n],
12472            SaSint::try_from(n - split).expect("fits"),
12473            ctx.threads as SaSint,
12474        );
12475    }
12476    index
12477}
12478
12479/// Creates the libsais64 context for parallel operations using OpenMP-style threading.
12480///
12481/// In multi-threaded environments, use one context per thread for parallel executions.
12482///
12483/// - `threads`: number of worker threads (can be 0 for the implementation default).
12484///
12485/// Returns the context, or `None` on allocation failure.
12486pub fn create_ctx_omp(threads: SaSint) -> Option<Context> {
12487    if threads < 0 {
12488        return None;
12489    }
12490
12491    create_ctx_main(normalize_omp_threads(threads))
12492}
12493
12494fn libsais64_new_32bit_fs(n: usize, fs: SaSint) -> Option<i32> {
12495    if n > i32::MAX as usize {
12496        return None;
12497    }
12498
12499    let n = n as SaSint;
12500    let int32_max = i32::MAX as SaSint;
12501    let expanded_space = fs as i128 + fs as i128 + n as i128 + n as i128;
12502    let new_fs = if expanded_space <= int32_max as i128 {
12503        fs + fs + n
12504    } else {
12505        int32_max - n
12506    };
12507
12508    i32::try_from(new_fs).ok()
12509}
12510
12511fn libsais64_run_32bit_omp(
12512    t: &[u8],
12513    sa: &mut [SaSint],
12514    fs: SaSint,
12515    freq: Option<&mut [SaSint]>,
12516    threads: SaSint,
12517    gsa: bool,
12518) -> Option<SaSint> {
12519    let new_fs = libsais64_new_32bit_fs(t.len(), fs)?;
12520    let mut sa32 = vec![0i32; t.len() + usize::try_from(new_fs).expect("new_fs is non-negative")];
12521
12522    let index = if let Some(freq) = freq {
12523        let mut freq32 = vec![0i32; ALPHABET_SIZE];
12524        let index = if gsa {
12525            crate::libsais_gsa_omp(t, &mut sa32, new_fs, Some(&mut freq32), threads as i32)
12526        } else {
12527            crate::libsais_omp(t, &mut sa32, new_fs, Some(&mut freq32), threads as i32)
12528        };
12529        if index >= 0 {
12530            for (dst, src) in freq.iter_mut().zip(freq32.iter()) {
12531                *dst = SaSint::from(*src);
12532            }
12533        }
12534        index
12535    } else if gsa {
12536        crate::libsais_gsa_omp(t, &mut sa32, new_fs, None, threads as i32)
12537    } else {
12538        crate::libsais_omp(t, &mut sa32, new_fs, None, threads as i32)
12539    };
12540
12541    if index >= 0 {
12542        for (dst, src) in sa.iter_mut().zip(sa32.iter()).take(t.len()) {
12543            *dst = SaSint::from(*src as u32);
12544        }
12545    }
12546
12547    Some(SaSint::from(index))
12548}
12549
12550fn copy_freq32_to_64(freq: &mut [SaSint], freq32: &[i32]) {
12551    for (dst, src) in freq.iter_mut().zip(freq32.iter()).take(ALPHABET_SIZE) {
12552        *dst = SaSint::from(*src);
12553    }
12554}
12555
12556fn libsais64_bwt_run_32bit_omp(
12557    t: &[u8],
12558    u: &mut [u8],
12559    fs: SaSint,
12560    freq: Option<&mut [SaSint]>,
12561    threads: SaSint,
12562) -> Option<SaSint> {
12563    let new_fs = libsais64_new_32bit_fs(t.len(), fs)?;
12564    let mut a32 = vec![0i32; t.len() + usize::try_from(new_fs).expect("new_fs is non-negative")];
12565
12566    let index = if let Some(freq) = freq {
12567        let mut freq32 = vec![0i32; ALPHABET_SIZE];
12568        let index =
12569            crate::libsais_bwt_omp(t, u, &mut a32, new_fs, Some(&mut freq32), threads as i32);
12570        if index >= 0 {
12571            copy_freq32_to_64(freq, &freq32);
12572        }
12573        index
12574    } else {
12575        crate::libsais_bwt_omp(t, u, &mut a32, new_fs, None, threads as i32)
12576    };
12577
12578    Some(SaSint::from(index))
12579}
12580
12581fn libsais64_bwt_aux_run_32bit_omp(
12582    t: &[u8],
12583    u: &mut [u8],
12584    fs: SaSint,
12585    freq: Option<&mut [SaSint]>,
12586    r: SaSint,
12587    i: &mut [SaSint],
12588    threads: SaSint,
12589) -> Option<SaSint> {
12590    if r > i32::MAX as SaSint {
12591        return None;
12592    }
12593
12594    let new_fs = libsais64_new_32bit_fs(t.len(), fs)?;
12595    let mut a32 = vec![0i32; t.len() + usize::try_from(new_fs).expect("new_fs is non-negative")];
12596    let sample_count = if t.is_empty() {
12597        1
12598    } else {
12599        (t.len() - 1) / usize::try_from(r).expect("r must be positive") + 1
12600    };
12601    let mut i32_out = vec![0i32; sample_count];
12602
12603    let index = if let Some(freq) = freq {
12604        let mut freq32 = vec![0i32; ALPHABET_SIZE];
12605        let index = crate::libsais_bwt_aux_omp(
12606            t,
12607            u,
12608            &mut a32,
12609            new_fs,
12610            Some(&mut freq32),
12611            r as i32,
12612            &mut i32_out,
12613            threads as i32,
12614        );
12615        if index >= 0 {
12616            copy_freq32_to_64(freq, &freq32);
12617        }
12618        index
12619    } else {
12620        crate::libsais_bwt_aux_omp(
12621            t,
12622            u,
12623            &mut a32,
12624            new_fs,
12625            None,
12626            r as i32,
12627            &mut i32_out,
12628            threads as i32,
12629        )
12630    };
12631
12632    if index >= 0 {
12633        for (dst, src) in i.iter_mut().zip(i32_out.iter()).take(sample_count) {
12634            *dst = SaSint::from(*src);
12635        }
12636    }
12637
12638    Some(SaSint::from(index))
12639}
12640
12641/// Constructs the suffix array of a given string in parallel using OpenMP.
12642///
12643/// # Arguments
12644/// - `t` (`[0..n-1]`): The input string.
12645/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
12646/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12647/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12648/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12649///
12650/// # Returns
12651/// 0 if no error occurred, -1 or -2 otherwise.
12652pub fn libsais64_omp(
12653    t: &[u8],
12654    sa: &mut [SaSint],
12655    fs: SaSint,
12656    freq: Option<&mut [SaSint]>,
12657    threads: SaSint,
12658) -> SaSint {
12659    if threads < 0 {
12660        return -1;
12661    }
12662    if fs < 0
12663        || sa.len()
12664            < t.len()
12665                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12666    {
12667        return -1;
12668    }
12669    if let Some(freq) = freq.as_ref() {
12670        if freq.len() < ALPHABET_SIZE {
12671            return -1;
12672        }
12673    }
12674    let n = t.len();
12675    if n <= 1 {
12676        if let Some(freq) = freq {
12677            freq[..ALPHABET_SIZE].fill(0);
12678            if n == 1 {
12679                sa[0] = 0;
12680                freq[t[0] as usize] += 1;
12681            }
12682        } else if n == 1 {
12683            sa[0] = 0;
12684        }
12685        return 0;
12686    }
12687
12688    let threads = normalize_omp_threads(threads);
12689    if n <= i32::MAX as usize {
12690        return libsais64_run_32bit_omp(t, sa, fs, freq, threads, false)
12691            .expect("n <= INT32_MAX must have 32-bit workspace");
12692    }
12693
12694    libsais64_main(t, sa, LIBSAIS_FLAGS_NONE, 0, None, fs, freq, threads)
12695}
12696
12697/// Constructs the generalized suffix array (GSA) of given string set in parallel using OpenMP.
12698///
12699/// # Arguments
12700/// - `t` (`[0..n-1]`): The input string set using 0 as separators (`t[n-1]` must be 0).
12701/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
12702/// - `fs`: The extra space available at the end of SA array (0 should be enough for most cases).
12703/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12704/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12705///
12706/// # Returns
12707/// 0 if no error occurred, -1 or -2 otherwise.
12708pub fn libsais64_gsa_omp(
12709    t: &[u8],
12710    sa: &mut [SaSint],
12711    fs: SaSint,
12712    freq: Option<&mut [SaSint]>,
12713    threads: SaSint,
12714) -> SaSint {
12715    if threads < 0
12716        || t.last().copied().unwrap_or(0) != 0
12717        || fs < 0
12718        || sa.len()
12719            < t.len()
12720                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12721    {
12722        return -1;
12723    }
12724    if let Some(freq) = freq.as_ref() {
12725        if freq.len() < ALPHABET_SIZE {
12726            return -1;
12727        }
12728    }
12729    let n = t.len();
12730    if n <= 1 {
12731        if let Some(freq) = freq {
12732            freq[..ALPHABET_SIZE].fill(0);
12733            if n == 1 {
12734                sa[0] = 0;
12735                freq[t[0] as usize] += 1;
12736            }
12737        } else if n == 1 {
12738            sa[0] = 0;
12739        }
12740        return 0;
12741    }
12742
12743    let threads = normalize_omp_threads(threads);
12744    if n <= i32::MAX as usize {
12745        return libsais64_run_32bit_omp(t, sa, fs, freq, threads, true)
12746            .expect("n <= INT32_MAX must have 32-bit workspace");
12747    }
12748
12749    libsais64_main(t, sa, LIBSAIS_FLAGS_GSA, 0, None, fs, freq, threads)
12750}
12751
12752/// Alias for `libsais64_long_omp`. See its documentation.
12753pub fn libsais64_int_omp(
12754    t: &mut [SaSint],
12755    sa: &mut [SaSint],
12756    k: SaSint,
12757    fs: SaSint,
12758    threads: SaSint,
12759) -> SaSint {
12760    if threads < 0 {
12761        return -1;
12762    }
12763    if fs < 0
12764        || sa.len()
12765            < t.len()
12766                .saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12767    {
12768        return -1;
12769    }
12770    if t.len() <= 1 {
12771        if t.len() == 1 {
12772            sa[0] = 0;
12773        }
12774        return 0;
12775    }
12776
12777    libsais64_main_int(t, sa, k, fs, normalize_omp_threads(threads))
12778}
12779
12780/// Constructs the suffix array of a given integer array in parallel using OpenMP.
12781///
12782/// Note, during construction input array will be modified, but restored at the end if no errors occurred.
12783///
12784/// # Arguments
12785/// - `t` (`[0..n-1]`): The input integer array.
12786/// - `sa` (`[0..n-1+fs]`): The output array of suffixes.
12787/// - `k`: The alphabet size of the input integer array.
12788/// - `fs`: Extra space available at the end of SA array (can be 0, but 4k or better 6k is recommended for optimal performance).
12789/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12790///
12791/// # Returns
12792/// 0 if no error occurred, -1 or -2 otherwise.
12793pub fn libsais64_long_omp(
12794    t: &mut [SaSint],
12795    sa: &mut [SaSint],
12796    k: SaSint,
12797    fs: SaSint,
12798    threads: SaSint,
12799) -> SaSint {
12800    libsais64_int_omp(t, sa, k, fs, threads)
12801}
12802
12803/// Constructs the burrows-wheeler transformed string (BWT) of a given string in parallel using OpenMP.
12804///
12805/// # Arguments
12806/// - `t` (`[0..n-1]`): The input string.
12807/// - `u` (`[0..n-1]`): The output string (can be `t`).
12808/// - `a` (`[0..n-1+fs]`): The temporary array.
12809/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12810/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12811/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12812///
12813/// # Returns
12814/// The primary index if no error occurred, -1 or -2 otherwise.
12815pub fn libsais64_bwt_omp(
12816    t: &[u8],
12817    u: &mut [u8],
12818    a: &mut [SaSint],
12819    fs: SaSint,
12820    freq: Option<&mut [SaSint]>,
12821    threads: SaSint,
12822) -> SaSint {
12823    let n = t.len();
12824    if threads < 0
12825        || fs < 0
12826        || u.len() < n
12827        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12828        || freq.as_ref().is_some_and(|freq| freq.len() < ALPHABET_SIZE)
12829    {
12830        return -1;
12831    }
12832
12833    if n <= 1 {
12834        if let Some(freq) = freq {
12835            freq[..ALPHABET_SIZE].fill(0);
12836            if n == 1 {
12837                u[0] = t[0];
12838                freq[t[0] as usize] += 1;
12839            }
12840        } else if n == 1 {
12841            u[0] = t[0];
12842        }
12843        return n as SaSint;
12844    }
12845
12846    let threads = normalize_omp_threads(threads);
12847    if n <= i32::MAX as usize {
12848        return libsais64_bwt_run_32bit_omp(t, u, fs, freq, threads)
12849            .expect("n <= INT32_MAX must have 32-bit workspace");
12850    }
12851
12852    let mut index = libsais64_main(t, a, LIBSAIS_FLAGS_BWT, 0, None, fs, freq, threads);
12853    if index >= 0 {
12854        index += 1;
12855        let index_usize = usize::try_from(index).expect("index must be non-negative");
12856        u[0] = t[n - 1];
12857        bwt_copy_8u_omp(
12858            &mut u[1..index_usize],
12859            &a[..index_usize - 1],
12860            index - 1,
12861            threads,
12862        );
12863        bwt_copy_8u_omp(
12864            &mut u[index_usize..n],
12865            &a[index_usize..n],
12866            SaSint::try_from(n - index_usize).expect("fits"),
12867            threads,
12868        );
12869    }
12870    index
12871}
12872
12873/// Constructs the burrows-wheeler transformed string (BWT) of a given string with auxiliary indexes in parallel using OpenMP.
12874///
12875/// # Arguments
12876/// - `t` (`[0..n-1]`): The input string.
12877/// - `u` (`[0..n-1]`): The output string (can be `t`).
12878/// - `a` (`[0..n-1+fs]`): The temporary array.
12879/// - `fs`: The extra space available at the end of A array (0 should be enough for most cases).
12880/// - `freq` (`[0..255]`): The output symbol frequency table (can be `None`).
12881/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
12882/// - `i` (`[0..(n-1)/r]`): The output auxiliary indexes.
12883/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
12884///
12885/// # Returns
12886/// 0 if no error occurred, -1 or -2 otherwise.
12887pub fn libsais64_bwt_aux_omp(
12888    t: &[u8],
12889    u: &mut [u8],
12890    a: &mut [SaSint],
12891    fs: SaSint,
12892    freq: Option<&mut [SaSint]>,
12893    r: SaSint,
12894    i: &mut [SaSint],
12895    threads: SaSint,
12896) -> SaSint {
12897    let n = t.len();
12898    if threads < 0
12899        || fs < 0
12900        || r < 2
12901        || (r & (r - 1)) != 0
12902        || u.len() < n
12903        || a.len() < n.saturating_add(usize::try_from(fs).unwrap_or(usize::MAX))
12904    {
12905        return -1;
12906    }
12907    if let Some(freq) = freq.as_ref() {
12908        if freq.len() < ALPHABET_SIZE {
12909            return -1;
12910        }
12911    }
12912    let sample_count = if n == 0 {
12913        1
12914    } else {
12915        usize::try_from((SaSint::try_from(n).expect("input length must fit SaSint") - 1) / r)
12916            .expect("sample count must be non-negative")
12917            + 1
12918    };
12919    if i.len() < sample_count {
12920        return -1;
12921    }
12922    if n <= 1 {
12923        if let Some(freq) = freq {
12924            freq[..ALPHABET_SIZE].fill(0);
12925            if n == 1 {
12926                u[0] = t[0];
12927                freq[t[0] as usize] += 1;
12928            }
12929        } else if n == 1 {
12930            u[0] = t[0];
12931        }
12932        i[0] = n as SaSint;
12933        return 0;
12934    }
12935
12936    let threads = normalize_omp_threads(threads);
12937    if n <= i32::MAX as usize && r <= i32::MAX as SaSint {
12938        return libsais64_bwt_aux_run_32bit_omp(t, u, fs, freq, r, i, threads)
12939            .expect("n/r <= INT32_MAX must have 32-bit workspace");
12940    }
12941
12942    let index = libsais64_main(t, a, LIBSAIS_FLAGS_BWT, r, Some(i), fs, freq, threads);
12943    if index == 0 {
12944        let split = usize::try_from(i[0]).expect("primary index must be non-negative");
12945        u[0] = t[n - 1];
12946        bwt_copy_8u_omp(&mut u[1..split], &a[..split - 1], i[0] - 1, threads);
12947        bwt_copy_8u_omp(
12948            &mut u[split..n],
12949            &a[split..n],
12950            SaSint::try_from(n - split).expect("fits"),
12951            threads,
12952        );
12953    }
12954    index
12955}
12956
12957/// Internal helper: compute phi.
12958#[doc(hidden)]
12959pub fn compute_phi(
12960    sa: &[SaSint],
12961    plcp: &mut [SaSint],
12962    n: SaSint,
12963    omp_block_start: FastSint,
12964    omp_block_size: FastSint,
12965) {
12966    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
12967    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
12968    let end = start + size;
12969    let n_usize = usize::try_from(n).expect("n must be non-negative");
12970    let mut i = start;
12971    let mut k = if omp_block_start > 0 {
12972        sa[start - 1]
12973    } else {
12974        n
12975    };
12976
12977    let fast_end = omp_block_start + omp_block_size - 64 - 3;
12978    while (i as FastSint) < fast_end {
12979        plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")] = k;
12980        k = sa[i];
12981        plcp[usize::try_from(sa[i + 1]).expect("suffix index must be non-negative")] = k;
12982        k = sa[i + 1];
12983        plcp[usize::try_from(sa[i + 2]).expect("suffix index must be non-negative")] = k;
12984        k = sa[i + 2];
12985        plcp[usize::try_from(sa[i + 3]).expect("suffix index must be non-negative")] = k;
12986        k = sa[i + 3];
12987        i += 4;
12988    }
12989
12990    while i < end.min(n_usize) {
12991        plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")] = k;
12992        k = sa[i];
12993        i += 1;
12994    }
12995}
12996
12997/// Internal helper: compute phi (OpenMP variant).
12998#[doc(hidden)]
12999pub fn compute_phi_omp(sa: &[SaSint], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13000    if threads == 1 || n < 65_536 {
13001        compute_phi(sa, plcp, n, 0, n as FastSint);
13002        return;
13003    }
13004
13005    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13006    let block_stride = ((n as FastSint) / (threads as FastSint)) & !15;
13007    let plcp_addr = plcp.as_mut_ptr() as usize;
13008    let n_usize = usize::try_from(n).expect("n must be non-negative");
13009
13010    run_rayon_with_threads(threads_usize, || {
13011        (0..threads_usize).into_par_iter().for_each(|thread| {
13012            let block_start = thread as FastSint * block_stride;
13013            let block_size = if thread + 1 < threads_usize {
13014                block_stride
13015            } else {
13016                n as FastSint - block_start
13017            };
13018            let start = usize::try_from(block_start).expect("omp_block_start must be non-negative");
13019            let size = usize::try_from(block_size).expect("omp_block_size must be non-negative");
13020            let end = start + size;
13021            let mut i = start;
13022            let mut k = if block_start > 0 { sa[start - 1] } else { n };
13023            let plcp_ptr = plcp_addr as *mut SaSint;
13024
13025            let fast_end = block_start + block_size - 64 - 3;
13026            while (i as FastSint) < fast_end {
13027                unsafe {
13028                    // SA is a suffix-array permutation, so each thread writes a disjoint PLCP slot.
13029                    *plcp_ptr
13030                        .add(usize::try_from(sa[i]).expect("suffix index must be non-negative")) =
13031                        k;
13032                    k = sa[i];
13033                    *plcp_ptr.add(
13034                        usize::try_from(sa[i + 1]).expect("suffix index must be non-negative"),
13035                    ) = k;
13036                    k = sa[i + 1];
13037                    *plcp_ptr.add(
13038                        usize::try_from(sa[i + 2]).expect("suffix index must be non-negative"),
13039                    ) = k;
13040                    k = sa[i + 2];
13041                    *plcp_ptr.add(
13042                        usize::try_from(sa[i + 3]).expect("suffix index must be non-negative"),
13043                    ) = k;
13044                    k = sa[i + 3];
13045                }
13046                i += 4;
13047            }
13048
13049            while i < end.min(n_usize) {
13050                unsafe {
13051                    // SA is a suffix-array permutation, so each thread writes a disjoint PLCP slot.
13052                    *plcp_ptr
13053                        .add(usize::try_from(sa[i]).expect("suffix index must be non-negative")) =
13054                        k;
13055                }
13056                k = sa[i];
13057                i += 1;
13058            }
13059        });
13060    });
13061}
13062
13063/// Internal helper: compute plcp.
13064#[doc(hidden)]
13065pub fn compute_plcp(
13066    t: &[u8],
13067    plcp: &mut [SaSint],
13068    n: FastSint,
13069    omp_block_start: FastSint,
13070    omp_block_size: FastSint,
13071) {
13072    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13073    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13074    let end = start + size;
13075    let n_usize = usize::try_from(n).expect("n must be non-negative");
13076    let mut l = 0usize;
13077
13078    for i in start..end.min(n_usize) {
13079        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13080        let m = n_usize - i.max(k);
13081        while l < m && t[i + l] == t[k + l] {
13082            l += 1;
13083        }
13084        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13085        l = l.saturating_sub(1);
13086    }
13087}
13088
13089/// Internal helper: compute plcp (OpenMP variant).
13090#[doc(hidden)]
13091pub fn compute_plcp_omp(t: &[u8], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13092    if threads == 1 || n < 65_536 {
13093        compute_plcp(t, plcp, n as FastSint, 0, n as FastSint);
13094        return;
13095    }
13096
13097    let n_usize = usize::try_from(n).expect("n must be non-negative");
13098    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13099    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13100    run_rayon_with_threads(threads_usize, || {
13101        plcp[..n_usize]
13102            .par_chunks_mut(chunk_size)
13103            .enumerate()
13104            .for_each(|(chunk_index, chunk)| {
13105                let start = chunk_index * chunk_size;
13106                let mut l = 0usize;
13107                for (offset, value) in chunk.iter_mut().enumerate() {
13108                    let i = start + offset;
13109                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13110                    let m = n_usize - i.max(k);
13111                    while l < m && t[i + l] == t[k + l] {
13112                        l += 1;
13113                    }
13114                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13115                    l = l.saturating_sub(1);
13116                }
13117            });
13118    });
13119}
13120
13121/// Internal helper: compute plcp gsa.
13122#[doc(hidden)]
13123pub fn compute_plcp_gsa(
13124    t: &[u8],
13125    plcp: &mut [SaSint],
13126    omp_block_start: FastSint,
13127    omp_block_size: FastSint,
13128) {
13129    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13130    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13131    let end = start + size;
13132    let mut l = 0usize;
13133
13134    for i in start..end.min(t.len()) {
13135        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13136        while t[i + l] > 0 && t[i + l] == t[k + l] {
13137            l += 1;
13138        }
13139        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13140        l = l.saturating_sub(1);
13141    }
13142}
13143
13144/// Internal helper: compute plcp gsa (OpenMP variant).
13145#[doc(hidden)]
13146pub fn compute_plcp_gsa_omp(t: &[u8], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13147    if threads == 1 || n < 65_536 {
13148        compute_plcp_gsa(t, plcp, 0, n as FastSint);
13149        return;
13150    }
13151
13152    let n_usize = usize::try_from(n).expect("n must be non-negative");
13153    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13154    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13155    run_rayon_with_threads(threads_usize, || {
13156        plcp[..n_usize]
13157            .par_chunks_mut(chunk_size)
13158            .enumerate()
13159            .for_each(|(chunk_index, chunk)| {
13160                let start = chunk_index * chunk_size;
13161                let mut l = 0usize;
13162                for (offset, value) in chunk.iter_mut().enumerate() {
13163                    let i = start + offset;
13164                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13165                    while t[i + l] > 0 && t[i + l] == t[k + l] {
13166                        l += 1;
13167                    }
13168                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13169                    l = l.saturating_sub(1);
13170                }
13171            });
13172    });
13173}
13174
13175/// Internal helper: compute plcp int.
13176#[doc(hidden)]
13177pub fn compute_plcp_int(
13178    t: &[SaSint],
13179    plcp: &mut [SaSint],
13180    n: FastSint,
13181    omp_block_start: FastSint,
13182    omp_block_size: FastSint,
13183) {
13184    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13185    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13186    let end = start + size;
13187    let n_usize = usize::try_from(n).expect("n must be non-negative");
13188    let mut l = 0usize;
13189
13190    for i in start..end.min(n_usize) {
13191        let k = usize::try_from(plcp[i]).expect("phi entry must be non-negative");
13192        let m = n_usize - i.max(k);
13193        while l < m && t[i + l] == t[k + l] {
13194            l += 1;
13195        }
13196        plcp[i] = SaSint::try_from(l).expect("LCP length must fit SaSint");
13197        l = l.saturating_sub(1);
13198    }
13199}
13200
13201/// Internal helper: compute plcp int (OpenMP variant).
13202#[doc(hidden)]
13203pub fn compute_plcp_int_omp(t: &[SaSint], plcp: &mut [SaSint], n: SaSint, threads: SaSint) {
13204    if threads == 1 || n < 65_536 {
13205        compute_plcp_int(t, plcp, n as FastSint, 0, n as FastSint);
13206        return;
13207    }
13208
13209    let n_usize = usize::try_from(n).expect("n must be non-negative");
13210    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13211    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13212    run_rayon_with_threads(threads_usize, || {
13213        plcp[..n_usize]
13214            .par_chunks_mut(chunk_size)
13215            .enumerate()
13216            .for_each(|(chunk_index, chunk)| {
13217                let start = chunk_index * chunk_size;
13218                let mut l = 0usize;
13219                for (offset, value) in chunk.iter_mut().enumerate() {
13220                    let i = start + offset;
13221                    let k = usize::try_from(*value).expect("phi entry must be non-negative");
13222                    let m = n_usize - i.max(k);
13223                    while l < m && t[i + l] == t[k + l] {
13224                        l += 1;
13225                    }
13226                    *value = SaSint::try_from(l).expect("LCP length must fit SaSint");
13227                    l = l.saturating_sub(1);
13228                }
13229            });
13230    });
13231}
13232
13233/// Internal helper: compute lcp.
13234#[doc(hidden)]
13235pub fn compute_lcp(
13236    plcp: &[SaSint],
13237    sa: &[SaSint],
13238    lcp: &mut [SaSint],
13239    omp_block_start: FastSint,
13240    omp_block_size: FastSint,
13241) {
13242    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13243    let size = usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13244    let end = start + size;
13245
13246    for i in start..end.min(sa.len()) {
13247        lcp[i] = plcp[usize::try_from(sa[i]).expect("suffix index must be non-negative")];
13248    }
13249}
13250
13251/// Internal helper: compute lcp (OpenMP variant).
13252#[doc(hidden)]
13253pub fn compute_lcp_omp(
13254    plcp: &[SaSint],
13255    sa: &[SaSint],
13256    lcp: &mut [SaSint],
13257    n: SaSint,
13258    threads: SaSint,
13259) {
13260    if threads == 1 || n < 65_536 {
13261        compute_lcp(plcp, sa, lcp, 0, n as FastSint);
13262        return;
13263    }
13264
13265    let n_usize = usize::try_from(n).expect("n must be non-negative");
13266    assert!(plcp.len() >= n_usize);
13267    assert!(sa.len() >= n_usize);
13268    assert!(lcp.len() >= n_usize);
13269    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
13270    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
13271    let plcp_ptr = plcp.as_ptr() as usize;
13272    let sa_ptr = sa.as_ptr() as usize;
13273    run_rayon_with_threads(threads_usize, || {
13274        lcp[..n_usize]
13275            .par_chunks_mut(chunk_size)
13276            .enumerate()
13277            .for_each(|(chunk_index, chunk)| {
13278                let start = chunk_index * chunk_size;
13279                let dst_ptr = chunk.as_mut_ptr();
13280                let sa_ptr = sa_ptr as *const SaSint;
13281                let plcp_ptr = plcp_ptr as *const SaSint;
13282                for offset in 0..chunk.len() {
13283                    let i = start + offset;
13284                    let suffix = unsafe { *sa_ptr.add(i) };
13285                    let suffix =
13286                        usize::try_from(suffix).expect("suffix index must be non-negative");
13287                    assert!(suffix < plcp.len());
13288                    unsafe {
13289                        *dst_ptr.add(offset) = *plcp_ptr.add(suffix);
13290                    }
13291                }
13292            });
13293    });
13294}
13295
13296/// Constructs the permuted longest common prefix array (PLCP) of a given string and a suffix array.
13297///
13298/// # Arguments
13299/// - `t` (`[0..n-1]`): The input string.
13300/// - `sa` (`[0..n-1]`): The input suffix array.
13301/// - `plcp` (`[0..n-1]`): The output permuted longest common prefix array.
13302///
13303/// # Returns
13304/// 0 if no error occurred, -1 otherwise.
13305pub fn libsais64_plcp(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13306    if sa.len() != t.len() || plcp.len() != t.len() {
13307        return -1;
13308    }
13309    if !suffix_entries_in_bounds(sa, t.len()) {
13310        return -1;
13311    }
13312    if t.len() <= 1 {
13313        if t.len() == 1 {
13314            plcp[0] = 0;
13315        }
13316        return 0;
13317    }
13318
13319    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13320    compute_phi_omp(sa, plcp, n, 1);
13321    compute_plcp_omp(t, plcp, n, 1);
13322    0
13323}
13324
13325/// Constructs the permuted longest common prefix array (PLCP) of a given string set and a generalized suffix array (GSA).
13326///
13327/// # Arguments
13328/// - `t` (`[0..n-1]`): The input string set using 0 as separators (`t[n-1]` must be 0).
13329/// - `sa` (`[0..n-1]`): The input generalized suffix array.
13330/// - `plcp` (`[0..n-1]`): The output permuted longest common prefix array.
13331///
13332/// # Returns
13333/// 0 if no error occurred, -1 otherwise.
13334pub fn libsais64_plcp_gsa(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13335    if t.last().copied().unwrap_or(0) != 0 {
13336        return -1;
13337    }
13338    if sa.len() != t.len() || plcp.len() != t.len() {
13339        return -1;
13340    }
13341    if !suffix_entries_in_bounds(sa, t.len()) {
13342        return -1;
13343    }
13344    if t.len() <= 1 {
13345        if t.len() == 1 {
13346            plcp[0] = 0;
13347        }
13348        return 0;
13349    }
13350
13351    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13352    compute_phi_omp(sa, plcp, n, 1);
13353    compute_plcp_gsa_omp(t, plcp, n, 1);
13354    0
13355}
13356
13357/// Constructs the PLCP of a given integer array and suffix array.
13358///
13359/// - `t` (`[0..n-1]`): the input integer array.
13360/// - `sa` (`[0..n-1]`): the input suffix array.
13361/// - `plcp` (`[0..n-1]`): the output permuted longest common prefix array.
13362///
13363/// Returns 0 on success, -1 on error.
13364pub fn libsais64_plcp_int(t: &[SaSint], sa: &[SaSint], plcp: &mut [SaSint]) -> SaSint {
13365    if sa.len() != t.len() || plcp.len() != t.len() {
13366        return -1;
13367    }
13368    if !suffix_entries_in_bounds(sa, t.len()) {
13369        return -1;
13370    }
13371    if t.len() <= 1 {
13372        if t.len() == 1 {
13373            plcp[0] = 0;
13374        }
13375        return 0;
13376    }
13377
13378    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13379    compute_phi_omp(sa, plcp, n, 1);
13380    compute_plcp_int_omp(t, plcp, n, 1);
13381    0
13382}
13383
13384/// Constructs the longest common prefix array (LCP) of a given permuted longest common prefix array (PLCP) and a suffix array.
13385///
13386/// # Arguments
13387/// - `plcp` (`[0..n-1]`): The input permuted longest common prefix array.
13388/// - `sa` (`[0..n-1]`): The input suffix array or generalized suffix array (GSA).
13389/// - `lcp` (`[0..n-1]`): The output longest common prefix array (can be `sa`).
13390///
13391/// # Returns
13392/// 0 if no error occurred, -1 otherwise.
13393pub fn libsais64_lcp(plcp: &[SaSint], sa: &[SaSint], lcp: &mut [SaSint]) -> SaSint {
13394    if plcp.len() != sa.len() || lcp.len() != sa.len() {
13395        return -1;
13396    }
13397    if !suffix_entries_in_bounds(sa, plcp.len()) {
13398        return -1;
13399    }
13400    if sa.len() <= 1 {
13401        if sa.len() == 1 {
13402            lcp[0] = plcp[usize::try_from(sa[0]).expect("suffix index must be non-negative")];
13403        }
13404        return 0;
13405    }
13406
13407    compute_lcp_omp(
13408        plcp,
13409        sa,
13410        lcp,
13411        SaSint::try_from(sa.len()).expect("suffix array length must fit SaSint"),
13412        1,
13413    );
13414    0
13415}
13416
13417/// Constructs the permuted longest common prefix array (PLCP) of a given string and a suffix array in parallel using OpenMP.
13418///
13419/// # Arguments
13420/// - `t` (`[0..n-1]`): The input string.
13421/// - `sa` (`[0..n-1]`): The input suffix array.
13422/// - `plcp` (`[0..n-1]`): The output permuted longest common prefix array.
13423/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13424///
13425/// # Returns
13426/// 0 if no error occurred, -1 otherwise.
13427pub fn libsais64_plcp_omp(t: &[u8], sa: &[SaSint], plcp: &mut [SaSint], threads: SaSint) -> SaSint {
13428    if threads < 0 {
13429        return -1;
13430    }
13431    if sa.len() != t.len() || plcp.len() != t.len() {
13432        return -1;
13433    }
13434    if !suffix_entries_in_bounds(sa, t.len()) {
13435        return -1;
13436    }
13437    if t.len() <= 1 {
13438        if t.len() == 1 {
13439            plcp[0] = 0;
13440        }
13441        return 0;
13442    }
13443
13444    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13445    let threads = normalize_omp_threads(threads);
13446    compute_phi_omp(sa, plcp, n, threads);
13447    compute_plcp_omp(t, plcp, n, threads);
13448    0
13449}
13450
13451/// Constructs the permuted longest common prefix array (PLCP) of a given string set and a generalized suffix array (GSA) in parallel using OpenMP.
13452///
13453/// # Arguments
13454/// - `t` (`[0..n-1]`): The input string set using 0 as separators (`t[n-1]` must be 0).
13455/// - `sa` (`[0..n-1]`): The input generalized suffix array.
13456/// - `plcp` (`[0..n-1]`): The output permuted longest common prefix array.
13457/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
13458///
13459/// # Returns
13460/// 0 if no error occurred, -1 otherwise.
13461pub fn libsais64_plcp_gsa_omp(
13462    t: &[u8],
13463    sa: &[SaSint],
13464    plcp: &mut [SaSint],
13465    threads: SaSint,
13466) -> SaSint {
13467    if threads < 0 || t.last().copied().unwrap_or(0) != 0 {
13468        return -1;
13469    }
13470    if sa.len() != t.len() || plcp.len() != t.len() {
13471        return -1;
13472    }
13473    if !suffix_entries_in_bounds(sa, t.len()) {
13474        return -1;
13475    }
13476    if t.len() <= 1 {
13477        if t.len() == 1 {
13478            plcp[0] = 0;
13479        }
13480        return 0;
13481    }
13482
13483    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13484    let threads = normalize_omp_threads(threads);
13485    compute_phi_omp(sa, plcp, n, threads);
13486    compute_plcp_gsa_omp(t, plcp, n, threads);
13487    0
13488}
13489
13490/// Constructs the PLCP of a given integer array and suffix array in parallel using OpenMP-style threading.
13491///
13492/// - `t` (`[0..n-1]`): the input integer array.
13493/// - `sa` (`[0..n-1]`): the input suffix array.
13494/// - `plcp` (`[0..n-1]`): the output permuted longest common prefix array.
13495/// - `threads`: number of worker threads (can be 0 for the implementation default).
13496///
13497/// Returns 0 on success, -1 on error.
13498pub fn libsais64_plcp_int_omp(
13499    t: &[SaSint],
13500    sa: &[SaSint],
13501    plcp: &mut [SaSint],
13502    threads: SaSint,
13503) -> SaSint {
13504    if threads < 0 {
13505        return -1;
13506    }
13507    if sa.len() != t.len() || plcp.len() != t.len() {
13508        return -1;
13509    }
13510    if !suffix_entries_in_bounds(sa, t.len()) {
13511        return -1;
13512    }
13513    if t.len() <= 1 {
13514        if t.len() == 1 {
13515            plcp[0] = 0;
13516        }
13517        return 0;
13518    }
13519
13520    let n = SaSint::try_from(t.len()).expect("input length must fit SaSint");
13521    let threads = normalize_omp_threads(threads);
13522    compute_phi_omp(sa, plcp, n, threads);
13523    compute_plcp_int_omp(t, plcp, n, threads);
13524    0
13525}
13526
13527/// Constructs the longest common prefix array (LCP) of a given permuted longest common prefix array (PLCP) and a suffix array in parallel using OpenMP.
13528///
13529/// # Arguments
13530/// - `plcp` (`[0..n-1]`): The input permuted longest common prefix array.
13531/// - `sa` (`[0..n-1]`): The input suffix array or generalized suffix array (GSA).
13532/// - `lcp` (`[0..n-1]`): The output longest common prefix array (can be `sa`).
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 libsais64_lcp_omp(
13538    plcp: &[SaSint],
13539    sa: &[SaSint],
13540    lcp: &mut [SaSint],
13541    threads: SaSint,
13542) -> SaSint {
13543    if threads < 0 {
13544        return -1;
13545    }
13546    if plcp.len() != sa.len() || lcp.len() != sa.len() {
13547        return -1;
13548    }
13549    if !suffix_entries_in_bounds(sa, plcp.len()) {
13550        return -1;
13551    }
13552    if sa.len() <= 1 {
13553        if sa.len() == 1 {
13554            lcp[0] = plcp[usize::try_from(sa[0]).expect("suffix index must be non-negative")];
13555        }
13556        return 0;
13557    }
13558
13559    compute_lcp_omp(
13560        plcp,
13561        sa,
13562        lcp,
13563        SaSint::try_from(sa.len()).expect("suffix array length must fit SaSint"),
13564        normalize_omp_threads(threads),
13565    );
13566    0
13567}
13568
13569fn suffix_entries_in_bounds(sa: &[SaSint], len: usize) -> bool {
13570    sa.iter()
13571        .all(|&value| usize::try_from(value).is_ok_and(|index| index < len))
13572}
13573
13574/// Internal helper: unbwt compute histogram.
13575#[doc(hidden)]
13576pub fn unbwt_compute_histogram(t: &[u8], n: FastSint, count: &mut [SaUint]) {
13577    let n = usize::try_from(n).expect("n must be non-negative");
13578    assert!(count.len() >= ALPHABET_SIZE);
13579    for &byte in &t[..n] {
13580        count[byte as usize] += 1;
13581    }
13582}
13583
13584/// Internal helper: unbwt transpose bucket2.
13585#[doc(hidden)]
13586pub fn unbwt_transpose_bucket2(bucket2: &mut [SaUint]) {
13587    assert!(bucket2.len() >= ALPHABET_SIZE * ALPHABET_SIZE);
13588    for x in 0..ALPHABET_SIZE {
13589        for y in x + 1..ALPHABET_SIZE {
13590            bucket2.swap((y << 8) + x, (x << 8) + y);
13591        }
13592    }
13593}
13594
13595/// Internal helper: unbwt compute bigram histogram single.
13596#[doc(hidden)]
13597pub fn unbwt_compute_bigram_histogram_single(
13598    t: &[u8],
13599    bucket1: &mut [SaUint],
13600    bucket2: &mut [SaUint],
13601    index: FastUint,
13602) {
13603    let mut sum = 1usize;
13604    for c in 0..ALPHABET_SIZE {
13605        let prev = sum;
13606        sum += bucket1[c] as usize;
13607        bucket1[c] = prev as SaUint;
13608        if prev != sum {
13609            let bucket2_p = &mut bucket2[c << 8..(c + 1) << 8];
13610
13611            let hi = sum.min(index);
13612            if hi > prev {
13613                unbwt_compute_histogram(&t[prev..], (hi - prev) as FastSint, bucket2_p);
13614            }
13615
13616            let lo = prev.max(index + 1);
13617            if sum > lo {
13618                unbwt_compute_histogram(&t[lo - 1..], (sum - lo) as FastSint, bucket2_p);
13619            }
13620        }
13621    }
13622
13623    unbwt_transpose_bucket2(bucket2);
13624}
13625
13626/// Internal helper: unbwt calculate fastbits.
13627#[doc(hidden)]
13628pub fn unbwt_calculate_fastbits(
13629    bucket2: &mut [SaUint],
13630    fastbits: &mut [u16],
13631    lastc: FastUint,
13632    shift: FastUint,
13633) {
13634    let mut v = 0usize;
13635    let mut w = 0usize;
13636    let mut sum = 1usize;
13637
13638    for c in 0..ALPHABET_SIZE {
13639        if c == lastc {
13640            sum += 1;
13641        }
13642
13643        for _d in 0..ALPHABET_SIZE {
13644            let prev = sum;
13645            sum += bucket2[w] as usize;
13646            bucket2[w] = prev as SaUint;
13647            if prev != sum {
13648                while v <= ((sum - 1) >> shift) {
13649                    fastbits[v] = w as u16;
13650                    v += 1;
13651                }
13652            }
13653            w += 1;
13654        }
13655    }
13656}
13657
13658/// Internal helper: unbwt calculate bi psi.
13659#[doc(hidden)]
13660pub fn unbwt_calculate_bi_psi(
13661    t: &[u8],
13662    p: &mut [SaUint],
13663    bucket1: &mut [SaUint],
13664    bucket2: &mut [SaUint],
13665    index: FastUint,
13666    omp_block_start: FastSint,
13667    omp_block_end: FastSint,
13668) {
13669    let mut i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13670    let mut j = index;
13671    let block_end = usize::try_from(omp_block_end).expect("omp_block_end must be non-negative");
13672    if block_end < j {
13673        j = block_end;
13674    }
13675    while i < j {
13676        let c = t[i] as usize;
13677        let pidx = bucket1[c] as usize;
13678        bucket1[c] += 1;
13679        let tidx = index.wrapping_sub(pidx) as i64;
13680        if tidx != 0 {
13681            let src = pidx.wrapping_add((tidx >> 63) as usize);
13682            let w = ((t[src] as usize) << 8) + c;
13683            let dst = bucket2[w] as usize;
13684            p[dst] = i as SaUint;
13685            bucket2[w] += 1;
13686        }
13687        i += 1;
13688    }
13689
13690    let mut i = index;
13691    if usize::try_from(omp_block_start).expect("omp_block_start must be non-negative") > i {
13692        i = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13693    }
13694    i += 1;
13695    while i <= block_end {
13696        let c = t[i - 1] as usize;
13697        let pidx = bucket1[c] as usize;
13698        bucket1[c] += 1;
13699        let tidx = index.wrapping_sub(pidx) as i64;
13700        if tidx != 0 {
13701            let src = pidx.wrapping_add((tidx >> 63) as usize);
13702            let w = ((t[src] as usize) << 8) + c;
13703            let dst = bucket2[w] as usize;
13704            p[dst] = i as SaUint;
13705            bucket2[w] += 1;
13706        }
13707        i += 1;
13708    }
13709}
13710
13711/// Internal helper: unbwt calculate biPSI.
13712#[doc(hidden)]
13713#[allow(dead_code, non_snake_case)]
13714pub fn unbwt_calculate_biPSI(
13715    t: &[u8],
13716    p: &mut [SaUint],
13717    bucket1: &mut [SaUint],
13718    bucket2: &mut [SaUint],
13719    index: FastUint,
13720    omp_block_start: FastSint,
13721    omp_block_end: FastSint,
13722) {
13723    unbwt_calculate_bi_psi(
13724        t,
13725        p,
13726        bucket1,
13727        bucket2,
13728        index,
13729        omp_block_start,
13730        omp_block_end,
13731    );
13732}
13733
13734/// Internal helper: unbwt init single.
13735#[doc(hidden)]
13736pub fn unbwt_init_single(
13737    t: &[u8],
13738    p: &mut [SaUint],
13739    n: SaSint,
13740    freq: Option<&[SaSint]>,
13741    i: &[SaUint],
13742    bucket2: &mut [SaUint],
13743    fastbits: &mut [u16],
13744) {
13745    let mut bucket1 = vec![0u64; ALPHABET_SIZE];
13746    let index = i[0] as usize;
13747    let lastc = t[0] as usize;
13748    let mut shift = 0usize;
13749    while (usize::try_from(n).expect("n must be non-negative") >> shift)
13750        > (1usize << UNBWT_FASTBITS)
13751    {
13752        shift += 1;
13753    }
13754
13755    if let Some(freq) = freq {
13756        for c in 0..ALPHABET_SIZE {
13757            bucket1[c] = freq[c] as SaUint;
13758        }
13759    } else {
13760        unbwt_compute_histogram(t, n as FastSint, &mut bucket1);
13761    }
13762
13763    bucket2.fill(0);
13764    unbwt_compute_bigram_histogram_single(t, &mut bucket1, bucket2, index);
13765    unbwt_calculate_fastbits(bucket2, fastbits, lastc, shift);
13766    unbwt_calculate_bi_psi(t, p, &mut bucket1, bucket2, index, 0, n as FastSint);
13767}
13768
13769/// Internal helper: unbwt compute bigram histogram parallel.
13770#[doc(hidden)]
13771pub fn unbwt_compute_bigram_histogram_parallel(
13772    t: &[u8],
13773    index: FastUint,
13774    bucket1: &mut [SaUint],
13775    bucket2: &mut [SaUint],
13776    omp_block_start: FastSint,
13777    omp_block_size: FastSint,
13778) {
13779    let start = usize::try_from(omp_block_start).expect("omp_block_start must be non-negative");
13780    let end = start + usize::try_from(omp_block_size).expect("omp_block_size must be non-negative");
13781    for &c_u8 in &t[start..end] {
13782        let c = c_u8 as usize;
13783        let p = bucket1[c] as usize;
13784        bucket1[c] += 1;
13785        let tidx = index.wrapping_sub(p) as i64;
13786        if tidx != 0 {
13787            let src = p.wrapping_add((tidx >> 63) as usize);
13788            let w = ((t[src] as usize) << 8) + c;
13789            bucket2[w] += 1;
13790        }
13791    }
13792}
13793
13794/// Internal helper: unbwt init parallel.
13795#[doc(hidden)]
13796pub fn unbwt_init_parallel(
13797    t: &[u8],
13798    p: &mut [SaUint],
13799    n: SaSint,
13800    freq: Option<&[SaSint]>,
13801    i: &[SaUint],
13802    bucket2: &mut [SaUint],
13803    fastbits: &mut [u16],
13804    buckets: Option<&mut [SaUint]>,
13805    threads: SaSint,
13806) {
13807    let num_threads = usize::try_from(threads.max(1)).expect("threads must be non-negative");
13808    if num_threads <= 1 || usize::try_from(n).expect("n must be non-negative") < 65_536 {
13809        unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
13810        return;
13811    }
13812
13813    let buckets = match buckets {
13814        Some(buckets) => buckets,
13815        None => {
13816            unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
13817            return;
13818        }
13819    };
13820
13821    let segment_len = ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE;
13822    assert!(buckets.len() >= num_threads * segment_len);
13823
13824    let index = i[0] as usize;
13825    let lastc = t[0] as usize;
13826    let mut shift = 0usize;
13827    while (usize::try_from(n).expect("n must be non-negative") >> shift)
13828        > (1usize << UNBWT_FASTBITS)
13829    {
13830        shift += 1;
13831    }
13832
13833    let mut bucket1 = vec![0u64; ALPHABET_SIZE];
13834    bucket2.fill(0);
13835
13836    let n_fast = n as FastSint;
13837    let block_stride = (n_fast / num_threads as FastSint) & (-16);
13838    let mut block_starts = vec![0usize; num_threads];
13839    let mut block_sizes = vec![0usize; num_threads];
13840
13841    for thread in 0..num_threads {
13842        let start = usize::try_from(thread as FastSint * block_stride)
13843            .expect("block start must be non-negative");
13844        let size = if thread + 1 < num_threads {
13845            usize::try_from(block_stride).expect("block stride must be non-negative")
13846        } else {
13847            usize::try_from(n_fast - thread as FastSint * block_stride)
13848                .expect("block size must be non-negative")
13849        };
13850        block_starts[thread] = start;
13851        block_sizes[thread] = size;
13852
13853        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13854        let (bucket1_local, _) = segment.split_at_mut(ALPHABET_SIZE);
13855        bucket1_local.fill(0);
13856        unbwt_compute_histogram(&t[start..], size as FastSint, bucket1_local);
13857    }
13858
13859    for thread in 0..num_threads {
13860        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13861        let (bucket1_temp, _) = segment.split_at_mut(ALPHABET_SIZE);
13862        for c in 0..ALPHABET_SIZE {
13863            let a = bucket1[c];
13864            let b = bucket1_temp[c];
13865            bucket1[c] = a + b;
13866            bucket1_temp[c] = a;
13867        }
13868    }
13869
13870    let mut sum = 1usize;
13871    for c in 0..ALPHABET_SIZE {
13872        let prev = sum;
13873        sum += bucket1[c] as usize;
13874        bucket1[c] = prev as SaUint;
13875    }
13876
13877    for thread in 0..num_threads {
13878        let start = block_starts[thread];
13879        let size = block_sizes[thread];
13880        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13881        let (bucket1_local, bucket2_local) = segment.split_at_mut(ALPHABET_SIZE);
13882        for c in 0..ALPHABET_SIZE {
13883            bucket1_local[c] += bucket1[c];
13884        }
13885        bucket2_local.fill(0);
13886        unbwt_compute_bigram_histogram_parallel(
13887            t,
13888            index,
13889            bucket1_local,
13890            bucket2_local,
13891            start as FastSint,
13892            size as FastSint,
13893        );
13894    }
13895
13896    for thread in 0..num_threads {
13897        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13898        let (_, bucket2_temp) = segment.split_at_mut(ALPHABET_SIZE);
13899        for c in 0..ALPHABET_SIZE * ALPHABET_SIZE {
13900            let a = bucket2[c];
13901            let b = bucket2_temp[c];
13902            bucket2[c] = a + b;
13903            bucket2_temp[c] = a;
13904        }
13905    }
13906
13907    unbwt_calculate_fastbits(bucket2, fastbits, lastc, shift);
13908
13909    for thread in (1..num_threads).rev() {
13910        let src_start = (thread - 1) * segment_len;
13911        let dst_start = thread * segment_len;
13912        let (head, tail) = buckets.split_at_mut(dst_start);
13913        let src = &head[src_start..src_start + ALPHABET_SIZE];
13914        let dst = &mut tail[..ALPHABET_SIZE];
13915        dst.copy_from_slice(src);
13916    }
13917    buckets[..ALPHABET_SIZE].copy_from_slice(&bucket1);
13918
13919    for thread in 0..num_threads {
13920        let start = block_starts[thread];
13921        let size = block_sizes[thread];
13922        let segment = &mut buckets[thread * segment_len..(thread + 1) * segment_len];
13923        let (bucket1_local, bucket2_local) = segment.split_at_mut(ALPHABET_SIZE);
13924        for c in 0..ALPHABET_SIZE * ALPHABET_SIZE {
13925            bucket2_local[c] += bucket2[c];
13926        }
13927        unbwt_calculate_bi_psi(
13928            t,
13929            p,
13930            bucket1_local,
13931            bucket2_local,
13932            index,
13933            start as FastSint,
13934            (start + size) as FastSint,
13935        );
13936    }
13937
13938    let last_segment = &buckets[(num_threads - 1) * segment_len..num_threads * segment_len];
13939    let (_, last_bucket2) = last_segment.split_at(ALPHABET_SIZE);
13940    bucket2.copy_from_slice(last_bucket2);
13941}
13942
13943fn bswap16(value: u16) -> u16 {
13944    value.swap_bytes()
13945}
13946
13947fn unbwt_resolve_symbol(bucket2: &[SaUint], fastbits: &[u16], shift: FastUint, p: SaUint) -> u16 {
13948    let mut c = fastbits[(p as usize) >> shift];
13949    while bucket2[c as usize] <= p {
13950        c += 1;
13951    }
13952    c
13953}
13954
13955/// Internal helper: unbwt decode 1.
13956#[doc(hidden)]
13957pub fn unbwt_decode_1(
13958    u: &mut [u8],
13959    p: &[SaUint],
13960    bucket2: &[SaUint],
13961    fastbits: &[u16],
13962    shift: FastUint,
13963    i0: &mut FastUint,
13964    k: FastUint,
13965) {
13966    let words = &mut u[..2 * k];
13967    let mut p0 = *i0 as SaUint;
13968
13969    for i in 0..k {
13970        let c0 = unbwt_resolve_symbol(bucket2, fastbits, shift, p0);
13971        p0 = p[p0 as usize];
13972        let bytes = bswap16(c0).to_ne_bytes();
13973        words[2 * i] = bytes[0];
13974        words[2 * i + 1] = bytes[1];
13975    }
13976
13977    *i0 = p0 as FastUint;
13978}
13979
13980/// Internal helper: unbwt decode 2.
13981#[doc(hidden)]
13982pub fn unbwt_decode_2(
13983    u: &mut [u8],
13984    p: &[SaUint],
13985    bucket2: &[SaUint],
13986    fastbits: &[u16],
13987    shift: FastUint,
13988    r: FastUint,
13989    i0: &mut FastUint,
13990    i1: &mut FastUint,
13991    k: FastUint,
13992) {
13993    let width = 2 * k;
13994    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
13995    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
13996}
13997
13998/// Internal helper: unbwt decode 3.
13999#[doc(hidden)]
14000pub fn unbwt_decode_3(
14001    u: &mut [u8],
14002    p: &[SaUint],
14003    bucket2: &[SaUint],
14004    fastbits: &[u16],
14005    shift: FastUint,
14006    r: FastUint,
14007    i0: &mut FastUint,
14008    i1: &mut FastUint,
14009    i2: &mut FastUint,
14010    k: FastUint,
14011) {
14012    let width = 2 * k;
14013    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14014    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14015    unbwt_decode_1(
14016        &mut u[2 * r..2 * r + width],
14017        p,
14018        bucket2,
14019        fastbits,
14020        shift,
14021        i2,
14022        k,
14023    );
14024}
14025
14026/// Internal helper: unbwt decode 4.
14027#[doc(hidden)]
14028pub fn unbwt_decode_4(
14029    u: &mut [u8],
14030    p: &[SaUint],
14031    bucket2: &[SaUint],
14032    fastbits: &[u16],
14033    shift: FastUint,
14034    r: FastUint,
14035    i0: &mut FastUint,
14036    i1: &mut FastUint,
14037    i2: &mut FastUint,
14038    i3: &mut FastUint,
14039    k: FastUint,
14040) {
14041    let width = 2 * k;
14042    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14043    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14044    unbwt_decode_1(
14045        &mut u[2 * r..2 * r + width],
14046        p,
14047        bucket2,
14048        fastbits,
14049        shift,
14050        i2,
14051        k,
14052    );
14053    unbwt_decode_1(
14054        &mut u[3 * r..3 * r + width],
14055        p,
14056        bucket2,
14057        fastbits,
14058        shift,
14059        i3,
14060        k,
14061    );
14062}
14063
14064/// Internal helper: unbwt decode 5.
14065#[doc(hidden)]
14066pub fn unbwt_decode_5(
14067    u: &mut [u8],
14068    p: &[SaUint],
14069    bucket2: &[SaUint],
14070    fastbits: &[u16],
14071    shift: FastUint,
14072    r: FastUint,
14073    i0: &mut FastUint,
14074    i1: &mut FastUint,
14075    i2: &mut FastUint,
14076    i3: &mut FastUint,
14077    i4: &mut FastUint,
14078    k: FastUint,
14079) {
14080    let width = 2 * k;
14081    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14082    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14083    unbwt_decode_1(
14084        &mut u[2 * r..2 * r + width],
14085        p,
14086        bucket2,
14087        fastbits,
14088        shift,
14089        i2,
14090        k,
14091    );
14092    unbwt_decode_1(
14093        &mut u[3 * r..3 * r + width],
14094        p,
14095        bucket2,
14096        fastbits,
14097        shift,
14098        i3,
14099        k,
14100    );
14101    unbwt_decode_1(
14102        &mut u[4 * r..4 * r + width],
14103        p,
14104        bucket2,
14105        fastbits,
14106        shift,
14107        i4,
14108        k,
14109    );
14110}
14111
14112/// Internal helper: unbwt decode 6.
14113#[doc(hidden)]
14114pub fn unbwt_decode_6(
14115    u: &mut [u8],
14116    p: &[SaUint],
14117    bucket2: &[SaUint],
14118    fastbits: &[u16],
14119    shift: FastUint,
14120    r: FastUint,
14121    i0: &mut FastUint,
14122    i1: &mut FastUint,
14123    i2: &mut FastUint,
14124    i3: &mut FastUint,
14125    i4: &mut FastUint,
14126    i5: &mut FastUint,
14127    k: FastUint,
14128) {
14129    let width = 2 * k;
14130    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14131    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14132    unbwt_decode_1(
14133        &mut u[2 * r..2 * r + width],
14134        p,
14135        bucket2,
14136        fastbits,
14137        shift,
14138        i2,
14139        k,
14140    );
14141    unbwt_decode_1(
14142        &mut u[3 * r..3 * r + width],
14143        p,
14144        bucket2,
14145        fastbits,
14146        shift,
14147        i3,
14148        k,
14149    );
14150    unbwt_decode_1(
14151        &mut u[4 * r..4 * r + width],
14152        p,
14153        bucket2,
14154        fastbits,
14155        shift,
14156        i4,
14157        k,
14158    );
14159    unbwt_decode_1(
14160        &mut u[5 * r..5 * r + width],
14161        p,
14162        bucket2,
14163        fastbits,
14164        shift,
14165        i5,
14166        k,
14167    );
14168}
14169
14170/// Internal helper: unbwt decode 7.
14171#[doc(hidden)]
14172pub fn unbwt_decode_7(
14173    u: &mut [u8],
14174    p: &[SaUint],
14175    bucket2: &[SaUint],
14176    fastbits: &[u16],
14177    shift: FastUint,
14178    r: FastUint,
14179    i0: &mut FastUint,
14180    i1: &mut FastUint,
14181    i2: &mut FastUint,
14182    i3: &mut FastUint,
14183    i4: &mut FastUint,
14184    i5: &mut FastUint,
14185    i6: &mut FastUint,
14186    k: FastUint,
14187) {
14188    let width = 2 * k;
14189    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14190    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14191    unbwt_decode_1(
14192        &mut u[2 * r..2 * r + width],
14193        p,
14194        bucket2,
14195        fastbits,
14196        shift,
14197        i2,
14198        k,
14199    );
14200    unbwt_decode_1(
14201        &mut u[3 * r..3 * r + width],
14202        p,
14203        bucket2,
14204        fastbits,
14205        shift,
14206        i3,
14207        k,
14208    );
14209    unbwt_decode_1(
14210        &mut u[4 * r..4 * r + width],
14211        p,
14212        bucket2,
14213        fastbits,
14214        shift,
14215        i4,
14216        k,
14217    );
14218    unbwt_decode_1(
14219        &mut u[5 * r..5 * r + width],
14220        p,
14221        bucket2,
14222        fastbits,
14223        shift,
14224        i5,
14225        k,
14226    );
14227    unbwt_decode_1(
14228        &mut u[6 * r..6 * r + width],
14229        p,
14230        bucket2,
14231        fastbits,
14232        shift,
14233        i6,
14234        k,
14235    );
14236}
14237
14238/// Internal helper: unbwt decode 8.
14239#[doc(hidden)]
14240pub fn unbwt_decode_8(
14241    u: &mut [u8],
14242    p: &[SaUint],
14243    bucket2: &[SaUint],
14244    fastbits: &[u16],
14245    shift: FastUint,
14246    r: FastUint,
14247    i0: &mut FastUint,
14248    i1: &mut FastUint,
14249    i2: &mut FastUint,
14250    i3: &mut FastUint,
14251    i4: &mut FastUint,
14252    i5: &mut FastUint,
14253    i6: &mut FastUint,
14254    i7: &mut FastUint,
14255    k: FastUint,
14256) {
14257    let width = 2 * k;
14258    unbwt_decode_1(&mut u[0..width], p, bucket2, fastbits, shift, i0, k);
14259    unbwt_decode_1(&mut u[r..r + width], p, bucket2, fastbits, shift, i1, k);
14260    unbwt_decode_1(
14261        &mut u[2 * r..2 * r + width],
14262        p,
14263        bucket2,
14264        fastbits,
14265        shift,
14266        i2,
14267        k,
14268    );
14269    unbwt_decode_1(
14270        &mut u[3 * r..3 * r + width],
14271        p,
14272        bucket2,
14273        fastbits,
14274        shift,
14275        i3,
14276        k,
14277    );
14278    unbwt_decode_1(
14279        &mut u[4 * r..4 * r + width],
14280        p,
14281        bucket2,
14282        fastbits,
14283        shift,
14284        i4,
14285        k,
14286    );
14287    unbwt_decode_1(
14288        &mut u[5 * r..5 * r + width],
14289        p,
14290        bucket2,
14291        fastbits,
14292        shift,
14293        i5,
14294        k,
14295    );
14296    unbwt_decode_1(
14297        &mut u[6 * r..6 * r + width],
14298        p,
14299        bucket2,
14300        fastbits,
14301        shift,
14302        i6,
14303        k,
14304    );
14305    unbwt_decode_1(
14306        &mut u[7 * r..7 * r + width],
14307        p,
14308        bucket2,
14309        fastbits,
14310        shift,
14311        i7,
14312        k,
14313    );
14314}
14315
14316/// Internal helper: unbwt decode.
14317#[doc(hidden)]
14318pub fn unbwt_decode(
14319    u: &mut [u8],
14320    p: &[SaUint],
14321    n: SaSint,
14322    r: SaSint,
14323    i: &[SaUint],
14324    bucket2: &[SaUint],
14325    fastbits: &[u16],
14326    mut blocks: FastSint,
14327    remainder: FastUint,
14328) {
14329    let mut shift = 0usize;
14330    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14331        > (1usize << UNBWT_FASTBITS)
14332    {
14333        shift += 1;
14334    }
14335    let mut offset = 0usize;
14336    let mut i_index = 0usize;
14337    let r_usize = usize::try_from(r).expect("r must be non-negative");
14338
14339    while blocks > 8 {
14340        let mut i0 = i[i_index] as FastUint;
14341        let mut i1 = i[i_index + 1] as FastUint;
14342        let mut i2 = i[i_index + 2] as FastUint;
14343        let mut i3 = i[i_index + 3] as FastUint;
14344        let mut i4 = i[i_index + 4] as FastUint;
14345        let mut i5 = i[i_index + 5] as FastUint;
14346        let mut i6 = i[i_index + 6] as FastUint;
14347        let mut i7 = i[i_index + 7] as FastUint;
14348        unbwt_decode_8(
14349            &mut u[offset..],
14350            p,
14351            bucket2,
14352            fastbits,
14353            shift,
14354            r_usize,
14355            &mut i0,
14356            &mut i1,
14357            &mut i2,
14358            &mut i3,
14359            &mut i4,
14360            &mut i5,
14361            &mut i6,
14362            &mut i7,
14363            r_usize >> 1,
14364        );
14365        i_index += 8;
14366        blocks -= 8;
14367        offset += 8 * r_usize;
14368    }
14369
14370    match blocks {
14371        1 => {
14372            let mut i0 = i[i_index] as FastUint;
14373            unbwt_decode_1(
14374                &mut u[offset..],
14375                p,
14376                bucket2,
14377                fastbits,
14378                shift,
14379                &mut i0,
14380                remainder >> 1,
14381            );
14382        }
14383        2 => {
14384            let mut i0 = i[i_index] as FastUint;
14385            let mut i1 = i[i_index + 1] as FastUint;
14386            unbwt_decode_2(
14387                &mut u[offset..],
14388                p,
14389                bucket2,
14390                fastbits,
14391                shift,
14392                r_usize,
14393                &mut i0,
14394                &mut i1,
14395                remainder >> 1,
14396            );
14397            unbwt_decode_1(
14398                &mut u[offset + 2 * (remainder >> 1)..],
14399                p,
14400                bucket2,
14401                fastbits,
14402                shift,
14403                &mut i0,
14404                (r_usize >> 1) - (remainder >> 1),
14405            );
14406        }
14407        3 => {
14408            let mut i0 = i[i_index] as FastUint;
14409            let mut i1 = i[i_index + 1] as FastUint;
14410            let mut i2 = i[i_index + 2] as FastUint;
14411            unbwt_decode_3(
14412                &mut u[offset..],
14413                p,
14414                bucket2,
14415                fastbits,
14416                shift,
14417                r_usize,
14418                &mut i0,
14419                &mut i1,
14420                &mut i2,
14421                remainder >> 1,
14422            );
14423            unbwt_decode_2(
14424                &mut u[offset + 2 * (remainder >> 1)..],
14425                p,
14426                bucket2,
14427                fastbits,
14428                shift,
14429                r_usize,
14430                &mut i0,
14431                &mut i1,
14432                (r_usize >> 1) - (remainder >> 1),
14433            );
14434        }
14435        4 => {
14436            let mut i0 = i[i_index] as FastUint;
14437            let mut i1 = i[i_index + 1] as FastUint;
14438            let mut i2 = i[i_index + 2] as FastUint;
14439            let mut i3 = i[i_index + 3] as FastUint;
14440            unbwt_decode_4(
14441                &mut u[offset..],
14442                p,
14443                bucket2,
14444                fastbits,
14445                shift,
14446                r_usize,
14447                &mut i0,
14448                &mut i1,
14449                &mut i2,
14450                &mut i3,
14451                remainder >> 1,
14452            );
14453            unbwt_decode_3(
14454                &mut u[offset + 2 * (remainder >> 1)..],
14455                p,
14456                bucket2,
14457                fastbits,
14458                shift,
14459                r_usize,
14460                &mut i0,
14461                &mut i1,
14462                &mut i2,
14463                (r_usize >> 1) - (remainder >> 1),
14464            );
14465        }
14466        5 => {
14467            let mut i0 = i[i_index] as FastUint;
14468            let mut i1 = i[i_index + 1] as FastUint;
14469            let mut i2 = i[i_index + 2] as FastUint;
14470            let mut i3 = i[i_index + 3] as FastUint;
14471            let mut i4 = i[i_index + 4] as FastUint;
14472            unbwt_decode_5(
14473                &mut u[offset..],
14474                p,
14475                bucket2,
14476                fastbits,
14477                shift,
14478                r_usize,
14479                &mut i0,
14480                &mut i1,
14481                &mut i2,
14482                &mut i3,
14483                &mut i4,
14484                remainder >> 1,
14485            );
14486            unbwt_decode_4(
14487                &mut u[offset + 2 * (remainder >> 1)..],
14488                p,
14489                bucket2,
14490                fastbits,
14491                shift,
14492                r_usize,
14493                &mut i0,
14494                &mut i1,
14495                &mut i2,
14496                &mut i3,
14497                (r_usize >> 1) - (remainder >> 1),
14498            );
14499        }
14500        6 => {
14501            let mut i0 = i[i_index] as FastUint;
14502            let mut i1 = i[i_index + 1] as FastUint;
14503            let mut i2 = i[i_index + 2] as FastUint;
14504            let mut i3 = i[i_index + 3] as FastUint;
14505            let mut i4 = i[i_index + 4] as FastUint;
14506            let mut i5 = i[i_index + 5] as FastUint;
14507            unbwt_decode_6(
14508                &mut u[offset..],
14509                p,
14510                bucket2,
14511                fastbits,
14512                shift,
14513                r_usize,
14514                &mut i0,
14515                &mut i1,
14516                &mut i2,
14517                &mut i3,
14518                &mut i4,
14519                &mut i5,
14520                remainder >> 1,
14521            );
14522            unbwt_decode_5(
14523                &mut u[offset + 2 * (remainder >> 1)..],
14524                p,
14525                bucket2,
14526                fastbits,
14527                shift,
14528                r_usize,
14529                &mut i0,
14530                &mut i1,
14531                &mut i2,
14532                &mut i3,
14533                &mut i4,
14534                (r_usize >> 1) - (remainder >> 1),
14535            );
14536        }
14537        7 => {
14538            let mut i0 = i[i_index] as FastUint;
14539            let mut i1 = i[i_index + 1] as FastUint;
14540            let mut i2 = i[i_index + 2] as FastUint;
14541            let mut i3 = i[i_index + 3] as FastUint;
14542            let mut i4 = i[i_index + 4] as FastUint;
14543            let mut i5 = i[i_index + 5] as FastUint;
14544            let mut i6 = i[i_index + 6] as FastUint;
14545            unbwt_decode_7(
14546                &mut u[offset..],
14547                p,
14548                bucket2,
14549                fastbits,
14550                shift,
14551                r_usize,
14552                &mut i0,
14553                &mut i1,
14554                &mut i2,
14555                &mut i3,
14556                &mut i4,
14557                &mut i5,
14558                &mut i6,
14559                remainder >> 1,
14560            );
14561            unbwt_decode_6(
14562                &mut u[offset + 2 * (remainder >> 1)..],
14563                p,
14564                bucket2,
14565                fastbits,
14566                shift,
14567                r_usize,
14568                &mut i0,
14569                &mut i1,
14570                &mut i2,
14571                &mut i3,
14572                &mut i4,
14573                &mut i5,
14574                (r_usize >> 1) - (remainder >> 1),
14575            );
14576        }
14577        8 => {
14578            let mut i0 = i[i_index] as FastUint;
14579            let mut i1 = i[i_index + 1] as FastUint;
14580            let mut i2 = i[i_index + 2] as FastUint;
14581            let mut i3 = i[i_index + 3] as FastUint;
14582            let mut i4 = i[i_index + 4] as FastUint;
14583            let mut i5 = i[i_index + 5] as FastUint;
14584            let mut i6 = i[i_index + 6] as FastUint;
14585            let mut i7 = i[i_index + 7] as FastUint;
14586            unbwt_decode_8(
14587                &mut u[offset..],
14588                p,
14589                bucket2,
14590                fastbits,
14591                shift,
14592                r_usize,
14593                &mut i0,
14594                &mut i1,
14595                &mut i2,
14596                &mut i3,
14597                &mut i4,
14598                &mut i5,
14599                &mut i6,
14600                &mut i7,
14601                remainder >> 1,
14602            );
14603            unbwt_decode_7(
14604                &mut u[offset + 2 * (remainder >> 1)..],
14605                p,
14606                bucket2,
14607                fastbits,
14608                shift,
14609                r_usize,
14610                &mut i0,
14611                &mut i1,
14612                &mut i2,
14613                &mut i3,
14614                &mut i4,
14615                &mut i5,
14616                &mut i6,
14617                (r_usize >> 1) - (remainder >> 1),
14618            );
14619        }
14620        _ => {}
14621    }
14622}
14623
14624/// Internal helper: unbwt decode (OpenMP variant).
14625#[doc(hidden)]
14626pub fn unbwt_decode_omp(
14627    t: &[u8],
14628    u: &mut [u8],
14629    p: &[SaUint],
14630    n: SaSint,
14631    r: SaSint,
14632    i: &[SaUint],
14633    bucket2: &[SaUint],
14634    fastbits: &[u16],
14635    threads: SaSint,
14636) {
14637    let lastc = t[0];
14638    let blocks = 1 + ((n as FastSint - 1) / r as FastSint);
14639    let remainder = usize::try_from(n).expect("n must be non-negative")
14640        - usize::try_from(r).expect("r must be non-negative")
14641            * (usize::try_from(blocks).expect("blocks") - 1);
14642    let max_threads = usize::try_from(blocks.min(threads.max(1) as FastSint))
14643        .expect("thread count must fit usize");
14644    let block_stride = usize::try_from(blocks).expect("blocks must be non-negative") / max_threads;
14645    let block_remainder =
14646        usize::try_from(blocks).expect("blocks must be non-negative") % max_threads;
14647    let r_usize = usize::try_from(r).expect("r must be non-negative");
14648
14649    let u_ptr = SyncMutPtr::new(u);
14650    run_rayon_with_threads(max_threads, || {
14651        (0..max_threads).into_par_iter().for_each(|thread| {
14652            let block_size = block_stride + usize::from(thread < block_remainder);
14653            let block_start = block_stride * thread + thread.min(block_remainder);
14654            let u = unsafe { u_ptr.as_slice() };
14655            unbwt_decode(
14656                &mut u[r_usize * block_start..],
14657                p,
14658                n,
14659                r,
14660                &i[block_start..],
14661                bucket2,
14662                fastbits,
14663                block_size as FastSint,
14664                if thread + 1 < max_threads {
14665                    r_usize
14666                } else {
14667                    remainder
14668                },
14669            );
14670        });
14671    });
14672    u[usize::try_from(n).expect("n must be non-negative") - 1] = lastc;
14673}
14674
14675/// Internal helper: unbwt core.
14676#[doc(hidden)]
14677pub fn unbwt_core(
14678    t: &[u8],
14679    u: &mut [u8],
14680    p: &mut [SaUint],
14681    n: SaSint,
14682    freq: Option<&[SaSint]>,
14683    r: SaSint,
14684    i: &[SaUint],
14685    bucket2: &mut [SaUint],
14686    fastbits: &mut [u16],
14687    buckets: Option<&mut [SaUint]>,
14688    threads: SaSint,
14689) -> SaSint {
14690    if threads > 1 && n >= 262_144 {
14691        unbwt_init_parallel(t, p, n, freq, i, bucket2, fastbits, buckets, threads);
14692    } else {
14693        unbwt_init_single(t, p, n, freq, i, bucket2, fastbits);
14694    }
14695
14696    unbwt_decode_omp(t, u, p, n, r, i, bucket2, fastbits, threads);
14697    0
14698}
14699
14700/// Internal helper: unbwt main.
14701#[doc(hidden)]
14702pub fn unbwt_main(
14703    t: &[u8],
14704    u: &mut [u8],
14705    p: &mut [SaUint],
14706    n: SaSint,
14707    freq: Option<&[SaSint]>,
14708    r: SaSint,
14709    i: &[SaUint],
14710    threads: SaSint,
14711) -> SaSint {
14712    let mut shift = 0usize;
14713    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14714        > (1usize << UNBWT_FASTBITS)
14715    {
14716        shift += 1;
14717    }
14718
14719    let mut bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
14720    let mut fastbits =
14721        vec![0u16; 1 + (usize::try_from(n).expect("n must be non-negative") >> shift)];
14722    let mut buckets = if threads > 1 && n >= 262_144 {
14723        Some(vec![
14724            0u64;
14725            usize::try_from(threads)
14726                .expect("threads must be non-negative")
14727                * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)
14728        ])
14729    } else {
14730        None
14731    };
14732
14733    unbwt_core(
14734        t,
14735        u,
14736        p,
14737        n,
14738        freq,
14739        r,
14740        i,
14741        &mut bucket2,
14742        &mut fastbits,
14743        buckets.as_deref_mut(),
14744        threads,
14745    )
14746}
14747
14748/// Internal helper: unbwt main ctx.
14749#[doc(hidden)]
14750pub fn unbwt_main_ctx(
14751    ctx: &mut UnbwtContext,
14752    t: &[u8],
14753    u: &mut [u8],
14754    p: &mut [SaUint],
14755    n: SaSint,
14756    freq: Option<&[SaSint]>,
14757    r: SaSint,
14758    i: &[SaUint],
14759) -> SaSint {
14760    if ctx.threads <= 0 {
14761        return -2;
14762    }
14763    let mut shift = 0usize;
14764    while (usize::try_from(n).expect("n must be non-negative") >> shift)
14765        > (1usize << UNBWT_FASTBITS)
14766    {
14767        shift += 1;
14768    }
14769    let required_fastbits = 1 + (usize::try_from(n).expect("n must be non-negative") >> shift);
14770    if ctx.bucket2.len() < ALPHABET_SIZE * ALPHABET_SIZE
14771        || ctx.fastbits.len() < required_fastbits
14772        || (ctx.threads > 1 && ctx.buckets.is_none())
14773    {
14774        return -2;
14775    }
14776
14777    unbwt_core(
14778        t,
14779        u,
14780        p,
14781        n,
14782        freq,
14783        r,
14784        i,
14785        &mut ctx.bucket2,
14786        &mut ctx.fastbits,
14787        ctx.buckets.as_deref_mut(),
14788        ctx.threads as SaSint,
14789    )
14790}
14791
14792/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with primary index.
14793///
14794/// # Arguments
14795/// - `t` (`[0..n-1]`): The input string.
14796/// - `u` (`[0..n-1]`): The output string (can be `t`).
14797/// - `a` (`[0..n]`): The temporary array (NOTE, temporary array must be n + 1 size).
14798/// - `freq` (`[0..255]`): The input symbol frequency table (can be `None`).
14799/// - `i`: The primary index.
14800///
14801/// # Returns
14802/// 0 if no error occurred, -1 or -2 otherwise.
14803pub fn libsais64_unbwt(
14804    t: &[u8],
14805    u: &mut [u8],
14806    a: &mut [SaSint],
14807    freq: Option<&[SaSint]>,
14808    i: SaSint,
14809) -> SaSint {
14810    libsais64_unbwt_aux(
14811        t,
14812        u,
14813        a,
14814        freq,
14815        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
14816        &[i],
14817    )
14818}
14819
14820/// Reconstructs the original string from a given BWT and primary index using a libsais64 reverse-BWT context.
14821///
14822/// - `ctx`: the libsais64 reverse-BWT context.
14823/// - `t` (`[0..n-1]`): the input string.
14824/// - `u` (`[0..n-1]`): the output string (can alias `t`).
14825/// - `a` (`[0..n]`): the temporary array (must have length `n + 1`).
14826/// - `freq` (`[0..255]`): optional input symbol frequency table.
14827/// - `i`: the primary index.
14828///
14829/// Returns 0 on success, -1 or -2 on error.
14830pub fn libsais64_unbwt_ctx(
14831    ctx: &mut UnbwtContext,
14832    t: &[u8],
14833    u: &mut [u8],
14834    a: &mut [SaSint],
14835    freq: Option<&[SaSint]>,
14836    i: SaSint,
14837) -> SaSint {
14838    libsais64_unbwt_aux_ctx(
14839        ctx,
14840        t,
14841        u,
14842        a,
14843        freq,
14844        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
14845        &[i],
14846    )
14847}
14848
14849/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with auxiliary indexes.
14850///
14851/// # Arguments
14852/// - `t` (`[0..n-1]`): The input string.
14853/// - `u` (`[0..n-1]`): The output string (can be `t`).
14854/// - `a` (`[0..n]`): The temporary array (NOTE, temporary array must be n + 1 size).
14855/// - `freq` (`[0..255]`): The input symbol frequency table (can be `None`).
14856/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
14857/// - `i` (`[0..(n-1)/r]`): The input auxiliary indexes.
14858///
14859/// # Returns
14860/// 0 if no error occurred, -1 or -2 otherwise.
14861pub fn libsais64_unbwt_aux(
14862    t: &[u8],
14863    u: &mut [u8],
14864    a: &mut [SaSint],
14865    freq: Option<&[SaSint]>,
14866    r: SaSint,
14867    i: &[SaSint],
14868) -> SaSint {
14869    let t_len = t.len();
14870    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
14871    if u.len() < t_len
14872        || a.len() < t_len
14873        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
14874        || (r != n && (r < 2 || (r & (r - 1)) != 0))
14875    {
14876        return -1;
14877    }
14878    let sample_count = if n == 0 {
14879        1
14880    } else {
14881        ((n - 1) / r + 1) as usize
14882    };
14883    if i.len() < sample_count {
14884        return -1;
14885    }
14886
14887    if n <= 1 {
14888        if i[0] != n {
14889            return -1;
14890        }
14891        if n == 1 {
14892            u[0] = t[0];
14893        }
14894        return 0;
14895    }
14896
14897    for t in 0..sample_count {
14898        let sample = i[t];
14899        if sample <= 0 || sample > n {
14900            return -1;
14901        }
14902    }
14903
14904    let i_uint: Vec<SaUint> = i
14905        .iter()
14906        .take(sample_count)
14907        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
14908        .collect();
14909    let mut p = vec![0u64; t_len + 1];
14910    let result = unbwt_main(t, u, &mut p, n, freq, r, &i_uint, 1);
14911    for t in 0..t_len {
14912        a[t] = p[t] as SaSint;
14913    }
14914    result
14915}
14916
14917/// Reconstructs the original string from a given BWT with auxiliary indexes using a libsais64 reverse-BWT context.
14918///
14919/// - `ctx`: the libsais64 reverse-BWT context.
14920/// - `t` (`[0..n-1]`): the input string.
14921/// - `u` (`[0..n-1]`): the output string (can alias `t`).
14922/// - `a` (`[0..n]`): the temporary array (must have length `n + 1`).
14923/// - `freq` (`[0..255]`): optional input symbol frequency table.
14924/// - `r`: sampling rate for the auxiliary indexes (must be a power of two).
14925/// - `i` (`[0..(n-1)/r]`): input auxiliary indexes.
14926///
14927/// Returns 0 on success, -1 or -2 on error.
14928pub fn libsais64_unbwt_aux_ctx(
14929    ctx: &mut UnbwtContext,
14930    t: &[u8],
14931    u: &mut [u8],
14932    a: &mut [SaSint],
14933    freq: Option<&[SaSint]>,
14934    r: SaSint,
14935    i: &[SaSint],
14936) -> SaSint {
14937    let t_len = t.len();
14938    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
14939    if u.len() < t_len
14940        || a.len() < t_len
14941        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
14942        || (r != n && (r < 2 || (r & (r - 1)) != 0))
14943    {
14944        return -1;
14945    }
14946    let sample_count = if n == 0 {
14947        1
14948    } else {
14949        ((n - 1) / r + 1) as usize
14950    };
14951    if i.len() < sample_count {
14952        return -1;
14953    }
14954
14955    if n <= 1 {
14956        if i[0] != n {
14957            return -1;
14958        }
14959        if n == 1 {
14960            u[0] = t[0];
14961        }
14962        return 0;
14963    }
14964
14965    for t in 0..sample_count {
14966        let sample = i[t];
14967        if sample <= 0 || sample > n {
14968            return -1;
14969        }
14970    }
14971
14972    let i_uint: Vec<SaUint> = i
14973        .iter()
14974        .take(sample_count)
14975        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
14976        .collect();
14977    let mut p = vec![0u64; t_len + 1];
14978    let result = unbwt_main_ctx(ctx, t, u, &mut p, n, freq, r, &i_uint);
14979    for t in 0..t_len {
14980        a[t] = p[t] as SaSint;
14981    }
14982    result
14983}
14984
14985/// Creates the libsais64 reverse-BWT context for parallel `libsais64_unbwt_*` operations using OpenMP-style threading.
14986///
14987/// In multi-threaded environments, use one context per thread for parallel executions.
14988///
14989/// - `threads`: number of worker threads (can be 0 for the implementation default).
14990///
14991/// Returns the context, or `None` on allocation failure.
14992pub fn unbwt_create_ctx_omp(threads: SaSint) -> Option<UnbwtContext> {
14993    if threads < 0 {
14994        return None;
14995    }
14996    unbwt_create_ctx_main(normalize_omp_threads(threads))
14997}
14998
14999/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with primary index in parallel using OpenMP.
15000///
15001/// # Arguments
15002/// - `t` (`[0..n-1]`): The input string.
15003/// - `u` (`[0..n-1]`): The output string (can be `t`).
15004/// - `a` (`[0..n]`): The temporary array (NOTE, temporary array must be n + 1 size).
15005/// - `freq` (`[0..255]`): The input symbol frequency table (can be `None`).
15006/// - `i`: The primary index.
15007/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
15008///
15009/// # Returns
15010/// 0 if no error occurred, -1 or -2 otherwise.
15011pub fn libsais64_unbwt_omp(
15012    t: &[u8],
15013    u: &mut [u8],
15014    a: &mut [SaSint],
15015    freq: Option<&[SaSint]>,
15016    i: SaSint,
15017    threads: SaSint,
15018) -> SaSint {
15019    libsais64_unbwt_aux_omp(
15020        t,
15021        u,
15022        a,
15023        freq,
15024        SaSint::try_from(t.len()).expect("input length must fit SaSint"),
15025        &[i],
15026        threads,
15027    )
15028}
15029
15030/// Constructs the original string from a given burrows-wheeler transformed string (BWT) with auxiliary indexes in parallel using OpenMP.
15031///
15032/// # Arguments
15033/// - `t` (`[0..n-1]`): The input string.
15034/// - `u` (`[0..n-1]`): The output string (can be `t`).
15035/// - `a` (`[0..n]`): The temporary array (NOTE, temporary array must be n + 1 size).
15036/// - `freq` (`[0..255]`): The input symbol frequency table (can be `None`).
15037/// - `r`: The sampling rate for auxiliary indexes (must be power of 2).
15038/// - `i` (`[0..(n-1)/r]`): The input auxiliary indexes.
15039/// - `threads`: The number of OpenMP threads to use (can be 0 for OpenMP default).
15040///
15041/// # Returns
15042/// 0 if no error occurred, -1 or -2 otherwise.
15043pub fn libsais64_unbwt_aux_omp(
15044    t: &[u8],
15045    u: &mut [u8],
15046    a: &mut [SaSint],
15047    freq: Option<&[SaSint]>,
15048    r: SaSint,
15049    i: &[SaSint],
15050    threads: SaSint,
15051) -> SaSint {
15052    let t_len = t.len();
15053    let n = SaSint::try_from(t_len).expect("input length must fit SaSint");
15054    if threads < 0
15055        || u.len() < t_len
15056        || a.len() < t_len
15057        || freq.is_some_and(|freq| freq.len() < ALPHABET_SIZE)
15058        || (r != n && (r < 2 || (r & (r - 1)) != 0))
15059    {
15060        return -1;
15061    }
15062    let sample_count = if n == 0 {
15063        1
15064    } else {
15065        ((n - 1) / r + 1) as usize
15066    };
15067    if i.len() < sample_count {
15068        return -1;
15069    }
15070
15071    if n <= 1 {
15072        if i[0] != n {
15073            return -1;
15074        }
15075        if n == 1 {
15076            u[0] = t[0];
15077        }
15078        return 0;
15079    }
15080
15081    for sample in i.iter().take(sample_count) {
15082        let sample = *sample;
15083        if sample <= 0 || sample > n {
15084            return -1;
15085        }
15086    }
15087
15088    let threads = if threads > 0 { threads } else { 1 };
15089    let i_uint: Vec<SaUint> = i
15090        .iter()
15091        .take(sample_count)
15092        .map(|&sample| SaUint::try_from(sample).expect("sample was validated positive"))
15093        .collect();
15094    let mut p = vec![0u64; t_len + 1];
15095    let result = unbwt_main(t, u, &mut p, n, freq, r, &i_uint, threads);
15096    for idx in 0..t_len {
15097        a[idx] = p[idx] as SaSint;
15098    }
15099    result
15100}
15101
15102/// Internal helper: bwt copy 8u.
15103#[doc(hidden)]
15104pub fn bwt_copy_8u(u: &mut [u8], a: &[SaSint], n: SaSint) {
15105    if n <= 0 {
15106        return;
15107    }
15108
15109    let n_usize = usize::try_from(n).expect("n must be non-negative");
15110    for i in 0..n_usize {
15111        u[i] = a[i] as u8;
15112    }
15113}
15114
15115/// Internal helper: bwt copy 8u (OpenMP variant).
15116#[doc(hidden)]
15117pub fn bwt_copy_8u_omp(u: &mut [u8], a: &[SaSint], n: SaSint, threads: SaSint) {
15118    if threads == 1 || n < 65_536 {
15119        bwt_copy_8u(u, a, n);
15120        return;
15121    }
15122
15123    let n_usize = usize::try_from(n).expect("n must be non-negative");
15124    assert!(u.len() >= n_usize);
15125    assert!(a.len() >= n_usize);
15126    let threads_usize = usize::try_from(threads).expect("threads must be non-negative");
15127    let chunk_size = ((n_usize / threads_usize) & !15usize).max(16);
15128    let a_ptr = a.as_ptr() as usize;
15129    run_rayon_with_threads(threads_usize, || {
15130        u[..n_usize]
15131            .par_chunks_mut(chunk_size)
15132            .enumerate()
15133            .for_each(|(chunk_index, chunk)| {
15134                let start = chunk_index * chunk_size;
15135                let dst_ptr = chunk.as_mut_ptr();
15136                let src_ptr = unsafe { (a_ptr as *const SaSint).add(start) };
15137                for offset in 0..chunk.len() {
15138                    unsafe {
15139                        *dst_ptr.add(offset) = *src_ptr.add(offset) as u8;
15140                    }
15141                }
15142            });
15143    });
15144}
15145
15146/// Internal helper: accumulate counts s32 2.
15147#[doc(hidden)]
15148pub fn accumulate_counts_s32_2(bucket00: &mut [SaSint], bucket01: &[SaSint]) {
15149    assert_eq!(bucket00.len(), bucket01.len());
15150    for (dst, src) in bucket00.iter_mut().zip(bucket01.iter()) {
15151        *dst += *src;
15152    }
15153}
15154
15155/// Internal helper: accumulate counts s32 3.
15156#[doc(hidden)]
15157pub fn accumulate_counts_s32_3(bucket00: &mut [SaSint], bucket01: &[SaSint], bucket02: &[SaSint]) {
15158    assert_eq!(bucket00.len(), bucket01.len());
15159    assert_eq!(bucket00.len(), bucket02.len());
15160    for ((dst, src1), src2) in bucket00
15161        .iter_mut()
15162        .zip(bucket01.iter())
15163        .zip(bucket02.iter())
15164    {
15165        *dst += *src1 + *src2;
15166    }
15167}
15168
15169/// Internal helper: accumulate counts s32 4.
15170#[doc(hidden)]
15171pub fn accumulate_counts_s32_4(
15172    bucket00: &mut [SaSint],
15173    bucket01: &[SaSint],
15174    bucket02: &[SaSint],
15175    bucket03: &[SaSint],
15176) {
15177    assert_eq!(bucket00.len(), bucket01.len());
15178    assert_eq!(bucket00.len(), bucket02.len());
15179    assert_eq!(bucket00.len(), bucket03.len());
15180    for (((dst, src1), src2), src3) in bucket00
15181        .iter_mut()
15182        .zip(bucket01.iter())
15183        .zip(bucket02.iter())
15184        .zip(bucket03.iter())
15185    {
15186        *dst += *src1 + *src2 + *src3;
15187    }
15188}
15189
15190/// Internal helper: accumulate counts s32 5.
15191#[doc(hidden)]
15192pub fn accumulate_counts_s32_5(
15193    bucket00: &mut [SaSint],
15194    bucket01: &[SaSint],
15195    bucket02: &[SaSint],
15196    bucket03: &[SaSint],
15197    bucket04: &[SaSint],
15198) {
15199    assert_eq!(bucket00.len(), bucket01.len());
15200    assert_eq!(bucket00.len(), bucket02.len());
15201    assert_eq!(bucket00.len(), bucket03.len());
15202    assert_eq!(bucket00.len(), bucket04.len());
15203    for ((((dst, src1), src2), src3), src4) in bucket00
15204        .iter_mut()
15205        .zip(bucket01.iter())
15206        .zip(bucket02.iter())
15207        .zip(bucket03.iter())
15208        .zip(bucket04.iter())
15209    {
15210        *dst += *src1 + *src2 + *src3 + *src4;
15211    }
15212}
15213
15214/// Internal helper: accumulate counts s32 6.
15215#[doc(hidden)]
15216pub fn accumulate_counts_s32_6(
15217    bucket00: &mut [SaSint],
15218    bucket01: &[SaSint],
15219    bucket02: &[SaSint],
15220    bucket03: &[SaSint],
15221    bucket04: &[SaSint],
15222    bucket05: &[SaSint],
15223) {
15224    assert_eq!(bucket00.len(), bucket01.len());
15225    assert_eq!(bucket00.len(), bucket02.len());
15226    assert_eq!(bucket00.len(), bucket03.len());
15227    assert_eq!(bucket00.len(), bucket04.len());
15228    assert_eq!(bucket00.len(), bucket05.len());
15229    for (((((dst, src1), src2), src3), src4), src5) in bucket00
15230        .iter_mut()
15231        .zip(bucket01.iter())
15232        .zip(bucket02.iter())
15233        .zip(bucket03.iter())
15234        .zip(bucket04.iter())
15235        .zip(bucket05.iter())
15236    {
15237        *dst += *src1 + *src2 + *src3 + *src4 + *src5;
15238    }
15239}
15240
15241/// Internal helper: accumulate counts s32 7.
15242#[doc(hidden)]
15243pub fn accumulate_counts_s32_7(
15244    bucket00: &mut [SaSint],
15245    bucket01: &[SaSint],
15246    bucket02: &[SaSint],
15247    bucket03: &[SaSint],
15248    bucket04: &[SaSint],
15249    bucket05: &[SaSint],
15250    bucket06: &[SaSint],
15251) {
15252    assert_eq!(bucket00.len(), bucket01.len());
15253    assert_eq!(bucket00.len(), bucket02.len());
15254    assert_eq!(bucket00.len(), bucket03.len());
15255    assert_eq!(bucket00.len(), bucket04.len());
15256    assert_eq!(bucket00.len(), bucket05.len());
15257    assert_eq!(bucket00.len(), bucket06.len());
15258    for ((((((dst, src1), src2), src3), src4), src5), src6) in bucket00
15259        .iter_mut()
15260        .zip(bucket01.iter())
15261        .zip(bucket02.iter())
15262        .zip(bucket03.iter())
15263        .zip(bucket04.iter())
15264        .zip(bucket05.iter())
15265        .zip(bucket06.iter())
15266    {
15267        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6;
15268    }
15269}
15270
15271/// Internal helper: accumulate counts s32 8.
15272#[doc(hidden)]
15273pub fn accumulate_counts_s32_8(
15274    bucket00: &mut [SaSint],
15275    bucket01: &[SaSint],
15276    bucket02: &[SaSint],
15277    bucket03: &[SaSint],
15278    bucket04: &[SaSint],
15279    bucket05: &[SaSint],
15280    bucket06: &[SaSint],
15281    bucket07: &[SaSint],
15282) {
15283    assert_eq!(bucket00.len(), bucket01.len());
15284    assert_eq!(bucket00.len(), bucket02.len());
15285    assert_eq!(bucket00.len(), bucket03.len());
15286    assert_eq!(bucket00.len(), bucket04.len());
15287    assert_eq!(bucket00.len(), bucket05.len());
15288    assert_eq!(bucket00.len(), bucket06.len());
15289    assert_eq!(bucket00.len(), bucket07.len());
15290    for (((((((dst, src1), src2), src3), src4), src5), src6), src7) in bucket00
15291        .iter_mut()
15292        .zip(bucket01.iter())
15293        .zip(bucket02.iter())
15294        .zip(bucket03.iter())
15295        .zip(bucket04.iter())
15296        .zip(bucket05.iter())
15297        .zip(bucket06.iter())
15298        .zip(bucket07.iter())
15299    {
15300        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6 + *src7;
15301    }
15302}
15303
15304/// Internal helper: accumulate counts s32 9.
15305#[doc(hidden)]
15306pub fn accumulate_counts_s32_9(
15307    bucket00: &mut [SaSint],
15308    bucket01: &[SaSint],
15309    bucket02: &[SaSint],
15310    bucket03: &[SaSint],
15311    bucket04: &[SaSint],
15312    bucket05: &[SaSint],
15313    bucket06: &[SaSint],
15314    bucket07: &[SaSint],
15315    bucket08: &[SaSint],
15316) {
15317    assert_eq!(bucket00.len(), bucket01.len());
15318    assert_eq!(bucket00.len(), bucket02.len());
15319    assert_eq!(bucket00.len(), bucket03.len());
15320    assert_eq!(bucket00.len(), bucket04.len());
15321    assert_eq!(bucket00.len(), bucket05.len());
15322    assert_eq!(bucket00.len(), bucket06.len());
15323    assert_eq!(bucket00.len(), bucket07.len());
15324    assert_eq!(bucket00.len(), bucket08.len());
15325    for ((((((((dst, src1), src2), src3), src4), src5), src6), src7), src8) in bucket00
15326        .iter_mut()
15327        .zip(bucket01.iter())
15328        .zip(bucket02.iter())
15329        .zip(bucket03.iter())
15330        .zip(bucket04.iter())
15331        .zip(bucket05.iter())
15332        .zip(bucket06.iter())
15333        .zip(bucket07.iter())
15334        .zip(bucket08.iter())
15335    {
15336        *dst += *src1 + *src2 + *src3 + *src4 + *src5 + *src6 + *src7 + *src8;
15337    }
15338}
15339
15340/// Internal helper: accumulate counts s32.
15341#[doc(hidden)]
15342pub fn accumulate_counts_s32(
15343    buckets: &mut [SaSint],
15344    bucket_size: FastSint,
15345    bucket_stride: FastSint,
15346    mut num_buckets: FastSint,
15347) {
15348    if num_buckets <= 1 {
15349        return;
15350    }
15351
15352    let bucket_size = usize::try_from(bucket_size).expect("bucket_size must be non-negative");
15353    let bucket_stride = usize::try_from(bucket_stride).expect("bucket_stride must be non-negative");
15354    let num_buckets_usize = usize::try_from(num_buckets).expect("num_buckets must be non-negative");
15355    assert!(buckets.len() >= bucket_size + (num_buckets_usize - 1) * bucket_stride);
15356    let bucket00_start = (num_buckets_usize - 1) * bucket_stride;
15357
15358    while num_buckets >= 9 {
15359        let start = bucket00_start
15360            - usize::try_from(num_buckets - 9).expect("non-negative") * bucket_stride;
15361        accumulate_counts_at(buckets, start, bucket_size, bucket_stride, 9);
15362        num_buckets -= 8;
15363    }
15364
15365    match num_buckets {
15366        1 => {}
15367        2..=8 => accumulate_counts_at(
15368            buckets,
15369            bucket00_start,
15370            bucket_size,
15371            bucket_stride,
15372            usize::try_from(num_buckets).expect("non-negative"),
15373        ),
15374        _ => {}
15375    }
15376}
15377
15378fn block_slice<T>(slice: &[T], block_start: FastSint, block_size: FastSint) -> &[T] {
15379    let start = usize::try_from(block_start).expect("block_start must be non-negative");
15380    let len = usize::try_from(block_size).expect("block_size must be non-negative");
15381    &slice[start..start + len]
15382}
15383
15384#[allow(dead_code)]
15385struct SharedMutArray<'a> {
15386    ptr: *mut SaSint,
15387    len: usize,
15388    _marker: PhantomData<&'a mut [SaSint]>,
15389}
15390
15391#[allow(dead_code)]
15392impl<'a> SharedMutArray<'a> {
15393    fn new(slice: &'a mut [SaSint]) -> Self {
15394        Self {
15395            ptr: slice.as_mut_ptr(),
15396            len: slice.len(),
15397            _marker: PhantomData,
15398        }
15399    }
15400
15401    fn len(&self) -> usize {
15402        self.len
15403    }
15404
15405    fn slice_mut(&mut self, start: usize, len: usize) -> &mut [SaSint] {
15406        assert!(start <= self.len);
15407        assert!(len <= self.len - start);
15408        unsafe {
15409            // The recursive driver aliases multiple logical views into one SA backing store.
15410            // This helper centralizes that checked projection so the driver can be translated
15411            // without pretending those regions are independent Rust slices.
15412            std::slice::from_raw_parts_mut(self.ptr.add(start), len)
15413        }
15414    }
15415}
15416
15417fn accumulate_counts_at(
15418    buckets: &mut [SaSint],
15419    bucket00_start: usize,
15420    bucket_size: usize,
15421    bucket_stride: usize,
15422    count: usize,
15423) {
15424    assert!((2..=9).contains(&count));
15425    assert!(bucket00_start >= (count - 1) * bucket_stride);
15426
15427    let dst_end = bucket00_start + bucket_size;
15428    let mut sums = vec![0; bucket_size];
15429
15430    for i in 0..count {
15431        let start = bucket00_start - i * bucket_stride;
15432        let end = start + bucket_size;
15433        for (sum, value) in sums.iter_mut().zip(buckets[start..end].iter()) {
15434            *sum += *value;
15435        }
15436    }
15437
15438    buckets[bucket00_start..dst_end].copy_from_slice(&sums);
15439}
15440
15441/// Internal helper: thread state size.
15442#[doc(hidden)]
15443pub fn thread_state_size() -> usize {
15444    mem::size_of::<ThreadState>()
15445}
15446
15447#[cfg(all(test, feature = "upstream-c"))]
15448mod tests {
15449    use super::*;
15450
15451    unsafe extern "C" {
15452        fn probe_public_libsais64(t: *const u8, sa: *mut SaSint, n: SaSint, fs: SaSint) -> SaSint;
15453        fn probe_public_libsais64_freq(
15454            t: *const u8,
15455            sa: *mut SaSint,
15456            n: SaSint,
15457            fs: SaSint,
15458            freq: *mut SaSint,
15459        ) -> SaSint;
15460        fn probe_public_libsais64_gsa(
15461            t: *const u8,
15462            sa: *mut SaSint,
15463            n: SaSint,
15464            fs: SaSint,
15465        ) -> SaSint;
15466        fn probe_public_libsais64_gsa_freq(
15467            t: *const u8,
15468            sa: *mut SaSint,
15469            n: SaSint,
15470            fs: SaSint,
15471            freq: *mut SaSint,
15472        ) -> SaSint;
15473        fn probe_public_libsais64_long(
15474            t: *mut SaSint,
15475            sa: *mut SaSint,
15476            n: SaSint,
15477            k: SaSint,
15478            fs: SaSint,
15479        ) -> SaSint;
15480        fn probe_public_libsais64_bwt(
15481            t: *const u8,
15482            u: *mut u8,
15483            a: *mut SaSint,
15484            n: SaSint,
15485            fs: SaSint,
15486        ) -> SaSint;
15487        fn probe_public_libsais64_bwt_freq(
15488            t: *const u8,
15489            u: *mut u8,
15490            a: *mut SaSint,
15491            n: SaSint,
15492            fs: SaSint,
15493            freq: *mut SaSint,
15494        ) -> SaSint;
15495        fn probe_public_libsais64_bwt_aux(
15496            t: *const u8,
15497            u: *mut u8,
15498            a: *mut SaSint,
15499            n: SaSint,
15500            fs: SaSint,
15501            r: SaSint,
15502            i: *mut SaSint,
15503        ) -> SaSint;
15504        fn probe_public_libsais64_bwt_aux_freq(
15505            t: *const u8,
15506            u: *mut u8,
15507            a: *mut SaSint,
15508            n: SaSint,
15509            fs: SaSint,
15510            freq: *mut SaSint,
15511            r: SaSint,
15512            i: *mut SaSint,
15513        ) -> SaSint;
15514        fn probe_public_libsais64_unbwt(
15515            t: *const u8,
15516            u: *mut u8,
15517            a: *mut SaSint,
15518            n: SaSint,
15519            i: SaSint,
15520        ) -> SaSint;
15521        fn probe_public_libsais64_unbwt_freq(
15522            t: *const u8,
15523            u: *mut u8,
15524            a: *mut SaSint,
15525            n: SaSint,
15526            freq: *const SaSint,
15527            i: SaSint,
15528        ) -> SaSint;
15529        fn probe_public_libsais64_unbwt_aux(
15530            t: *const u8,
15531            u: *mut u8,
15532            a: *mut SaSint,
15533            n: SaSint,
15534            r: SaSint,
15535            i: *const SaSint,
15536        ) -> SaSint;
15537        fn probe_public_libsais64_unbwt_aux_freq(
15538            t: *const u8,
15539            u: *mut u8,
15540            a: *mut SaSint,
15541            n: SaSint,
15542            freq: *const SaSint,
15543            r: SaSint,
15544            i: *const SaSint,
15545        ) -> SaSint;
15546        fn probe_public_libsais64_plcp(
15547            t: *const u8,
15548            sa: *const SaSint,
15549            plcp: *mut SaSint,
15550            n: SaSint,
15551        ) -> SaSint;
15552        fn probe_public_libsais64_plcp_gsa(
15553            t: *const u8,
15554            sa: *const SaSint,
15555            plcp: *mut SaSint,
15556            n: SaSint,
15557        ) -> SaSint;
15558        fn probe_public_libsais64_lcp(
15559            plcp: *const SaSint,
15560            sa: *const SaSint,
15561            lcp: *mut SaSint,
15562            n: SaSint,
15563        ) -> SaSint;
15564        fn probe_libsais64_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        fn probe_libsais64_gather_marked_lms_suffixes(
15572            sa: *mut SaSint,
15573            m: SaSint,
15574            l: FastSint,
15575            omp_block_start: FastSint,
15576            omp_block_size: FastSint,
15577        ) -> FastSint;
15578        fn probe_libsais64_renumber_and_gather_lms_suffixes_omp(
15579            sa: *mut SaSint,
15580            n: SaSint,
15581            m: SaSint,
15582            fs: SaSint,
15583            threads: SaSint,
15584        ) -> SaSint;
15585        fn probe_libsais64_renumber_distinct_lms_suffixes_32s_4k(
15586            sa: *mut SaSint,
15587            m: SaSint,
15588            name: SaSint,
15589            omp_block_start: FastSint,
15590            omp_block_size: FastSint,
15591        ) -> SaSint;
15592        fn probe_libsais64_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
15593            sa: *mut SaSint,
15594            n: SaSint,
15595            m: SaSint,
15596            threads: SaSint,
15597        ) -> SaSint;
15598        fn probe_libsais64_renumber_unique_and_nonunique_lms_suffixes_32s(
15599            t: *mut SaSint,
15600            sa: *mut SaSint,
15601            m: SaSint,
15602            f: SaSint,
15603            omp_block_start: FastSint,
15604            omp_block_size: FastSint,
15605        ) -> SaSint;
15606        fn probe_libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_omp(
15607            t: *mut SaSint,
15608            sa: *mut SaSint,
15609            m: SaSint,
15610            threads: SaSint,
15611        ) -> SaSint;
15612    }
15613
15614    #[test]
15615    fn libsais64_align_up_matches_power_of_two_alignment() {
15616        assert_eq!(align_up(0, 4096), 0);
15617        assert_eq!(align_up(1, 4096), 4096);
15618        assert_eq!(align_up(4095, 4096), 4096);
15619        assert_eq!(align_up(4096, 4096), 4096);
15620        assert_eq!(align_up(4097, 4096), 8192);
15621        assert_eq!(align_up(65, 64), 128);
15622    }
15623
15624    #[test]
15625    fn libsais64_shared_mut_array_projects_mutable_spans_from_one_backing_buffer() {
15626        let mut backing = vec![1, 2, 3, 4, 5, 6];
15627        let len;
15628        {
15629            let mut shared = SharedMutArray::new(&mut backing);
15630            shared.slice_mut(1, 3).copy_from_slice(&[20, 30, 40]);
15631            shared.slice_mut(4, 2).copy_from_slice(&[50, 60]);
15632            len = shared.len();
15633        }
15634        assert_eq!(backing, vec![1, 20, 30, 40, 50, 60]);
15635        assert_eq!(len, 6);
15636    }
15637
15638    #[test]
15639    fn libsais64_create_ctx_main_matches_single_thread_layout() {
15640        let ctx = create_ctx_main(1).expect("context");
15641        assert_eq!(ctx.buckets.len(), 8 * ALPHABET_SIZE);
15642        assert_eq!(ctx.threads, 1);
15643        assert!(ctx.thread_state.is_none());
15644    }
15645
15646    #[test]
15647    fn libsais64_create_ctx_main_allocates_thread_state_for_multi_threaded_mode() {
15648        let ctx = create_ctx_main(3).expect("context");
15649        let states = ctx.thread_state.expect("thread state");
15650        assert_eq!(states.len(), 3);
15651        assert!(states
15652            .iter()
15653            .all(|state| state.buckets.len() == 4 * ALPHABET_SIZE));
15654        assert!(states
15655            .iter()
15656            .all(|state| state.cache.len() == LIBSAIS_PER_THREAD_CACHE_SIZE));
15657    }
15658
15659    #[test]
15660    fn libsais64_create_ctx_wraps_single_thread_main_context() {
15661        let ctx = create_ctx().expect("context");
15662        assert_eq!(ctx.threads, 1);
15663        assert_eq!(ctx.buckets.len(), 8 * ALPHABET_SIZE);
15664        assert!(ctx.thread_state.is_none());
15665    }
15666
15667    #[test]
15668    fn libsais64_free_ctx_accepts_context_value() {
15669        let ctx = create_ctx().expect("context");
15670        free_ctx(ctx);
15671    }
15672
15673    #[test]
15674    fn libsais64_unbwt_create_ctx_main_allocates_expected_buffers() {
15675        let ctx = unbwt_create_ctx_main(3).expect("context");
15676        assert_eq!(ctx.bucket2.len(), ALPHABET_SIZE * ALPHABET_SIZE);
15677        assert_eq!(ctx.fastbits.len(), 1 + (1 << UNBWT_FASTBITS));
15678        assert_eq!(
15679            ctx.buckets.as_ref().expect("parallel buckets").len(),
15680            3 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)
15681        );
15682        assert_eq!(ctx.threads, 3);
15683    }
15684
15685    #[test]
15686    fn libsais64_unbwt_compute_histogram_counts_bytes() {
15687        let t = b"banana";
15688        let mut count = vec![0u64; ALPHABET_SIZE];
15689        unbwt_compute_histogram(t, t.len() as FastSint, &mut count);
15690        assert_eq!(count[b'a' as usize], 3);
15691        assert_eq!(count[b'b' as usize], 1);
15692        assert_eq!(count[b'n' as usize], 2);
15693    }
15694
15695    #[test]
15696    fn libsais64_unbwt_transpose_bucket2_swaps_matrix_entries() {
15697        let mut bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15698        bucket2[(2 << 8) + 1] = 7;
15699        bucket2[(1 << 8) + 2] = 9;
15700        unbwt_transpose_bucket2(&mut bucket2);
15701        assert_eq!(bucket2[(1 << 8) + 2], 7);
15702        assert_eq!(bucket2[(2 << 8) + 1], 9);
15703    }
15704
15705    #[test]
15706    fn libsais64_unbwt_init_single_builds_monotone_fastbits_and_writes_psi() {
15707        let t = b"annb\x00aa";
15708        let mut p = vec![0u64; t.len() + 1];
15709        let mut bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15710        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
15711        let i = vec![4u64];
15712
15713        unbwt_init_single(
15714            t,
15715            &mut p,
15716            t.len() as SaSint,
15717            None,
15718            &i,
15719            &mut bucket2,
15720            &mut fastbits,
15721        );
15722
15723        assert!(fastbits
15724            .iter()
15725            .all(|&value| usize::from(value) < ALPHABET_SIZE * ALPHABET_SIZE));
15726        assert!(fastbits.iter().any(|&value| value != 0));
15727        assert!(p.iter().any(|&value| value != 0));
15728    }
15729
15730    #[test]
15731    fn libsais64_unbwt_init_parallel_currently_matches_single_initializer() {
15732        let t = b"annb\x00aa";
15733        let mut p_single = vec![0u64; t.len() + 1];
15734        let mut p_parallel = vec![0u64; t.len() + 1];
15735        let mut bucket2_single = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15736        let mut bucket2_parallel = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15737        let mut fastbits_single = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
15738        let mut fastbits_parallel = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
15739        let i = vec![4u64];
15740        let mut scratch = vec![0u64; 2 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)];
15741
15742        unbwt_init_single(
15743            t,
15744            &mut p_single,
15745            t.len() as SaSint,
15746            None,
15747            &i,
15748            &mut bucket2_single,
15749            &mut fastbits_single,
15750        );
15751        unbwt_init_parallel(
15752            t,
15753            &mut p_parallel,
15754            t.len() as SaSint,
15755            None,
15756            &i,
15757            &mut bucket2_parallel,
15758            &mut fastbits_parallel,
15759            Some(&mut scratch),
15760            2,
15761        );
15762
15763        assert_eq!(p_parallel, p_single);
15764        assert_eq!(bucket2_parallel, bucket2_single);
15765        assert_eq!(fastbits_parallel, fastbits_single);
15766    }
15767
15768    #[test]
15769    fn libsais64_unbwt_decode_1_writes_big_endian_symbol_words() {
15770        let mut u = vec![0u8; 4];
15771        let p = vec![1u64, 0u64];
15772        let mut bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15773        bucket2[0x1234] = 0;
15774        bucket2[0x1235] = 2;
15775        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
15776        fastbits[0] = 0x1234;
15777        let mut i0 = 0usize;
15778
15779        unbwt_decode_1(&mut u, &p, &bucket2, &fastbits, 0, &mut i0, 2);
15780
15781        assert_eq!(u, vec![0x12, 0x35, 0x12, 0x35]);
15782        assert_eq!(i0, 0);
15783    }
15784
15785    #[test]
15786    fn libsais64_unbwt_decode_dispatches_two_block_tail_shape() {
15787        let mut u = vec![0u8; 8];
15788        let p = vec![1u64, 0u64];
15789        let mut bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
15790        bucket2[0x1234] = 0;
15791        bucket2[0x1235] = 2;
15792        let mut fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
15793        fastbits[0] = 0x1234;
15794        let i = vec![0u64, 0u64];
15795
15796        unbwt_decode(&mut u, &p, 4, 2, &i, &bucket2, &fastbits, 2, 2);
15797
15798        assert_eq!(u, vec![0x12, 0x35, 0x12, 0x35, 0x00, 0x00, 0x00, 0x00]);
15799    }
15800
15801    fn brute_force_suffix_array_u8(t: &[u8]) -> Vec<SaSint> {
15802        let mut sa: Vec<SaSint> = (0..t.len())
15803            .map(|index| SaSint::try_from(index).expect("index must fit SaSint"))
15804            .collect();
15805        sa.sort_by(|&lhs, &rhs| {
15806            t[usize::try_from(lhs).expect("non-negative")..]
15807                .cmp(&t[usize::try_from(rhs).expect("non-negative")..])
15808        });
15809        sa
15810    }
15811
15812    fn brute_force_plcp_u8(t: &[u8], sa: &[SaSint]) -> Vec<SaSint> {
15813        let mut rank = vec![0usize; t.len()];
15814        for (i, &suffix) in sa.iter().enumerate() {
15815            rank[usize::try_from(suffix).expect("suffix index must be non-negative")] = i;
15816        }
15817
15818        let mut plcp = vec![0; t.len()];
15819        for i in 0..t.len() {
15820            let r = rank[i];
15821            let prev = if r == 0 {
15822                t.len()
15823            } else {
15824                usize::try_from(sa[r - 1]).expect("suffix index must be non-negative")
15825            };
15826            if prev == t.len() {
15827                plcp[i] = 0;
15828                continue;
15829            }
15830
15831            let mut l = 0usize;
15832            while i + l < t.len() && prev + l < t.len() && t[i + l] == t[prev + l] {
15833                l += 1;
15834            }
15835            plcp[i] = l as SaSint;
15836        }
15837        plcp
15838    }
15839
15840    fn brute_force_lcp_from_sa_u8(t: &[u8], sa: &[SaSint]) -> Vec<SaSint> {
15841        let mut lcp = vec![0; sa.len()];
15842        for i in 0..sa.len() {
15843            let lhs = usize::try_from(sa[i]).expect("suffix index must be non-negative");
15844            let rhs = if i == 0 {
15845                sa.len()
15846            } else {
15847                usize::try_from(sa[i - 1]).expect("suffix index must be non-negative")
15848            };
15849            if rhs == sa.len() {
15850                lcp[i] = 0;
15851                continue;
15852            }
15853
15854            let mut l = 0usize;
15855            while lhs + l < t.len() && rhs + l < t.len() && t[lhs + l] == t[rhs + l] {
15856                l += 1;
15857            }
15858            lcp[i] = l as SaSint;
15859        }
15860        lcp
15861    }
15862
15863    fn make_libsais64_recursive_main_32s_text(repeats: usize) -> Vec<SaSint> {
15864        let motif = [9, 4, 9, 2, 9, 4, 9, 1];
15865        let mut t = Vec::with_capacity(repeats * motif.len() + 1);
15866        for _ in 0..repeats {
15867            t.extend_from_slice(&motif);
15868        }
15869        t.push(0);
15870        t
15871    }
15872
15873    fn make_libsais64_large_main_32s_stress_text(len: usize, alphabet: SaSint) -> Vec<SaSint> {
15874        let mut state: u32 = 0x1357_9bdf;
15875        let mut t = Vec::with_capacity(len + 1);
15876
15877        for i in 0..len {
15878            state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15879            let mut value = ((state >> 16) % (alphabet as u32 - 1)) as SaSint + 1;
15880
15881            if i % 17 < 8 {
15882                value = ((i / 17) as SaSint % 11) + 1;
15883            }
15884            if i % 29 < 10 {
15885                value = (((i / 29) as SaSint * 3) % 19) + 1;
15886            }
15887            if i % 64 >= 48 {
15888                value = t[i - 48];
15889            }
15890
15891            t.push(value);
15892        }
15893
15894        t.push(0);
15895        t
15896    }
15897
15898    fn assert_libsais64_main_32s_entry_matches_public_c_long(
15899        t: Vec<SaSint>,
15900        k: SaSint,
15901        fs: SaSint,
15902        compare_full_sa: bool,
15903    ) {
15904        let n = t.len() as SaSint;
15905        let n_usize = t.len();
15906        let threads = 1;
15907        let extra = usize::try_from(fs).expect("fs must be non-negative");
15908
15909        let mut c_t = t.clone();
15910        let mut c_sa = vec![0; t.len() + extra];
15911        let c_result =
15912            unsafe { probe_public_libsais64_long(c_t.as_mut_ptr(), c_sa.as_mut_ptr(), n, k, fs) };
15913
15914        let mut rust_t = t;
15915        let mut rust_sa = vec![0; rust_t.len() + extra];
15916        let mut thread_state = alloc_thread_state(threads).expect("thread state");
15917        let rust_result = libsais64_main_32s_entry(
15918            &mut rust_t,
15919            &mut rust_sa,
15920            n,
15921            k,
15922            fs,
15923            threads,
15924            &mut thread_state,
15925        );
15926
15927        assert_eq!(rust_result, c_result);
15928        assert_eq!(rust_t, c_t);
15929        if compare_full_sa {
15930            assert_eq!(rust_sa, c_sa);
15931        } else {
15932            assert_eq!(&rust_sa[..n_usize], &c_sa[..n_usize]);
15933        }
15934    }
15935
15936    fn assert_libsais64_main_32s_entry_matches_public_c_long_for_branch(k: SaSint) {
15937        assert_libsais64_main_32s_entry_matches_public_c_long(
15938            vec![17, 3, 17, 9, 5, 9, 2, 11, 2, 7, 1, 7, 0],
15939            k,
15940            0,
15941            true,
15942        );
15943    }
15944
15945    #[test]
15946    fn libsais64_matches_bruteforce_suffix_array_for_small_text() {
15947        let t = b"banana";
15948        let mut sa = vec![0; t.len()];
15949        let mut freq = vec![0; ALPHABET_SIZE];
15950
15951        let result = libsais64(t, &mut sa, 0, Some(&mut freq));
15952
15953        assert_eq!(result, 0);
15954        assert_eq!(sa, brute_force_suffix_array_u8(t));
15955        assert_eq!(freq[b'a' as usize], 3);
15956        assert_eq!(freq[b'b' as usize], 1);
15957        assert_eq!(freq[b'n' as usize], 2);
15958    }
15959
15960    #[test]
15961    fn libsais64_int_matches_bruteforce_suffix_array_for_small_integer_text() {
15962        let mut t = vec![2, 1, 3, 1, 0];
15963        let expected = {
15964            let mut sa: Vec<SaSint> = (0..t.len())
15965                .map(|index| SaSint::try_from(index).expect("index must fit SaSint"))
15966                .collect();
15967            sa.sort_by(|&lhs, &rhs| {
15968                t[usize::try_from(lhs).expect("non-negative")..]
15969                    .cmp(&t[usize::try_from(rhs).expect("non-negative")..])
15970            });
15971            sa
15972        };
15973        let mut sa = vec![0; t.len()];
15974
15975        let result = libsais64_int(&mut t, &mut sa, 4, 0);
15976
15977        assert_eq!(result, 0);
15978        assert_eq!(sa, expected);
15979    }
15980
15981    #[test]
15982    fn libsais64_plcp_matches_bruteforce_for_small_text() {
15983        let t = b"banana";
15984        let sa = brute_force_suffix_array_u8(t);
15985        let expected = brute_force_plcp_u8(t, &sa);
15986        let mut plcp = vec![0; t.len()];
15987
15988        let result = libsais64_plcp(t, &sa, &mut plcp);
15989
15990        assert_eq!(result, 0);
15991        assert_eq!(plcp, expected);
15992    }
15993
15994    #[test]
15995    fn libsais64_plcp_gsa_stops_at_separator() {
15996        let t = b"ab\0b\0";
15997        let sa = brute_force_suffix_array_u8(t);
15998        let mut plcp = vec![0; t.len()];
15999
16000        let result = libsais64_plcp_gsa(t, &sa, &mut plcp);
16001
16002        assert_eq!(result, 0);
16003        assert_eq!(plcp[2], 0);
16004        assert_eq!(plcp[4], 0);
16005    }
16006
16007    #[test]
16008    fn libsais64_lcp_matches_bruteforce_for_small_text() {
16009        let t = b"banana";
16010        let sa = brute_force_suffix_array_u8(t);
16011        let plcp = brute_force_plcp_u8(t, &sa);
16012        let expected = brute_force_lcp_from_sa_u8(t, &sa);
16013        let mut lcp = vec![0; t.len()];
16014
16015        let result = libsais64_lcp(&plcp, &sa, &mut lcp);
16016
16017        assert_eq!(result, 0);
16018        assert_eq!(lcp, expected);
16019    }
16020
16021    #[test]
16022    fn libsais64_unbwt_init_parallel_uses_block_partition_for_large_inputs() {
16023        let n = 70_003usize;
16024        let t: Vec<u8> = (0..n)
16025            .map(|i| i.wrapping_mul(37).wrapping_add(i >> 3) as u8)
16026            .collect();
16027        let i = [12_345u64];
16028
16029        let mut single_p = vec![0u64; n + 1];
16030        let mut threaded_p = vec![0u64; n + 1];
16031        let mut single_bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
16032        let mut threaded_bucket2 = vec![0u64; ALPHABET_SIZE * ALPHABET_SIZE];
16033        let mut single_fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16034        let mut threaded_fastbits = vec![0u16; 1 + (1 << UNBWT_FASTBITS)];
16035        let mut buckets = vec![0u64; 4 * (ALPHABET_SIZE + ALPHABET_SIZE * ALPHABET_SIZE)];
16036
16037        unbwt_init_single(
16038            &t,
16039            &mut single_p,
16040            n as SaSint,
16041            None,
16042            &i,
16043            &mut single_bucket2,
16044            &mut single_fastbits,
16045        );
16046        unbwt_init_parallel(
16047            &t,
16048            &mut threaded_p,
16049            n as SaSint,
16050            None,
16051            &i,
16052            &mut threaded_bucket2,
16053            &mut threaded_fastbits,
16054            Some(&mut buckets),
16055            4,
16056        );
16057
16058        assert_eq!(threaded_p, single_p);
16059        assert_eq!(threaded_bucket2, single_bucket2);
16060        assert_eq!(threaded_fastbits, single_fastbits);
16061    }
16062
16063    #[test]
16064    fn libsais64_radix_sort_lms_suffixes_8u_places_suffixes_by_bucket() {
16065        let t = vec![1_u8, 0, 1, 0];
16066        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
16067        let mut induction_bucket = vec![0; 2 * ALPHABET_SIZE];
16068        induction_bucket[buckets_index2(0, 0)] = 2;
16069        induction_bucket[buckets_index2(1, 0)] = 4;
16070        radix_sort_lms_suffixes_8u(&t, &mut sa, &mut induction_bucket, 4, 4);
16071        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
16072    }
16073
16074    #[test]
16075    fn libsais64_radix_sort_lms_suffixes_8u_omp_wraps_sequential_version() {
16076        let t = vec![9_u8, 1, 0, 1, 0];
16077        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
16078        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16079        buckets[4 * ALPHABET_SIZE + buckets_index2(0, 0)] = 2;
16080        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 4;
16081        let mut thread_state = alloc_thread_state(2).unwrap();
16082        radix_sort_lms_suffixes_8u_omp(&t, &mut sa, 9, 5, 0, &mut buckets, 2, &mut thread_state);
16083        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
16084    }
16085
16086    #[test]
16087    fn libsais64_radix_sort_lms_suffixes_32s_6k_places_suffixes_by_bucket() {
16088        let t = vec![1, 0, 1, 0];
16089        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
16090        let mut induction_bucket = vec![2, 4];
16091        radix_sort_lms_suffixes_32s_6k(&t, &mut sa, &mut induction_bucket, 4, 4);
16092        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
16093    }
16094
16095    #[test]
16096    fn libsais64_radix_sort_lms_suffixes_32s_2k_places_suffixes_by_bucket() {
16097        let t = vec![1, 0, 1, 0];
16098        let mut sa = vec![9, 9, 9, 9, 0, 1, 2, 3];
16099        let mut induction_bucket = vec![2, 0, 4, 0];
16100        radix_sort_lms_suffixes_32s_2k(&t, &mut sa, &mut induction_bucket, 4, 4);
16101        assert_eq!(&sa[..4], &[1, 3, 0, 2]);
16102    }
16103
16104    #[test]
16105    fn libsais64_radix_sort_lms_suffixes_32s_6k_omp_wraps_sequential_version() {
16106        let t = vec![9, 1, 0, 1, 0];
16107        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
16108        let mut induction_bucket = vec![2, 4];
16109        let mut thread_state = alloc_thread_state(2).unwrap();
16110        radix_sort_lms_suffixes_32s_6k_omp(
16111            &t,
16112            &mut sa,
16113            9,
16114            5,
16115            &mut induction_bucket,
16116            2,
16117            &mut thread_state,
16118        );
16119        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
16120    }
16121
16122    #[test]
16123    fn libsais64_radix_sort_lms_suffixes_32s_2k_omp_wraps_sequential_version() {
16124        let t = vec![9, 1, 0, 1, 0];
16125        let mut sa = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
16126        let mut induction_bucket = vec![2, 0, 4, 0];
16127        let mut thread_state = alloc_thread_state(2).unwrap();
16128        radix_sort_lms_suffixes_32s_2k_omp(
16129            &t,
16130            &mut sa,
16131            9,
16132            5,
16133            &mut induction_bucket,
16134            2,
16135            &mut thread_state,
16136        );
16137        assert_eq!(&sa[..4], &[2, 4, 1, 3]);
16138    }
16139
16140    #[test]
16141    fn libsais64_radix_sort_lms_suffixes_32s_block_omp_runs_cache_pipeline() {
16142        let t = vec![9, 1, 0, 1, 0];
16143        let mut sa_6k = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
16144        let mut bucket_6k = vec![2, 4];
16145        let mut cache = vec![ThreadCache::default(); 9];
16146        radix_sort_lms_suffixes_32s_6k_block_omp(
16147            &t,
16148            &mut sa_6k,
16149            &mut bucket_6k,
16150            &mut cache,
16151            5,
16152            4,
16153            2,
16154        );
16155        assert_eq!(&sa_6k[..4], &[2, 4, 1, 3]);
16156
16157        let mut sa_2k = vec![9, 9, 9, 9, 9, 1, 2, 3, 4];
16158        let mut bucket_2k = vec![2, 0, 4, 0];
16159        cache.fill(ThreadCache::default());
16160        radix_sort_lms_suffixes_32s_2k_block_omp(
16161            &t,
16162            &mut sa_2k,
16163            &mut bucket_2k,
16164            &mut cache,
16165            5,
16166            4,
16167            2,
16168        );
16169        assert_eq!(&sa_2k[..4], &[2, 4, 1, 3]);
16170    }
16171
16172    #[test]
16173    fn libsais64_radix_sort_lms_suffixes_8u_omp_uses_thread_state_for_large_inputs() {
16174        let m = 65_600usize;
16175        let n = 2 * m + 16;
16176        let start = n - m + 1;
16177        let t: Vec<u8> = (0..n).map(|i| (i % 4) as u8).collect();
16178        let suffixes: Vec<SaSint> = (0..m - 1).map(|i| i as SaSint).collect();
16179
16180        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16181        for &suffix in &suffixes {
16182            buckets[4 * ALPHABET_SIZE + buckets_index2(t[suffix as usize] as usize, 0)] += 1;
16183        }
16184        let mut sum = 0;
16185        for symbol in 0..ALPHABET_SIZE {
16186            let bucket = 4 * ALPHABET_SIZE + buckets_index2(symbol, 0);
16187            sum += buckets[bucket];
16188            buckets[bucket] = sum;
16189        }
16190
16191        let mut sa_single = vec![0; n];
16192        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
16193        let mut sa_threaded = sa_single.clone();
16194        let mut buckets_single = buckets.clone();
16195        let mut buckets_threaded = buckets;
16196        let mut thread_state = alloc_thread_state(4).unwrap();
16197        thread_state[3].m = m as FastSint;
16198
16199        radix_sort_lms_suffixes_8u_omp(
16200            &t,
16201            &mut sa_single,
16202            n as SaSint,
16203            m as SaSint,
16204            0,
16205            &mut buckets_single,
16206            1,
16207            &mut [],
16208        );
16209        radix_sort_lms_suffixes_8u_omp(
16210            &t,
16211            &mut sa_threaded,
16212            n as SaSint,
16213            m as SaSint,
16214            0,
16215            &mut buckets_threaded,
16216            4,
16217            &mut thread_state,
16218        );
16219
16220        assert_eq!(sa_threaded, sa_single);
16221    }
16222
16223    #[test]
16224    fn libsais64_radix_sort_lms_suffixes_32s_omp_uses_block_pipeline_for_large_inputs() {
16225        let m = 65_600usize;
16226        let n = 2 * m + 16;
16227        let start = n - m + 1;
16228        let t: Vec<SaSint> = (0..n).map(|i| (i % 4) as SaSint).collect();
16229        let suffixes: Vec<SaSint> = (0..m - 1).map(|i| i as SaSint).collect();
16230
16231        let mut bucket_ends = vec![0; 4];
16232        for &suffix in &suffixes {
16233            bucket_ends[t[suffix as usize] as usize] += 1;
16234        }
16235        let mut sum = 0;
16236        for bucket in &mut bucket_ends {
16237            sum += *bucket;
16238            *bucket = sum;
16239        }
16240
16241        let mut sa_single = vec![0; n];
16242        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
16243        let mut sa_threaded = sa_single.clone();
16244        let mut bucket_single = bucket_ends.clone();
16245        let mut bucket_threaded = bucket_ends.clone();
16246        let mut thread_state = alloc_thread_state(4).unwrap();
16247
16248        radix_sort_lms_suffixes_32s_6k_omp(
16249            &t,
16250            &mut sa_single,
16251            n as SaSint,
16252            m as SaSint,
16253            &mut bucket_single,
16254            1,
16255            &mut [],
16256        );
16257        radix_sort_lms_suffixes_32s_6k_omp(
16258            &t,
16259            &mut sa_threaded,
16260            n as SaSint,
16261            m as SaSint,
16262            &mut bucket_threaded,
16263            4,
16264            &mut thread_state,
16265        );
16266        assert_eq!(sa_threaded, sa_single);
16267        assert_eq!(bucket_threaded, bucket_single);
16268
16269        let mut bucket_2k = vec![0; 8];
16270        for (symbol, &end) in bucket_ends.iter().enumerate() {
16271            bucket_2k[buckets_index2(symbol, 0)] = end;
16272        }
16273        let mut sa_single = vec![0; n];
16274        sa_single[start..start + suffixes.len()].copy_from_slice(&suffixes);
16275        let mut sa_threaded = sa_single.clone();
16276        let mut bucket_single = bucket_2k.clone();
16277        let mut bucket_threaded = bucket_2k;
16278
16279        radix_sort_lms_suffixes_32s_2k_omp(
16280            &t,
16281            &mut sa_single,
16282            n as SaSint,
16283            m as SaSint,
16284            &mut bucket_single,
16285            1,
16286            &mut [],
16287        );
16288        radix_sort_lms_suffixes_32s_2k_omp(
16289            &t,
16290            &mut sa_threaded,
16291            n as SaSint,
16292            m as SaSint,
16293            &mut bucket_threaded,
16294            4,
16295            &mut thread_state,
16296        );
16297        assert_eq!(sa_threaded, sa_single);
16298        assert_eq!(bucket_threaded, bucket_single);
16299    }
16300
16301    #[test]
16302    fn libsais64_radix_sort_lms_suffixes_32s_1k_collects_lms_suffixes() {
16303        let t = vec![2, 1, 3, 1, 0];
16304        let mut sa = vec![0; t.len()];
16305        let mut buckets = vec![0, 2, 4, 5];
16306        let m = radix_sort_lms_suffixes_32s_1k(&t, &mut sa, t.len() as SaSint, &mut buckets);
16307        assert!(m >= 0);
16308    }
16309
16310    #[test]
16311    fn libsais64_radix_sort_set_markers_32s_6k_marks_target_suffixes() {
16312        let mut sa = vec![0; 6];
16313        let induction_bucket = vec![1, 3, 5];
16314        radix_sort_set_markers_32s_6k(&mut sa, &induction_bucket, 0, 3);
16315        assert_eq!(sa[1], SAINT_MIN);
16316        assert_eq!(sa[3], SAINT_MIN);
16317        assert_eq!(sa[5], SAINT_MIN);
16318    }
16319
16320    #[test]
16321    fn libsais64_radix_sort_set_markers_32s_4k_marks_target_suffixes() {
16322        let mut sa = vec![0; 6];
16323        let induction_bucket = vec![1, 0, 3, 0, 5, 0];
16324        radix_sort_set_markers_32s_4k(&mut sa, &induction_bucket, 0, 3);
16325        assert_eq!(sa[1], SUFFIX_GROUP_MARKER);
16326        assert_eq!(sa[3], SUFFIX_GROUP_MARKER);
16327        assert_eq!(sa[5], SUFFIX_GROUP_MARKER);
16328    }
16329
16330    #[test]
16331    fn libsais64_radix_sort_set_markers_32s_6k_omp_wraps_sequential_version() {
16332        let mut sa = vec![0; 6];
16333        let induction_bucket = vec![1, 3, 5];
16334        radix_sort_set_markers_32s_6k_omp(&mut sa, 4, &induction_bucket, 2);
16335        assert_eq!(sa[1], SAINT_MIN);
16336        assert_eq!(sa[3], SAINT_MIN);
16337        assert_eq!(sa[5], SAINT_MIN);
16338    }
16339
16340    #[test]
16341    fn libsais64_radix_sort_set_markers_32s_4k_omp_wraps_sequential_version() {
16342        let mut sa = vec![0; 6];
16343        let induction_bucket = vec![1, 0, 3, 0, 5, 0];
16344        radix_sort_set_markers_32s_4k_omp(&mut sa, 4, &induction_bucket, 2);
16345        assert_eq!(sa[1], SUFFIX_GROUP_MARKER);
16346        assert_eq!(sa[3], SUFFIX_GROUP_MARKER);
16347        assert_eq!(sa[5], SUFFIX_GROUP_MARKER);
16348    }
16349
16350    #[test]
16351    fn libsais64_radix_sort_set_markers_32s_omp_partitions_large_inputs() {
16352        let k = 65_600usize;
16353        let induction_bucket_6k: Vec<SaSint> = (0..k).map(|i| i as SaSint).collect();
16354        let mut sa_single = vec![0; k];
16355        let mut sa_threaded = vec![0; k];
16356        radix_sort_set_markers_32s_6k_omp(&mut sa_single, k as SaSint, &induction_bucket_6k, 1);
16357        radix_sort_set_markers_32s_6k_omp(&mut sa_threaded, k as SaSint, &induction_bucket_6k, 4);
16358        assert_eq!(sa_threaded, sa_single);
16359
16360        let mut induction_bucket_4k = vec![0; 2 * k];
16361        for i in 0..k {
16362            induction_bucket_4k[buckets_index2(i, 0)] = i as SaSint;
16363        }
16364        let mut sa_single = vec![0; k];
16365        let mut sa_threaded = vec![0; k];
16366        radix_sort_set_markers_32s_4k_omp(&mut sa_single, k as SaSint, &induction_bucket_4k, 1);
16367        radix_sort_set_markers_32s_4k_omp(&mut sa_threaded, k as SaSint, &induction_bucket_4k, 4);
16368        assert_eq!(sa_threaded, sa_single);
16369    }
16370
16371    #[test]
16372    fn libsais64_partial_sorting_scan_left_to_right_8u_emits_induced_suffixes() {
16373        let t = vec![2_u8, 1, 3, 1, 0];
16374        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
16375        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16376        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 2;
16377        let d = partial_sorting_scan_left_to_right_8u(&t, &mut sa, &mut buckets, 0, 0, 2);
16378        assert!(d >= 0);
16379        assert!(sa.iter().any(|&v| v != 0));
16380    }
16381
16382    #[test]
16383    fn libsais64_partial_sorting_scan_left_to_right_8u_omp_wraps_sequential_version() {
16384        let t = vec![2_u8, 1, 3, 1, 0];
16385        let mut sa = vec![0; 8];
16386        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16387        buckets[4 * ALPHABET_SIZE + buckets_index2(0, 0)] = 1;
16388        let mut thread_state = alloc_thread_state(2).unwrap();
16389        let d = partial_sorting_scan_left_to_right_8u_omp(
16390            &t,
16391            &mut sa,
16392            5,
16393            4,
16394            &mut buckets,
16395            0,
16396            0,
16397            2,
16398            &mut thread_state,
16399        );
16400        assert!(d >= 1);
16401    }
16402
16403    #[test]
16404    fn libsais64_partial_sorting_scan_left_to_right_32s_6k_emits_induced_suffixes() {
16405        let t = vec![2, 1, 3, 1, 0];
16406        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
16407        let mut buckets = vec![0; 4 * 4];
16408        buckets[buckets_index4(1, 0)] = 2;
16409        let d = partial_sorting_scan_left_to_right_32s_6k(&t, &mut sa, &mut buckets, 0, 0, 2);
16410        assert!(d >= 0);
16411        assert!(sa.iter().any(|&v| v != 0));
16412    }
16413
16414    #[test]
16415    fn libsais64_partial_sorting_scan_left_to_right_32s_4k_emits_induced_suffixes() {
16416        let t = vec![2, 1, 3, 1, 0];
16417        let k = 4usize;
16418        let mut sa = vec![2 | SUFFIX_GROUP_MARKER, 4, 0, 0, 0, 0];
16419        let mut buckets = vec![0; 4 * k];
16420        buckets[2 * k + 1] = 2;
16421        let d = partial_sorting_scan_left_to_right_32s_4k(
16422            &t,
16423            &mut sa,
16424            k as SaSint,
16425            &mut buckets,
16426            0,
16427            0,
16428            2,
16429        );
16430        assert!(d >= 0);
16431        assert!(sa.iter().any(|&v| v != 0));
16432    }
16433
16434    #[test]
16435    fn libsais64_partial_sorting_scan_left_to_right_32s_1k_emits_induced_suffixes() {
16436        let t = vec![2, 1, 3, 1, 0];
16437        let mut sa = vec![2, 4, 0, 0, 0, 0];
16438        let mut buckets = vec![0; 4];
16439        buckets[1] = 2;
16440        partial_sorting_scan_left_to_right_32s_1k(&t, &mut sa, &mut buckets, 0, 2);
16441        assert!(sa.iter().any(|&v| v != 0));
16442    }
16443
16444    #[test]
16445    fn libsais64_partial_sorting_scan_left_to_right_32s_6k_omp_wraps_sequential_version() {
16446        let t = vec![2, 1, 3, 1, 0];
16447        let mut sa = vec![0; 8];
16448        let mut buckets = vec![0; 4 * 4];
16449        let mut thread_state = alloc_thread_state(2).unwrap();
16450        let d = partial_sorting_scan_left_to_right_32s_6k_omp(
16451            &t,
16452            &mut sa,
16453            5,
16454            &mut buckets,
16455            0,
16456            0,
16457            2,
16458            &mut thread_state,
16459        );
16460        assert!(d >= 1);
16461    }
16462
16463    #[test]
16464    fn libsais64_partial_sorting_scan_left_to_right_32s_4k_omp_wraps_sequential_version() {
16465        let t = vec![2, 1, 3, 1, 0];
16466        let k = 4usize;
16467        let mut sa = vec![0; 8];
16468        let mut buckets = vec![0; 4 * k];
16469        let mut thread_state = alloc_thread_state(2).unwrap();
16470        let d = partial_sorting_scan_left_to_right_32s_4k_omp(
16471            &t,
16472            &mut sa,
16473            5,
16474            k as SaSint,
16475            &mut buckets,
16476            0,
16477            2,
16478            &mut thread_state,
16479        );
16480        assert!(d >= 1);
16481    }
16482
16483    #[test]
16484    fn libsais64_partial_sorting_scan_left_to_right_32s_1k_omp_wraps_sequential_version() {
16485        let t = vec![2, 1, 3, 1, 0];
16486        let mut sa = vec![0; 8];
16487        let mut buckets = vec![0; 4];
16488        let mut thread_state = alloc_thread_state(2).unwrap();
16489        partial_sorting_scan_left_to_right_32s_1k_omp(
16490            &t,
16491            &mut sa,
16492            5,
16493            &mut buckets,
16494            2,
16495            &mut thread_state,
16496        );
16497        assert!(sa.iter().any(|&v| v != 0));
16498    }
16499
16500    #[test]
16501    fn libsais64_partial_sorting_scan_left_to_right_32s_6k_block_gather_records_bucket_symbols() {
16502        let t = vec![3, 1, 2, 0];
16503        let mut sa = vec![2 | SAINT_MIN, 0, 0, 0];
16504        let mut cache = vec![ThreadCache::default(); 1];
16505
16506        partial_sorting_scan_left_to_right_32s_6k_block_gather(&t, &mut sa, &mut cache, 0, 1);
16507
16508        assert_eq!(cache[0].index, 2 | SAINT_MIN);
16509        assert_eq!(cache[0].symbol, buckets_index4(1, 1) as SaSint);
16510    }
16511
16512    #[test]
16513    fn libsais64_partial_sorting_scan_left_to_right_32s_1k_block_gather_zeroes_positive_entries() {
16514        let t = vec![3, 1, 2, 0];
16515        let mut sa = vec![2, 0, 0, 0];
16516        let mut cache = vec![ThreadCache::default(); 1];
16517
16518        partial_sorting_scan_left_to_right_32s_1k_block_gather(&t, &mut sa, &mut cache, 0, 1);
16519
16520        assert_eq!(cache[0].symbol, 1);
16521        assert_eq!(cache[0].index, 1);
16522        assert_eq!(sa[0], 0);
16523    }
16524
16525    #[test]
16526    fn libsais64_partial_sorting_scan_left_to_right_32s_1k_block_omp_uses_relative_cache() {
16527        let block_start = 20_000usize;
16528        let block_size = 16_384usize;
16529        let n = block_start + block_size + 8;
16530        let t = vec![1; n];
16531        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
16532
16533        let mut sa_single = vec![0; n];
16534        sa_single[block_start..block_start + block_size].copy_from_slice(&suffixes);
16535        let mut sa_threaded = sa_single.clone();
16536        let mut bucket_single = vec![0, 0];
16537        let mut bucket_threaded = bucket_single.clone();
16538        let mut cache = vec![ThreadCache::default(); 4 * LIBSAIS_PER_THREAD_CACHE_SIZE];
16539
16540        partial_sorting_scan_left_to_right_32s_1k(
16541            &t,
16542            &mut sa_single,
16543            &mut bucket_single,
16544            block_start as FastSint,
16545            block_size as FastSint,
16546        );
16547        partial_sorting_scan_left_to_right_32s_1k_block_omp(
16548            &t,
16549            &mut sa_threaded,
16550            &mut bucket_threaded,
16551            &mut cache,
16552            block_start as FastSint,
16553            block_size as FastSint,
16554            4,
16555        );
16556
16557        assert_eq!(sa_threaded, sa_single);
16558        assert_eq!(bucket_threaded, bucket_single);
16559    }
16560
16561    #[test]
16562    fn libsais64_partial_sorting_scan_left_to_right_8u_block_prepare_records_cache_and_counts() {
16563        let t = vec![2_u8, 1, 3, 1, 0];
16564        let sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
16565        let k = 4;
16566        let mut buckets = vec![0; 4 * k];
16567        let mut cache = vec![ThreadCache::default(); 8];
16568        let mut state = ThreadState::new();
16569        let (position, count) = partial_sorting_scan_left_to_right_8u_block_prepare(
16570            &t,
16571            &sa,
16572            k as SaSint,
16573            &mut buckets,
16574            &mut cache,
16575            0,
16576            2,
16577        );
16578        state.position = position;
16579        state.count = count;
16580        assert!(state.count >= 1);
16581        assert!(cache
16582            .iter()
16583            .take(state.count as usize)
16584            .any(|entry| entry.symbol >= 0));
16585    }
16586
16587    #[test]
16588    fn libsais64_partial_sorting_scan_left_to_right_8u_block_place_writes_induced_values() {
16589        let mut sa = vec![0; 8];
16590        let mut buckets = vec![0; 8];
16591        buckets[0] = 0;
16592        buckets[1] = 1;
16593        let cache = vec![
16594            ThreadCache {
16595                index: 3 | SAINT_MIN,
16596                symbol: 0,
16597            },
16598            ThreadCache {
16599                index: 5,
16600                symbol: 1,
16601            },
16602        ];
16603        partial_sorting_scan_left_to_right_8u_block_place(&mut sa, &mut buckets, 2, &cache, 2, 0);
16604        assert!(sa[0] != 0 || sa[1] != 0);
16605    }
16606
16607    #[test]
16608    fn libsais64_partial_sorting_scan_left_to_right_8u_block_omp_wraps_sequential_version() {
16609        let t = vec![2_u8, 1, 3, 1, 0];
16610        let mut sa = vec![2 | SAINT_MIN, 4, 0, 0, 0, 0];
16611        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16612        let mut thread_state = alloc_thread_state(2).unwrap();
16613        let d = partial_sorting_scan_left_to_right_8u_block_omp(
16614            &t,
16615            &mut sa,
16616            4,
16617            &mut buckets,
16618            0,
16619            0,
16620            2,
16621            2,
16622            &mut thread_state,
16623        );
16624        assert!(d >= 0);
16625    }
16626
16627    #[test]
16628    fn libsais64_partial_sorting_shift_buckets_32s_6k_moves_temp_bucket_view_into_main_slots() {
16629        let k = 3usize;
16630        let mut buckets = vec![0; 6 * k];
16631        buckets[4 * k] = 10;
16632        buckets[4 * k + 1] = 11;
16633        buckets[4 * k + 2] = 12;
16634        buckets[4 * k + 3] = 13;
16635        partial_sorting_shift_buckets_32s_6k(k as SaSint, &mut buckets);
16636        assert_eq!(buckets[0], 10);
16637        assert_eq!(buckets[1], 11);
16638        assert_eq!(buckets[4], 12);
16639        assert_eq!(buckets[5], 13);
16640    }
16641
16642    #[test]
16643    fn libsais64_partial_sorting_scan_right_to_left_8u_emits_induced_suffixes() {
16644        let t = vec![0_u8, 1, 2, 1, 0];
16645        let mut sa = vec![0, 0, 4 | SAINT_MIN];
16646        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
16647        buckets[buckets_index2(1, 1)] = 2;
16648
16649        let d = partial_sorting_scan_right_to_left_8u(&t, &mut sa, &mut buckets, 0, 2, 1);
16650
16651        assert_eq!(d, 1);
16652        assert_eq!(sa[1], 3 | SAINT_MIN);
16653        assert_eq!(buckets[buckets_index2(1, 1)], 1);
16654        assert_eq!(buckets[2 * ALPHABET_SIZE + buckets_index2(1, 1)], 1);
16655    }
16656
16657    #[test]
16658    fn libsais64_partial_gsa_scan_right_to_left_8u_skips_separator_bucket() {
16659        let t = vec![1_u8, 0, 0];
16660        let mut sa = vec![0, 2 | SAINT_MIN];
16661        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
16662        buckets[buckets_index2(0, 1)] = 2;
16663
16664        let d = partial_gsa_scan_right_to_left_8u(&t, &mut sa, &mut buckets, 0, 1, 1);
16665
16666        assert_eq!(d, 1);
16667        assert_eq!(sa, vec![0, 2 | SAINT_MIN]);
16668        assert_eq!(buckets[buckets_index2(0, 1)], 2);
16669    }
16670
16671    #[test]
16672    fn libsais64_partial_sorting_scan_right_to_left_32s_6k_emits_induced_suffixes() {
16673        let t = vec![0, 1, 2, 1, 0];
16674        let mut sa = vec![0, 0, 4 | SAINT_MIN];
16675        let mut buckets = vec![0; 4 * 3];
16676        buckets[buckets_index4(1, 1)] = 2;
16677
16678        let d = partial_sorting_scan_right_to_left_32s_6k(&t, &mut sa, &mut buckets, 0, 2, 1);
16679
16680        assert_eq!(d, 1);
16681        assert_eq!(sa[1], 3 | SAINT_MIN);
16682        assert_eq!(buckets[buckets_index4(1, 1)], 1);
16683        assert_eq!(buckets[buckets_index4(1, 1) + 2], 1);
16684    }
16685
16686    #[test]
16687    fn libsais64_partial_sorting_scan_right_to_left_32s_1k_omp_wraps_sequential_version() {
16688        let t = vec![0, 1, 2, 1, 0];
16689        let mut sa = vec![0, 0, 4];
16690        let mut buckets = vec![0; 3];
16691        buckets[1] = 2;
16692        let mut thread_state = alloc_thread_state(2).unwrap();
16693
16694        partial_sorting_scan_right_to_left_32s_1k_omp(
16695            &t,
16696            &mut sa,
16697            3,
16698            &mut buckets,
16699            2,
16700            &mut thread_state,
16701        );
16702
16703        assert_eq!(sa[1], 3 | SAINT_MIN);
16704        assert_eq!(buckets[1], 1);
16705    }
16706
16707    #[test]
16708    fn libsais64_partial_sorting_scan_right_to_left_32s_6k_block_gather_records_symbols() {
16709        let t = vec![0, 1, 2, 1, 0];
16710        let sa = vec![0, 4 | SAINT_MIN, 0];
16711        let mut cache = vec![ThreadCache::default(); sa.len()];
16712
16713        partial_sorting_scan_right_to_left_32s_6k_block_gather(&t, &sa, &mut cache, 1, 1);
16714
16715        assert_eq!(cache[0].index, 4 | SAINT_MIN);
16716        assert_eq!(cache[0].symbol, buckets_index4(1, 1) as SaSint);
16717    }
16718
16719    #[test]
16720    fn libsais64_partial_sorting_scan_right_to_left_32s_4k_block_gather_zeroes_positive_entries() {
16721        let t = vec![0, 1, 2, 1, 0];
16722        let mut sa = vec![0, 4 | SUFFIX_GROUP_MARKER, 0];
16723        let mut cache = vec![ThreadCache::default(); sa.len()];
16724
16725        partial_sorting_scan_right_to_left_32s_4k_block_gather(&t, &mut sa, &mut cache, 1, 1);
16726
16727        assert_eq!(sa[1], 0);
16728        assert_eq!(cache[0].index, 4 | SUFFIX_GROUP_MARKER);
16729        assert_eq!(cache[0].symbol, buckets_index2(1, 1) as SaSint);
16730    }
16731
16732    #[test]
16733    fn libsais64_partial_sorting_scan_right_to_left_32s_1k_block_gather_stores_preinduced_entries()
16734    {
16735        let t = vec![0, 1, 2, 1, 0];
16736        let mut sa = vec![0, 4, 0];
16737        let mut cache = vec![ThreadCache::default(); sa.len()];
16738
16739        partial_sorting_scan_right_to_left_32s_1k_block_gather(&t, &mut sa, &mut cache, 1, 1);
16740
16741        assert_eq!(sa[1], 0);
16742        assert_eq!(cache[0].index, 3 | SAINT_MIN);
16743        assert_eq!(cache[0].symbol, 1);
16744    }
16745
16746    #[test]
16747    fn libsais64_partial_sorting_scan_right_to_left_32s_6k_block_sort_updates_bucket_and_marker_state(
16748    ) {
16749        let t = vec![0, 1, 2, 1, 0];
16750        let mut cache = vec![ThreadCache::default(); 3];
16751        cache[0].index = 4 | SAINT_MIN;
16752        cache[0].symbol = buckets_index4(1, 1) as SaSint;
16753        let mut buckets = vec![0; 4 * 3];
16754        buckets[buckets_index4(1, 1)] = 2;
16755
16756        let d = partial_sorting_scan_right_to_left_32s_6k_block_sort(
16757            &t,
16758            &mut buckets,
16759            0,
16760            &mut cache,
16761            1,
16762            1,
16763        );
16764
16765        assert_eq!(d, 1);
16766        assert_eq!(cache[0].index, 3 | SAINT_MIN);
16767        assert_eq!(buckets[buckets_index4(1, 1)], 1);
16768        assert_eq!(buckets[buckets_index4(1, 1) + 2], 1);
16769    }
16770
16771    #[test]
16772    fn libsais64_partial_sorting_scan_right_to_left_32s_1k_block_omp_places_cached_suffixes() {
16773        let t = vec![0, 1, 2, 1, 0];
16774        let mut sa = vec![0, 4, 0];
16775        let mut buckets = vec![0; 3];
16776        buckets[1] = 2;
16777        let mut cache = vec![ThreadCache::default(); sa.len()];
16778
16779        partial_sorting_scan_right_to_left_32s_1k_block_omp(
16780            &t,
16781            &mut sa,
16782            &mut buckets,
16783            &mut cache,
16784            1,
16785            1,
16786            2,
16787        );
16788
16789        assert_eq!(sa[1], 3 | SAINT_MIN);
16790        assert_eq!(buckets[1], 1);
16791    }
16792
16793    #[test]
16794    fn libsais64_partial_sorting_scan_right_to_left_32s_1k_block_omp_uses_relative_cache() {
16795        let block_start = 20_000usize;
16796        let block_size = 16_384usize;
16797        let n = block_start + block_size + 8;
16798        let t = vec![1; n];
16799        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
16800
16801        let mut sa_single = vec![0; n];
16802        sa_single[block_start..block_start + block_size].copy_from_slice(&suffixes);
16803        let mut sa_threaded = sa_single.clone();
16804        let mut bucket_single = vec![0, block_size as SaSint];
16805        let mut bucket_threaded = bucket_single.clone();
16806        let mut cache = vec![ThreadCache::default(); 4 * LIBSAIS_PER_THREAD_CACHE_SIZE];
16807
16808        partial_sorting_scan_right_to_left_32s_1k(
16809            &t,
16810            &mut sa_single,
16811            &mut bucket_single,
16812            block_start as FastSint,
16813            block_size as FastSint,
16814        );
16815        partial_sorting_scan_right_to_left_32s_1k_block_omp(
16816            &t,
16817            &mut sa_threaded,
16818            &mut bucket_threaded,
16819            &mut cache,
16820            block_start as FastSint,
16821            block_size as FastSint,
16822            4,
16823        );
16824
16825        assert_eq!(sa_threaded, sa_single);
16826        assert_eq!(bucket_threaded, bucket_single);
16827    }
16828
16829    #[test]
16830    fn libsais64_partial_sorting_gather_lms_suffixes_32s_4k_compacts_negative_marked_entries() {
16831        let mut sa = vec![1 | SUFFIX_GROUP_MARKER, -3, 5 | SUFFIX_GROUP_MARKER, -7];
16832        let n = sa.len() as FastSint;
16833
16834        let l = partial_sorting_gather_lms_suffixes_32s_4k(&mut sa, 0, n);
16835
16836        assert_eq!(l, 2);
16837        assert_eq!(sa[0], (SAINT_MIN | SUFFIX_GROUP_MARKER) - 3);
16838        assert_eq!(sa[1], (SAINT_MIN | SUFFIX_GROUP_MARKER) - 7);
16839    }
16840
16841    #[test]
16842    fn libsais64_partial_sorting_gather_lms_suffixes_32s_1k_compacts_negative_marked_entries() {
16843        let mut sa = vec![1, -3, 5, -7];
16844        let n = sa.len() as FastSint;
16845
16846        let l = partial_sorting_gather_lms_suffixes_32s_1k(&mut sa, 0, n);
16847
16848        assert_eq!(l, 2);
16849        assert_eq!(sa[0], SAINT_MAX - 2);
16850        assert_eq!(sa[1], SAINT_MAX - 6);
16851    }
16852
16853    #[test]
16854    fn libsais64_partial_sorting_gather_lms_suffixes_32s_4k_omp_wraps_sequential_version() {
16855        let mut sa = vec![1 | SUFFIX_GROUP_MARKER, -3, 5 | SUFFIX_GROUP_MARKER, -7];
16856        let mut thread_state = alloc_thread_state(2).unwrap();
16857
16858        partial_sorting_gather_lms_suffixes_32s_4k_omp(&mut sa, 4, 2, &mut thread_state);
16859
16860        assert_eq!(sa[0], (SAINT_MIN | SUFFIX_GROUP_MARKER) - 3);
16861        assert_eq!(sa[1], (SAINT_MIN | SUFFIX_GROUP_MARKER) - 7);
16862    }
16863
16864    #[test]
16865    fn libsais64_partial_sorting_gather_lms_suffixes_32s_1k_omp_wraps_sequential_version() {
16866        let mut sa = vec![1, -3, 5, -7];
16867        let mut thread_state = alloc_thread_state(2).unwrap();
16868
16869        partial_sorting_gather_lms_suffixes_32s_1k_omp(&mut sa, 4, 2, &mut thread_state);
16870
16871        assert_eq!(sa[0], SAINT_MAX - 2);
16872        assert_eq!(sa[1], SAINT_MAX - 6);
16873    }
16874
16875    #[test]
16876    fn libsais64_partial_sorting_gather_lms_suffixes_32s_omp_uses_block_partition() {
16877        let n = 65_600usize;
16878        let input_4k: Vec<SaSint> = (0..n)
16879            .map(|i| {
16880                let value = (i as SaSint) | SUFFIX_GROUP_MARKER;
16881                if i % 5 == 0 {
16882                    value | SAINT_MIN
16883                } else {
16884                    value
16885                }
16886            })
16887            .collect();
16888        let count_4k = input_4k.iter().filter(|&&value| value < 0).count();
16889
16890        let mut single = input_4k.clone();
16891        let mut threaded = input_4k;
16892        let mut thread_state = alloc_thread_state(4).unwrap();
16893        partial_sorting_gather_lms_suffixes_32s_4k_omp(&mut single, n as SaSint, 1, &mut []);
16894        partial_sorting_gather_lms_suffixes_32s_4k_omp(
16895            &mut threaded,
16896            n as SaSint,
16897            4,
16898            &mut thread_state,
16899        );
16900        assert_eq!(&threaded[..count_4k], &single[..count_4k]);
16901
16902        let input_1k: Vec<SaSint> = (0..n)
16903            .map(|i| {
16904                let value = i as SaSint;
16905                if i % 7 == 0 {
16906                    value | SAINT_MIN
16907                } else {
16908                    value
16909                }
16910            })
16911            .collect();
16912        let count_1k = input_1k.iter().filter(|&&value| value < 0).count();
16913
16914        let mut single = input_1k.clone();
16915        let mut threaded = input_1k;
16916        partial_sorting_gather_lms_suffixes_32s_1k_omp(&mut single, n as SaSint, 1, &mut []);
16917        partial_sorting_gather_lms_suffixes_32s_1k_omp(
16918            &mut threaded,
16919            n as SaSint,
16920            4,
16921            &mut thread_state,
16922        );
16923        assert_eq!(&threaded[..count_1k], &single[..count_1k]);
16924    }
16925
16926    #[test]
16927    fn libsais64_partial_sorting_shift_markers_8u_omp_toggles_segment_markers() {
16928        let mut sa = vec![1 | SAINT_MIN, 2 | SAINT_MIN, 3, 4 | SAINT_MIN, 5];
16929        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
16930        buckets[4 * ALPHABET_SIZE + buckets_index2(1, 0)] = 5;
16931        buckets[buckets_index2(0, 0)] = 0;
16932        let len = sa.len() as SaSint;
16933        partial_sorting_shift_markers_8u_omp(&mut sa, len, &buckets, 1);
16934        assert!(sa.iter().any(|&v| (v & SAINT_MIN) == 0));
16935    }
16936
16937    #[test]
16938    fn libsais64_partial_sorting_shift_markers_32s_6k_omp_toggles_segment_markers() {
16939        let mut sa = vec![1 | SAINT_MIN, 2 | SAINT_MIN, 3, 4 | SAINT_MIN, 5];
16940        let k = 3usize;
16941        let mut buckets = vec![0; 6 * k];
16942        buckets[buckets_index4(1, 0)] = 5;
16943        buckets[4 * k + buckets_index2(0, 0)] = 0;
16944        partial_sorting_shift_markers_32s_6k_omp(&mut sa, k as SaSint, &buckets, 1);
16945        assert!(sa.iter().any(|&v| (v & SAINT_MIN) == 0));
16946    }
16947
16948    #[test]
16949    fn libsais64_partial_sorting_shift_markers_32s_4k_toggles_group_markers() {
16950        let mut sa = vec![
16951            1 | SUFFIX_GROUP_MARKER,
16952            2 | SUFFIX_GROUP_MARKER,
16953            3,
16954            4 | SUFFIX_GROUP_MARKER,
16955        ];
16956        let len = sa.len() as SaSint;
16957        partial_sorting_shift_markers_32s_4k(&mut sa, len);
16958        assert!(sa.iter().any(|&v| (v & SUFFIX_GROUP_MARKER) == 0));
16959    }
16960
16961    #[test]
16962    fn libsais64_clear_lms_suffixes_omp_zeroes_requested_bucket_ranges() {
16963        let mut sa = vec![5, 4, 3, 2, 1, 9];
16964        let n = sa.len() as SaSint;
16965        let bucket_start = vec![1, 4, 5];
16966        let bucket_end = vec![3, 5, 5];
16967
16968        clear_lms_suffixes_omp(&mut sa, n, 3, &bucket_start, &bucket_end, 2);
16969
16970        assert_eq!(sa, vec![5, 0, 0, 2, 0, 9]);
16971    }
16972
16973    #[test]
16974    fn libsais64_final_bwt_scan_left_to_right_8u_rewrites_sa_and_induces_suffixes() {
16975        let t = vec![0_u8, 1, 2, 1, 0];
16976        let mut sa = vec![1, 0, 0];
16977        let mut induction_bucket = vec![0, 1, 3];
16978
16979        final_bwt_scan_left_to_right_8u(&t, &mut sa, &mut induction_bucket, 0, 1);
16980
16981        assert_eq!(sa[0], 0);
16982        assert_eq!(induction_bucket[0], 1);
16983    }
16984
16985    #[test]
16986    fn libsais64_final_bwt_aux_scan_left_to_right_8u_updates_sampling_array() {
16987        let t = vec![0_u8, 1, 2, 1, 0];
16988        let mut sa = vec![1, 0, 0];
16989        let mut induction_bucket = vec![0, 1, 3];
16990        let mut i_out = vec![0; 2];
16991
16992        final_bwt_aux_scan_left_to_right_8u(
16993            &t,
16994            &mut sa,
16995            0,
16996            &mut i_out,
16997            &mut induction_bucket,
16998            0,
16999            1,
17000        );
17001
17002        assert_eq!(i_out[0], 1);
17003    }
17004
17005    #[test]
17006    fn libsais64_final_sorting_scan_left_to_right_8u_clears_marker_and_places_suffix() {
17007        let t = vec![0_u8, 1, 2, 1, 0];
17008        let mut sa = vec![1, 0, 0];
17009        let mut induction_bucket = vec![0, 1, 3];
17010
17011        final_sorting_scan_left_to_right_8u(&t, &mut sa, &mut induction_bucket, 0, 1);
17012
17013        assert_eq!(sa[0], 0);
17014        assert_eq!(induction_bucket[0], 1);
17015    }
17016
17017    #[test]
17018    fn libsais64_final_sorting_scan_left_to_right_32s_clears_marker_and_places_suffix() {
17019        let t = vec![0, 1, 2, 1, 0];
17020        let mut sa = vec![1, 0, 0];
17021        let mut induction_bucket = vec![0, 1, 3];
17022
17023        final_sorting_scan_left_to_right_32s(&t, &mut sa, &mut induction_bucket, 0, 1);
17024
17025        assert_eq!(sa[0], 0);
17026        assert_eq!(induction_bucket[0], 1);
17027    }
17028
17029    #[test]
17030    fn libsais64_final_bwt_scan_left_to_right_8u_block_prepare_records_cache_and_counts() {
17031        let t = vec![0_u8, 1, 2, 1, 0];
17032        let mut sa = vec![1, 2, 0];
17033        let mut buckets = vec![99; ALPHABET_SIZE];
17034        let mut cache = vec![ThreadCache::default(); 4];
17035
17036        let count = final_bwt_scan_left_to_right_8u_block_prepare(
17037            &t,
17038            &mut sa,
17039            ALPHABET_SIZE as SaSint,
17040            &mut buckets,
17041            &mut cache,
17042            0,
17043            2,
17044        );
17045
17046        assert_eq!(count, 2);
17047        assert_eq!(sa[0] & SAINT_MAX, 0);
17048        assert_eq!(sa[1], 1 | SAINT_MIN);
17049        assert_eq!(buckets[0], 1);
17050        assert_eq!(buckets[1], 1);
17051        assert_eq!(cache[0].symbol, 0);
17052        assert_eq!(cache[0].index & SAINT_MAX, 0);
17053        assert_eq!(cache[1].symbol, 1);
17054        assert_eq!(cache[1].index & SAINT_MAX, 1);
17055    }
17056
17057    #[test]
17058    fn libsais64_final_sorting_scan_left_to_right_8u_omp_wraps_sequential_behavior() {
17059        let t = vec![0_u8, 1, 2, 1, 0];
17060        let mut sa = vec![0; t.len()];
17061        let mut induction_bucket = vec![0, 1, 3];
17062        let mut expected_sa = sa.clone();
17063        let mut expected_bucket = induction_bucket.clone();
17064
17065        final_sorting_scan_left_to_right_8u_omp(
17066            &t,
17067            &mut expected_sa,
17068            t.len() as FastSint,
17069            ALPHABET_SIZE as SaSint,
17070            &mut expected_bucket,
17071            1,
17072            &mut [],
17073        );
17074
17075        let mut thread_state = alloc_thread_state(2).unwrap();
17076
17077        final_sorting_scan_left_to_right_8u_omp(
17078            &t,
17079            &mut sa,
17080            t.len() as FastSint,
17081            ALPHABET_SIZE as SaSint,
17082            &mut induction_bucket,
17083            2,
17084            &mut thread_state,
17085        );
17086
17087        assert_eq!(sa, expected_sa);
17088        assert_eq!(induction_bucket, expected_bucket);
17089    }
17090
17091    #[test]
17092    fn libsais64_final_bwt_scan_right_to_left_8u_returns_zero_index_and_induces_suffixes() {
17093        let t = vec![0_u8, 1, 2, 1, 0];
17094        let mut sa = vec![0, 2, 0];
17095        let mut induction_bucket = vec![1, 2, 3];
17096
17097        let index = final_bwt_scan_right_to_left_8u(&t, &mut sa, &mut induction_bucket, 0, 2);
17098
17099        assert_eq!(index, 0);
17100        assert_eq!(sa[1], 1);
17101        assert_eq!(induction_bucket[1], 1);
17102    }
17103
17104    #[test]
17105    fn libsais64_final_sorting_scan_right_to_left_8u_omp_matches_sequential_path() {
17106        let t = vec![0_u8, 1, 2, 1, 0];
17107        let mut sa = vec![0, 2, 0, 0];
17108        let mut induction_bucket = vec![1, 2, 3];
17109        let mut expected_sa = sa.clone();
17110        let mut expected_bucket = induction_bucket.clone();
17111
17112        final_sorting_scan_right_to_left_8u_omp(
17113            &t,
17114            &mut expected_sa,
17115            0,
17116            2,
17117            ALPHABET_SIZE as SaSint,
17118            &mut expected_bucket,
17119            1,
17120            &mut [],
17121        );
17122
17123        let mut thread_state = alloc_thread_state(2).unwrap();
17124        final_sorting_scan_right_to_left_8u_omp(
17125            &t,
17126            &mut sa,
17127            0,
17128            2,
17129            ALPHABET_SIZE as SaSint,
17130            &mut induction_bucket,
17131            2,
17132            &mut thread_state,
17133        );
17134
17135        assert_eq!(sa, expected_sa);
17136        assert_eq!(induction_bucket, expected_bucket);
17137    }
17138
17139    #[test]
17140    fn libsais64_induce_final_order_8u_omp_non_bwt_matches_direct_final_scans() {
17141        let t = vec![0_u8, 1, 2, 1, 0];
17142        let mut sa = vec![0, 2, 0, 0, 0];
17143        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
17144        buckets[6 * ALPHABET_SIZE..6 * ALPHABET_SIZE + 3].copy_from_slice(&[0, 1, 3]);
17145        buckets[7 * ALPHABET_SIZE..7 * ALPHABET_SIZE + 3].copy_from_slice(&[2, 4, 5]);
17146
17147        let mut expected_sa = sa.clone();
17148        let mut expected_left = vec![0, 1, 3];
17149        let mut expected_right = vec![2, 4, 5];
17150        final_sorting_scan_left_to_right_8u_omp(
17151            &t,
17152            &mut expected_sa,
17153            t.len() as FastSint,
17154            ALPHABET_SIZE as SaSint,
17155            &mut expected_left,
17156            1,
17157            &mut [],
17158        );
17159        final_sorting_scan_right_to_left_8u_omp(
17160            &t,
17161            &mut expected_sa,
17162            0,
17163            t.len() as FastSint,
17164            ALPHABET_SIZE as SaSint,
17165            &mut expected_right,
17166            1,
17167            &mut [],
17168        );
17169
17170        let mut thread_state = alloc_thread_state(2).unwrap();
17171        let result = induce_final_order_8u_omp(
17172            &t,
17173            &mut sa,
17174            t.len() as SaSint,
17175            ALPHABET_SIZE as SaSint,
17176            LIBSAIS_FLAGS_NONE,
17177            0,
17178            None,
17179            &mut buckets,
17180            2,
17181            &mut thread_state,
17182        );
17183
17184        assert_eq!(result, 0);
17185        assert_eq!(sa, expected_sa);
17186        assert_eq!(
17187            &buckets[6 * ALPHABET_SIZE..6 * ALPHABET_SIZE + 3],
17188            expected_left.as_slice()
17189        );
17190        assert_eq!(
17191            &buckets[7 * ALPHABET_SIZE..7 * ALPHABET_SIZE + 3],
17192            expected_right.as_slice()
17193        );
17194    }
17195
17196    #[test]
17197    fn libsais64_count_helpers_match_c_predicates() {
17198        let sa = [1, -1, 0, -3, 4, 0, -9];
17199        assert_eq!(
17200            count_negative_marked_suffixes(&sa, 0, sa.len() as FastSint),
17201            3
17202        );
17203        assert_eq!(count_zero_marked_suffixes(&sa, 0, sa.len() as FastSint), 2);
17204        assert_eq!(count_negative_marked_suffixes(&sa, 2, 3), 1);
17205        assert_eq!(count_zero_marked_suffixes(&sa, 2, 3), 1);
17206    }
17207
17208    #[test]
17209    fn libsais64_flip_suffix_markers_omp_toggles_saint_min_bits() {
17210        let mut sa = vec![1, -2, 3, -4];
17211        flip_suffix_markers_omp(&mut sa, 4, 1);
17212        assert_eq!(
17213            sa,
17214            vec![1 ^ SAINT_MIN, -2 ^ SAINT_MIN, 3 ^ SAINT_MIN, -4 ^ SAINT_MIN]
17215        );
17216    }
17217
17218    #[test]
17219    fn libsais64_place_cached_suffixes_writes_indices_to_symbol_slots() {
17220        let mut sa = vec![0; 8];
17221        let cache = vec![
17222            ThreadCache {
17223                symbol: 2,
17224                index: 10,
17225            },
17226            ThreadCache {
17227                symbol: 5,
17228                index: 20,
17229            },
17230            ThreadCache {
17231                symbol: 1,
17232                index: 30,
17233            },
17234        ];
17235
17236        place_cached_suffixes(&mut sa, &cache, 0, cache.len() as FastSint);
17237
17238        assert_eq!(sa[2], 10);
17239        assert_eq!(sa[5], 20);
17240        assert_eq!(sa[1], 30);
17241    }
17242
17243    #[test]
17244    fn libsais64_compact_and_place_cached_suffixes_discards_negative_symbols() {
17245        let mut sa = vec![0; 8];
17246        let mut cache = vec![
17247            ThreadCache {
17248                symbol: 2,
17249                index: 10,
17250            },
17251            ThreadCache {
17252                symbol: -1,
17253                index: 99,
17254            },
17255            ThreadCache {
17256                symbol: 5,
17257                index: 20,
17258            },
17259            ThreadCache {
17260                symbol: -4,
17261                index: 77,
17262            },
17263            ThreadCache {
17264                symbol: 1,
17265                index: 30,
17266            },
17267        ];
17268        let cache_len = cache.len() as FastSint;
17269
17270        compact_and_place_cached_suffixes(&mut sa, &mut cache, 0, cache_len);
17271
17272        assert_eq!(sa[2], 10);
17273        assert_eq!(sa[5], 20);
17274        assert_eq!(sa[1], 30);
17275        assert_eq!(
17276            cache[0],
17277            ThreadCache {
17278                symbol: 2,
17279                index: 10
17280            }
17281        );
17282        assert_eq!(
17283            cache[1],
17284            ThreadCache {
17285                symbol: 5,
17286                index: 20
17287            }
17288        );
17289        assert_eq!(
17290            cache[2],
17291            ThreadCache {
17292                symbol: 1,
17293                index: 30
17294            }
17295        );
17296    }
17297
17298    #[test]
17299    fn libsais64_gather_lms_suffixes_32s_collects_expected_suffix_starts() {
17300        let t = vec![2, 1, 3, 1, 0];
17301        let mut sa = vec![0; t.len()];
17302        let m = gather_lms_suffixes_32s(&t, &mut sa, t.len() as SaSint);
17303        assert!(m >= 0);
17304        assert!(sa
17305            .iter()
17306            .all(|&value| value >= 0 && value <= t.len() as SaSint));
17307        assert!(sa[t.len() - 1] >= 1 && sa[t.len() - 1] <= t.len() as SaSint - 1);
17308    }
17309
17310    #[test]
17311    fn libsais64_gather_compacted_lms_suffixes_32s_skips_negative_marked_symbols() {
17312        let t = vec![2, -1, 3, 1, 0];
17313        let mut sa = vec![0; t.len()];
17314        let m = gather_compacted_lms_suffixes_32s(&t, &mut sa, t.len() as SaSint);
17315        assert!(m >= 0);
17316        assert!(sa
17317            .iter()
17318            .all(|&value| value >= 0 && value <= t.len() as SaSint));
17319    }
17320
17321    #[test]
17322    fn libsais64_count_lms_suffixes_32s_2k_counts_two_bucket_categories() {
17323        let t = vec![2, 1, 3, 1, 0];
17324        let mut buckets = vec![0; 2 * 4];
17325        count_lms_suffixes_32s_2k(&t, t.len() as SaSint, 4, &mut buckets);
17326        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17327    }
17328
17329    #[test]
17330    fn libsais64_count_lms_suffixes_32s_4k_counts_four_bucket_categories() {
17331        let t = vec![2, 1, 3, 1, 0];
17332        let mut buckets = vec![0; 4 * 4];
17333        count_lms_suffixes_32s_4k(&t, t.len() as SaSint, 4, &mut buckets);
17334        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17335    }
17336
17337    #[test]
17338    fn libsais64_count_compacted_lms_suffixes_32s_2k_masks_saint_bits() {
17339        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
17340        let mut buckets = vec![0; 2 * 4];
17341        count_compacted_lms_suffixes_32s_2k(&t, t.len() as SaSint, 4, &mut buckets);
17342        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17343    }
17344
17345    #[test]
17346    fn libsais64_count_and_gather_lms_suffixes_8u_updates_sa_and_buckets() {
17347        let t = vec![2_u8, 1, 3, 1, 0];
17348        let mut sa = vec![0; t.len()];
17349        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
17350        let m = count_and_gather_lms_suffixes_8u(
17351            &t,
17352            &mut sa,
17353            t.len() as SaSint,
17354            &mut buckets,
17355            0,
17356            t.len() as FastSint,
17357        );
17358        assert_eq!(m, 1);
17359        assert_eq!(sa[t.len() - 1], 1);
17360        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17361    }
17362
17363    #[test]
17364    fn libsais64_count_and_gather_lms_suffixes_8u_omp_preserves_sequential_wrapper_behavior() {
17365        let t = vec![2_u8, 1, 3, 1, 0];
17366        let mut sa = vec![0; t.len()];
17367        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
17368        let mut thread_state = alloc_thread_state(2).unwrap();
17369        let m = count_and_gather_lms_suffixes_8u_omp(
17370            &t,
17371            &mut sa,
17372            t.len() as SaSint,
17373            &mut buckets,
17374            2,
17375            &mut thread_state,
17376        );
17377        assert_eq!(m, 1);
17378        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
17379    }
17380
17381    #[test]
17382    fn libsais64_get_bucket_stride_prefers_aligned_sizes_when_space_allows() {
17383        assert_eq!(get_bucket_stride(8192, 1000, 2), 1024);
17384        assert_eq!(get_bucket_stride(256, 17, 2), 32);
17385        assert_eq!(get_bucket_stride(8, 17, 2), 17);
17386    }
17387
17388    #[test]
17389    fn libsais64_count_suffixes_32s_counts_symbol_histogram() {
17390        let t = vec![2, 1, 2, 3, 1, 0, 2];
17391        let mut buckets = vec![0; 4];
17392        count_suffixes_32s(&t, t.len() as SaSint, 4, &mut buckets);
17393        assert_eq!(buckets, vec![1, 2, 3, 1]);
17394    }
17395
17396    #[test]
17397    fn libsais64_initialize_buckets_start_and_end_8u_sets_ranges_and_freq() {
17398        let mut buckets = vec![0; 8 * ALPHABET_SIZE];
17399        buckets[buckets_index4(0, 0)] = 1;
17400        buckets[buckets_index4(1, 1)] = 2;
17401        buckets[buckets_index4(2, 3)] = 3;
17402        let mut freq = vec![0; ALPHABET_SIZE];
17403        let k = initialize_buckets_start_and_end_8u(&mut buckets, Some(&mut freq));
17404        assert_eq!(k, 3);
17405        assert_eq!(freq[0], 1);
17406        assert_eq!(freq[1], 2);
17407        assert_eq!(freq[2], 3);
17408        assert_eq!(buckets[6 * ALPHABET_SIZE], 0);
17409        assert_eq!(buckets[7 * ALPHABET_SIZE], 1);
17410        assert_eq!(buckets[6 * ALPHABET_SIZE + 1], 1);
17411        assert_eq!(buckets[7 * ALPHABET_SIZE + 1], 3);
17412    }
17413
17414    #[test]
17415    fn libsais64_initialize_buckets_start_and_end_32s_6k_sets_prefix_ranges() {
17416        let k = 3;
17417        let mut buckets = vec![0; 6 * k];
17418        buckets[buckets_index4(0, 0)] = 1;
17419        buckets[buckets_index4(0, 1)] = 2;
17420        buckets[buckets_index4(1, 2)] = 3;
17421        buckets[buckets_index4(2, 3)] = 4;
17422        initialize_buckets_start_and_end_32s_6k(k as SaSint, &mut buckets);
17423        assert_eq!(&buckets[4 * k..5 * k], &[0, 3, 6]);
17424        assert_eq!(&buckets[5 * k..6 * k], &[3, 6, 10]);
17425    }
17426
17427    #[test]
17428    fn libsais64_initialize_buckets_start_and_end_32s_4k_sets_prefix_ranges() {
17429        let k = 3;
17430        let mut buckets = vec![0; 4 * k];
17431        buckets[buckets_index2(0, 0)] = 1;
17432        buckets[buckets_index2(0, 1)] = 2;
17433        buckets[buckets_index2(1, 0)] = 3;
17434        buckets[buckets_index2(2, 1)] = 4;
17435        initialize_buckets_start_and_end_32s_4k(k as SaSint, &mut buckets);
17436        assert_eq!(&buckets[2 * k..3 * k], &[0, 3, 6]);
17437        assert_eq!(&buckets[3 * k..4 * k], &[3, 6, 10]);
17438    }
17439
17440    #[test]
17441    fn libsais64_initialize_buckets_end_32s_2k_rewrites_first_lanes_to_end_positions() {
17442        let k = 3;
17443        let mut buckets = vec![1, 2, 3, 4, 5, 6];
17444        initialize_buckets_end_32s_2k(k as SaSint, &mut buckets);
17445        assert_eq!(buckets[0], 3);
17446        assert_eq!(buckets[2], 10);
17447        assert_eq!(buckets[4], 21);
17448    }
17449
17450    #[test]
17451    fn libsais64_initialize_buckets_start_and_end_32s_2k_copies_start_positions() {
17452        let k = 3;
17453        let mut buckets = vec![3, 2, 10, 4, 21, 6];
17454        initialize_buckets_start_and_end_32s_2k(k as SaSint, &mut buckets);
17455        assert_eq!(&buckets[..k], &[3, 10, 21]);
17456        assert_eq!(&buckets[k..2 * k], &[0, 3, 10]);
17457    }
17458
17459    #[test]
17460    fn libsais64_initialize_buckets_start_32s_1k_builds_prefix_starts() {
17461        let mut buckets = vec![1, 2, 3];
17462        initialize_buckets_start_32s_1k(3, &mut buckets);
17463        assert_eq!(buckets, vec![0, 1, 3]);
17464    }
17465
17466    #[test]
17467    fn libsais64_initialize_buckets_end_32s_1k_builds_prefix_ends() {
17468        let mut buckets = vec![1, 2, 3];
17469        initialize_buckets_end_32s_1k(3, &mut buckets);
17470        assert_eq!(buckets, vec![1, 3, 6]);
17471    }
17472
17473    #[test]
17474    fn libsais64_initialize_buckets_for_lms_suffixes_radix_sort_8u_returns_total_lms_slots() {
17475        let t = vec![2_u8, 1, 3, 1, 0];
17476        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17477        buckets[buckets_index4(0, 1)] = 1;
17478        buckets[buckets_index4(1, 3)] = 2;
17479        let sum = initialize_buckets_for_lms_suffixes_radix_sort_8u(&t, &mut buckets, 4);
17480        assert!(sum >= 0);
17481    }
17482
17483    #[test]
17484    fn libsais64_initialize_buckets_for_lms_suffixes_radix_sort_32s_2k_rewrites_two_lane_prefixes()
17485    {
17486        let t = vec![2, 1, 3, 1, 0];
17487        let mut buckets = vec![0; 2 * 4];
17488        initialize_buckets_for_lms_suffixes_radix_sort_32s_2k(&t, 4, &mut buckets, 4);
17489        assert!(buckets.iter().any(|&v| v != 0));
17490    }
17491
17492    #[test]
17493    fn libsais64_initialize_buckets_for_lms_suffixes_radix_sort_32s_6k_returns_total_lms_slots() {
17494        let t = vec![2, 1, 3, 1, 0];
17495        let mut buckets = vec![0; 6 * 4];
17496        buckets[buckets_index4(0, 1)] = 1;
17497        buckets[buckets_index4(1, 3)] = 2;
17498        let sum = initialize_buckets_for_lms_suffixes_radix_sort_32s_6k(&t, 4, &mut buckets, 4);
17499        assert!(sum >= 0);
17500    }
17501
17502    #[test]
17503    fn libsais64_initialize_buckets_for_radix_and_partial_sorting_32s_4k_sets_start_end_views() {
17504        let t = vec![2, 1, 3, 1, 0];
17505        let k = 4usize;
17506        let mut buckets = vec![0; 4 * k];
17507        buckets[buckets_index2(0, 0)] = 1;
17508        buckets[buckets_index2(0, 1)] = 2;
17509        buckets[buckets_index2(1, 0)] = 3;
17510        initialize_buckets_for_radix_and_partial_sorting_32s_4k(&t, k as SaSint, &mut buckets, 4);
17511        assert_eq!(buckets[2 * k], 0);
17512        assert!(buckets[3 * k] >= buckets[2 * k]);
17513    }
17514
17515    #[test]
17516    fn libsais64_initialize_buckets_for_partial_sorting_8u_sets_start_and_distinct_views() {
17517        let t = vec![2_u8, 1, 3, 1, 0];
17518        let mut buckets = vec![0; 6 * ALPHABET_SIZE];
17519        buckets[buckets_index4(0, 0)] = 1;
17520        buckets[buckets_index4(0, 2)] = 2;
17521        initialize_buckets_for_partial_sorting_8u(&t, &mut buckets, 4, 3);
17522        assert!(buckets[0] >= 4);
17523        assert!(buckets[1] >= 0);
17524        assert!(buckets[4 * ALPHABET_SIZE] >= 4);
17525    }
17526
17527    #[test]
17528    fn libsais64_initialize_buckets_for_partial_sorting_32s_6k_rewrites_bucket_views() {
17529        let t = vec![2, 1, 3, 1, 0];
17530        let k = 4usize;
17531        let mut buckets = vec![0; 6 * k];
17532        buckets[buckets_index4(0, 0)] = 1;
17533        buckets[buckets_index4(0, 1)] = 2;
17534        buckets[buckets_index4(1, 2)] = 3;
17535        initialize_buckets_for_partial_sorting_32s_6k(&t, k as SaSint, &mut buckets, 4, 3);
17536        assert!(buckets[0] >= 4);
17537        assert!(buckets[4 * k] >= 4);
17538    }
17539
17540    #[test]
17541    fn libsais64_place_lms_suffixes_interval_32s_4k_moves_suffixes_into_bucket_intervals() {
17542        let mut sa = vec![10, 11, 12, 13, 14];
17543        let k = 3usize;
17544        let mut buckets = vec![0; 4 * k];
17545        buckets[buckets_index2(0, 1)] = 0;
17546        buckets[buckets_index2(1, 1)] = 2;
17547        buckets[buckets_index2(2, 1)] = 3;
17548        buckets[3 * k] = 2;
17549        buckets[3 * k + 1] = 5;
17550
17551        place_lms_suffixes_interval_32s_4k(&mut sa, 5, k as SaSint, 5, &buckets);
17552
17553        assert_eq!(sa, vec![0, 0, 0, 0, 14]);
17554    }
17555
17556    #[test]
17557    fn libsais64_place_lms_suffixes_interval_32s_2k_moves_suffixes_into_bucket_intervals() {
17558        let mut sa = vec![10, 11, 12, 13, 14];
17559        let mut buckets = vec![0; 2 * 3];
17560        buckets[buckets_index2(0, 0)] = 2;
17561        buckets[buckets_index2(0, 1)] = 0;
17562        buckets[buckets_index2(1, 0)] = 5;
17563        buckets[buckets_index2(1, 1)] = 2;
17564        buckets[buckets_index2(2, 0)] = 5;
17565        buckets[buckets_index2(2, 1)] = 3;
17566
17567        place_lms_suffixes_interval_32s_2k(&mut sa, 5, 3, 5, &buckets);
17568
17569        assert_eq!(sa, vec![0, 0, 0, 0, 14]);
17570    }
17571
17572    #[test]
17573    fn libsais64_place_lms_suffixes_interval_32s_1k_places_suffixes_by_symbol_bucket() {
17574        let t = vec![0, 1, 1, 2, 2];
17575        let mut sa = vec![1, 2, 3, 4, 99];
17576        let buckets = vec![0, 2, 5];
17577
17578        place_lms_suffixes_interval_32s_1k(&t, &mut sa, 3, 4, &buckets);
17579
17580        assert_eq!(sa, vec![1, 2, 0, 3, 4]);
17581    }
17582
17583    #[test]
17584    fn libsais64_accumulate_counts_helpers_match_prefix_bucket_addition() {
17585        let mut bucket00 = vec![4, 5, 6];
17586        let bucket01 = vec![1, 2, 3];
17587        let bucket02 = vec![7, 8, 9];
17588        let bucket03 = vec![10, 11, 12];
17589        let bucket04 = vec![13, 14, 15];
17590        let bucket05 = vec![16, 17, 18];
17591        let bucket06 = vec![19, 20, 21];
17592        let bucket07 = vec![22, 23, 24];
17593        let bucket08 = vec![25, 26, 27];
17594
17595        accumulate_counts_s32_2(&mut bucket00, &bucket01);
17596        assert_eq!(bucket00, vec![5, 7, 9]);
17597
17598        accumulate_counts_s32_3(&mut bucket00, &bucket01, &bucket02);
17599        assert_eq!(bucket00, vec![13, 17, 21]);
17600
17601        accumulate_counts_s32_4(&mut bucket00, &bucket01, &bucket02, &bucket03);
17602        assert_eq!(bucket00, vec![31, 38, 45]);
17603
17604        accumulate_counts_s32_5(&mut bucket00, &bucket01, &bucket02, &bucket03, &bucket04);
17605        assert_eq!(bucket00, vec![62, 73, 84]);
17606
17607        accumulate_counts_s32_6(
17608            &mut bucket00,
17609            &bucket01,
17610            &bucket02,
17611            &bucket03,
17612            &bucket04,
17613            &bucket05,
17614        );
17615        assert_eq!(bucket00, vec![109, 125, 141]);
17616
17617        accumulate_counts_s32_7(
17618            &mut bucket00,
17619            &bucket01,
17620            &bucket02,
17621            &bucket03,
17622            &bucket04,
17623            &bucket05,
17624            &bucket06,
17625        );
17626        assert_eq!(bucket00, vec![175, 197, 219]);
17627
17628        accumulate_counts_s32_8(
17629            &mut bucket00,
17630            &bucket01,
17631            &bucket02,
17632            &bucket03,
17633            &bucket04,
17634            &bucket05,
17635            &bucket06,
17636            &bucket07,
17637        );
17638        assert_eq!(bucket00, vec![263, 292, 321]);
17639
17640        accumulate_counts_s32_9(
17641            &mut bucket00,
17642            &bucket01,
17643            &bucket02,
17644            &bucket03,
17645            &bucket04,
17646            &bucket05,
17647            &bucket06,
17648            &bucket07,
17649            &bucket08,
17650        );
17651        assert_eq!(bucket00, vec![376, 413, 450]);
17652    }
17653
17654    #[test]
17655    fn libsais64_accumulate_counts_s32_matches_dispatch_for_small_bucket_counts() {
17656        let mut buckets = vec![1, 2, 3, 4, 5, 6, 7, 8];
17657        accumulate_counts_s32(&mut buckets, 2, 2, 4);
17658        assert_eq!(buckets, vec![1, 2, 3, 4, 5, 6, 16, 20]);
17659    }
17660
17661    #[test]
17662    fn libsais64_accumulate_counts_s32_matches_dispatch_for_nine_buckets() {
17663        let mut buckets = vec![
17664            1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80, 9, 90,
17665        ];
17666        accumulate_counts_s32(&mut buckets, 2, 2, 9);
17667        assert_eq!(
17668            buckets,
17669            vec![1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80, 45, 450]
17670        );
17671    }
17672
17673    #[test]
17674    fn libsais64_accumulate_counts_s32_matches_chunked_nine_then_tail_behavior() {
17675        let mut buckets = (1..=11).collect::<Vec<SaSint>>();
17676        accumulate_counts_s32(&mut buckets, 1, 1, 11);
17677        assert_eq!(buckets, vec![1, 2, 3, 4, 5, 6, 7, 8, 45, 10, 66]);
17678    }
17679
17680    #[test]
17681    fn libsais64_final_sorting_scan_left_to_right_32s_block_omp_places_cached_suffixes() {
17682        let t = vec![0, 1, 2, 1, 0];
17683        let mut sa = vec![1, 2, 0, 0];
17684        let mut induction_bucket = vec![0, 1, 3];
17685        let mut cache = vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE];
17686
17687        final_sorting_scan_left_to_right_32s_block_omp(
17688            &t,
17689            &mut sa,
17690            &mut induction_bucket,
17691            &mut cache,
17692            0,
17693            2,
17694            2,
17695        );
17696
17697        assert_eq!(sa[0] & SAINT_MAX, 0);
17698        assert_eq!(sa[1] & SAINT_MAX, 1);
17699        assert_eq!(induction_bucket[0], 1);
17700        assert_eq!(induction_bucket[1], 2);
17701    }
17702
17703    #[test]
17704    fn libsais64_final_sorting_scan_left_to_right_8u_block_omp_uses_thread_buckets() {
17705        let block_start = 20_000usize;
17706        let block_size = 16_384usize;
17707        let n = block_start + block_size + 8;
17708        let t = vec![1_u8; n];
17709        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
17710
17711        let mut expected_sa = vec![0; n];
17712        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
17713        let mut threaded_sa = expected_sa.clone();
17714        let mut expected_bucket = vec![0; ALPHABET_SIZE];
17715        let mut threaded_bucket = expected_bucket.clone();
17716        let mut thread_state = alloc_thread_state(4).unwrap();
17717
17718        final_sorting_scan_left_to_right_8u(
17719            &t,
17720            &mut expected_sa,
17721            &mut expected_bucket,
17722            block_start as FastSint,
17723            block_size as FastSint,
17724        );
17725        final_sorting_scan_left_to_right_8u_block_omp(
17726            &t,
17727            &mut threaded_sa,
17728            ALPHABET_SIZE as SaSint,
17729            &mut threaded_bucket,
17730            block_start as FastSint,
17731            block_size as FastSint,
17732            4,
17733            &mut thread_state,
17734        );
17735
17736        assert_eq!(threaded_sa, expected_sa);
17737        assert_eq!(threaded_bucket, expected_bucket);
17738    }
17739
17740    #[test]
17741    fn libsais64_final_sorting_scan_right_to_left_32s_block_omp_runs_block_pipeline() {
17742        let t = vec![0, 1, 2, 1, 0];
17743        let mut sa = vec![0, 2, 0, 0];
17744        let mut induction_bucket = vec![1, 2, 3];
17745        let mut expected_sa = sa.clone();
17746        let mut expected_bucket = induction_bucket.clone();
17747        let mut cache = vec![ThreadCache::default(); LIBSAIS_PER_THREAD_CACHE_SIZE];
17748
17749        final_sorting_scan_right_to_left_32s(&t, &mut expected_sa, &mut expected_bucket, 0, 2);
17750        final_sorting_scan_right_to_left_32s_block_omp(
17751            &t,
17752            &mut sa,
17753            &mut induction_bucket,
17754            &mut cache,
17755            0,
17756            2,
17757            2,
17758        );
17759
17760        assert_eq!(sa, expected_sa);
17761        assert_eq!(induction_bucket, expected_bucket);
17762    }
17763
17764    #[test]
17765    fn libsais64_bwt_copy_8u_copies_low_bytes_from_suffix_array_storage() {
17766        let a = vec![65, 255, 256, -1];
17767        let mut u = vec![0_u8; 4];
17768
17769        bwt_copy_8u(&mut u, &a, 4);
17770
17771        assert_eq!(u, vec![65, 255, 0, 255]);
17772    }
17773
17774    #[test]
17775    fn libsais64_bwt_copy_8u_omp_matches_sequential_copy() {
17776        let a = vec![1, 2, 3, 4, 5];
17777        let mut u = vec![0_u8; 5];
17778
17779        bwt_copy_8u_omp(&mut u, &a, 5, 4);
17780
17781        assert_eq!(u, vec![1, 2, 3, 4, 5]);
17782    }
17783
17784    #[test]
17785    fn libsais64_conversion_helpers_use_little_endian_word_layout() {
17786        let s = vec![11_u32, 22, 33, 44];
17787        let mut d = vec![0_u64; 4];
17788        convert_32u_to_64u(&s, &mut d, 1, 2);
17789        assert_eq!(d, vec![0, 22, 33, 0]);
17790
17791        let mut words = vec![5_u32, 6, 7, 8, 0, 0, 0, 0];
17792        convert_inplace_32u_to_64u(&mut words, 0, 4);
17793        assert_eq!(words, vec![5, 0, 6, 0, 7, 0, 8, 0]);
17794        convert_inplace_64u_to_32u(&mut words, 0, 4);
17795        assert_eq!(&words[..4], &[5, 6, 7, 8]);
17796
17797        let mut words = vec![9_u32, 10, 11, 12, 0, 0, 0, 0];
17798        convert_inplace_32u_to_64u_omp(&mut words, 4, 2);
17799        assert_eq!(words, vec![9, 0, 10, 0, 11, 0, 12, 0]);
17800    }
17801
17802    #[test]
17803    fn libsais64_32bit_workspace_sizing_matches_upstream_capacity_rules() {
17804        assert_eq!(libsais64_new_32bit_fs(10, 4), Some(18));
17805        assert_eq!(libsais64_new_32bit_fs(i32::MAX as usize - 4, 100), Some(4));
17806        assert_eq!(libsais64_new_32bit_fs(i32::MAX as usize + 1, 0), None);
17807    }
17808
17809    #[test]
17810    fn libsais64_32bit_suffix_adapter_widens_suffix_array_and_frequency() {
17811        let text = b"banana";
17812        let fs = 4;
17813        let new_fs = libsais64_new_32bit_fs(text.len(), fs).expect("small workspace");
17814        let mut sa64 = vec![-1; text.len() + fs as usize];
17815        let mut freq64 = vec![-1; ALPHABET_SIZE];
17816        let rc64 = libsais64_run_32bit_omp(text, &mut sa64, fs, Some(&mut freq64), 2, false)
17817            .expect("small input uses 32-bit adapter");
17818
17819        let mut sa32 = vec![-1; text.len() + new_fs as usize];
17820        let mut freq32 = vec![-1; ALPHABET_SIZE];
17821        let rc32 = crate::libsais_omp(text, &mut sa32, new_fs, Some(&mut freq32), 2);
17822
17823        assert_eq!(rc64, SaSint::from(rc32));
17824        assert_eq!(
17825            &sa64[..text.len()],
17826            &sa32[..text.len()]
17827                .iter()
17828                .map(|&value| SaSint::from(value as u32))
17829                .collect::<Vec<_>>()
17830        );
17831        assert_eq!(freq64[b'a' as usize], 3);
17832        assert_eq!(freq64[b'b' as usize], 1);
17833        assert_eq!(freq64[b'n' as usize], 2);
17834        assert_eq!(
17835            freq64[..ALPHABET_SIZE],
17836            freq32
17837                .iter()
17838                .map(|&value| SaSint::from(value))
17839                .collect::<Vec<_>>()
17840        );
17841    }
17842
17843    #[test]
17844    fn libsais64_32bit_gsa_adapter_widens_suffix_array_and_frequency() {
17845        let text = b"ban\0ana\0";
17846        let fs = 2;
17847        let mut sa64 = vec![-1; text.len() + fs as usize];
17848        let mut freq64 = vec![-1; ALPHABET_SIZE];
17849
17850        let rc = libsais64_run_32bit_omp(text, &mut sa64, fs, Some(&mut freq64), 2, true)
17851            .expect("small GSA input uses 32-bit adapter");
17852
17853        let mut direct_sa = vec![0; text.len()];
17854        let mut direct_freq = vec![0; ALPHABET_SIZE];
17855        assert_eq!(
17856            crate::libsais_gsa(text, &mut direct_sa, 0, Some(&mut direct_freq)),
17857            0
17858        );
17859        assert_eq!(rc, 0);
17860        assert_eq!(
17861            sa64[..text.len()],
17862            direct_sa
17863                .iter()
17864                .map(|&value| SaSint::from(value as u32))
17865                .collect::<Vec<_>>()
17866        );
17867        assert_eq!(
17868            freq64[..ALPHABET_SIZE],
17869            direct_freq
17870                .iter()
17871                .map(|&value| SaSint::from(value))
17872                .collect::<Vec<_>>()
17873        );
17874    }
17875
17876    #[test]
17877    fn libsais64_32bit_bwt_adapters_widen_frequency_and_aux_samples() {
17878        let text = b"mississippi";
17879        let fs = 6;
17880        let r = 4;
17881
17882        let mut bwt64 = vec![0; text.len()];
17883        let mut freq64 = vec![-1; ALPHABET_SIZE];
17884        let primary64 = libsais64_bwt_run_32bit_omp(text, &mut bwt64, fs, Some(&mut freq64), 2)
17885            .expect("small input uses 32-bit BWT adapter");
17886
17887        let mut bwt32 = vec![0; text.len()];
17888        let mut work32 = vec![0; text.len() + fs as usize * 2 + text.len()];
17889        let mut freq32 = vec![-1; ALPHABET_SIZE];
17890        let primary32 =
17891            crate::libsais_bwt_omp(text, &mut bwt32, &mut work32, 23, Some(&mut freq32), 2);
17892
17893        assert_eq!(primary64, SaSint::from(primary32));
17894        assert_eq!(bwt64, bwt32);
17895        assert_eq!(
17896            freq64[..ALPHABET_SIZE],
17897            freq32
17898                .iter()
17899                .map(|&value| SaSint::from(value))
17900                .collect::<Vec<_>>()
17901        );
17902
17903        let mut aux_bwt64 = vec![0; text.len()];
17904        let mut aux64 = vec![-1; (text.len() - 1) / r as usize + 1];
17905        let mut aux_freq64 = vec![-1; ALPHABET_SIZE];
17906        let rc64 = libsais64_bwt_aux_run_32bit_omp(
17907            text,
17908            &mut aux_bwt64,
17909            fs,
17910            Some(&mut aux_freq64),
17911            r,
17912            &mut aux64,
17913            2,
17914        )
17915        .expect("small input uses 32-bit aux BWT adapter");
17916
17917        let mut aux_bwt32 = vec![0; text.len()];
17918        let mut aux_work32 = vec![0; text.len() + fs as usize * 2 + text.len()];
17919        let mut aux32 = vec![-1; aux64.len()];
17920        let mut aux_freq32 = vec![-1; ALPHABET_SIZE];
17921        let rc32 = crate::libsais_bwt_aux_omp(
17922            text,
17923            &mut aux_bwt32,
17924            &mut aux_work32,
17925            23,
17926            Some(&mut aux_freq32),
17927            r as i32,
17928            &mut aux32,
17929            2,
17930        );
17931
17932        assert_eq!(rc64, SaSint::from(rc32));
17933        assert_eq!(aux_bwt64, aux_bwt32);
17934        assert_eq!(
17935            aux64,
17936            aux32
17937                .iter()
17938                .map(|&value| SaSint::from(value))
17939                .collect::<Vec<_>>()
17940        );
17941        assert_eq!(
17942            aux_freq64[..ALPHABET_SIZE],
17943            aux_freq32
17944                .iter()
17945                .map(|&value| SaSint::from(value))
17946                .collect::<Vec<_>>()
17947        );
17948    }
17949
17950    #[test]
17951    fn libsais64_bwt_copy_8u_omp_uses_block_partition_for_large_inputs() {
17952        let n = 65_600usize;
17953        let a: Vec<SaSint> = (0..n).map(|i| (i * 17) as SaSint).collect();
17954        let mut threaded = vec![0; n];
17955        let mut sequential = vec![0; n];
17956
17957        bwt_copy_8u_omp(&mut threaded, &a, n as SaSint, 4);
17958        bwt_copy_8u(&mut sequential, &a, n as SaSint);
17959
17960        assert_eq!(threaded, sequential);
17961    }
17962
17963    #[test]
17964    fn libsais64_flip_suffix_markers_omp_uses_block_partition_for_large_inputs() {
17965        let n = 65_600usize;
17966        let mut single: Vec<SaSint> = (0..n).map(|i| (i as SaSint) ^ SAINT_MIN).collect();
17967        let mut threaded = single.clone();
17968
17969        flip_suffix_markers_omp(&mut single, n as SaSint, 1);
17970        flip_suffix_markers_omp(&mut threaded, n as SaSint, 4);
17971
17972        assert_eq!(threaded, single);
17973    }
17974
17975    #[test]
17976    fn libsais64_renumber_lms_suffixes_8u_writes_names_into_second_half() {
17977        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
17978
17979        let name = renumber_lms_suffixes_8u(&mut sa, 2, 0, 0, 2);
17980
17981        assert_eq!(name, 1);
17982        assert_eq!(sa[2], SAINT_MIN);
17983        assert_eq!(sa[3], SAINT_MIN | 1);
17984    }
17985
17986    #[test]
17987    fn libsais64_renumber_lms_suffixes_8u_matches_upstream_c_helper() {
17988        let mut sa_rust = vec![1 | SAINT_MIN, 3, 0, 0];
17989        let mut sa_c = sa_rust.clone();
17990
17991        let rust_name = renumber_lms_suffixes_8u(&mut sa_rust, 2, 0, 0, 2);
17992        let c_name =
17993            unsafe { probe_libsais64_renumber_lms_suffixes_8u(sa_c.as_mut_ptr(), 2, 0, 0, 2) };
17994
17995        assert_eq!(rust_name, c_name);
17996        assert_eq!(sa_rust, sa_c);
17997    }
17998
17999    #[test]
18000    fn libsais64_gather_marked_lms_suffixes_moves_negative_marked_entries_to_tail() {
18001        let mut sa = vec![0, 0, 1 | SAINT_MIN, 3];
18002
18003        let l = gather_marked_lms_suffixes(&mut sa, 2, 4, 0, 2);
18004
18005        assert_eq!(l, 3);
18006        assert_eq!(sa[3], 1);
18007    }
18008
18009    #[test]
18010    fn libsais64_gather_marked_lms_suffixes_matches_upstream_c_helper() {
18011        let mut sa_rust = vec![0, 0, 1 | SAINT_MIN, 3];
18012        let mut sa_c = sa_rust.clone();
18013
18014        let rust_l = gather_marked_lms_suffixes(&mut sa_rust, 2, 4, 0, 2);
18015        let c_l =
18016            unsafe { probe_libsais64_gather_marked_lms_suffixes(sa_c.as_mut_ptr(), 2, 4, 0, 2) };
18017
18018        assert_eq!(rust_l, c_l);
18019        assert_eq!(sa_rust, sa_c);
18020    }
18021
18022    #[test]
18023    fn libsais64_renumber_lms_suffixes_8u_omp_wraps_sequential_version() {
18024        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
18025        let mut thread_state = alloc_thread_state(2).unwrap();
18026
18027        let name = renumber_lms_suffixes_8u_omp(&mut sa, 2, 2, &mut thread_state);
18028
18029        assert_eq!(name, 1);
18030        assert_eq!(sa[2], SAINT_MIN);
18031    }
18032
18033    #[test]
18034    fn libsais64_renumber_lms_suffixes_8u_omp_uses_block_partition_for_large_inputs() {
18035        let m = 65_600usize;
18036        let mut input = vec![0; 2 * m];
18037        for (i, slot) in input[..m].iter_mut().enumerate() {
18038            let suffix = (2 * i + 1) as SaSint;
18039            *slot = if i % 5 == 0 {
18040                suffix | SAINT_MIN
18041            } else {
18042                suffix
18043            };
18044        }
18045
18046        let mut single = input.clone();
18047        let mut threaded = input;
18048        let mut thread_state = alloc_thread_state(4).unwrap();
18049        let single_name = renumber_lms_suffixes_8u(&mut single, m as SaSint, 0, 0, m as FastSint);
18050        let threaded_name =
18051            renumber_lms_suffixes_8u_omp(&mut threaded, m as SaSint, 4, &mut thread_state);
18052
18053        assert_eq!(threaded_name, single_name);
18054        assert_eq!(threaded, single);
18055    }
18056
18057    #[test]
18058    fn libsais64_gather_marked_lms_suffixes_omp_uses_block_partition_for_large_inputs() {
18059        let n = 131_200usize;
18060        let half_n = n >> 1;
18061        let mut input = vec![-77; n];
18062        for (i, slot) in input[..half_n].iter_mut().enumerate() {
18063            let suffix = (3 * i + 1) as SaSint;
18064            *slot = if i % 7 == 0 {
18065                suffix | SAINT_MIN
18066            } else {
18067                suffix
18068            };
18069        }
18070        let marked_count = input[..half_n].iter().filter(|&&value| value < 0).count();
18071
18072        let mut single = input.clone();
18073        let mut threaded = input;
18074        let mut thread_state = alloc_thread_state(4).unwrap();
18075        let _ = gather_marked_lms_suffixes(&mut single, 0, n as FastSint, 0, half_n as FastSint);
18076        gather_marked_lms_suffixes_omp(&mut threaded, n as SaSint, 0, 0, 4, &mut thread_state);
18077
18078        assert_eq!(&threaded[n - marked_count..], &single[n - marked_count..]);
18079    }
18080
18081    #[test]
18082    fn libsais64_renumber_and_gather_lms_suffixes_omp_uses_large_input_paths() {
18083        let m = 65_600usize;
18084        let n = 2 * m;
18085        let mut input = vec![0; n];
18086        for (i, slot) in input[..m].iter_mut().enumerate() {
18087            let suffix = (2 * i + 1) as SaSint;
18088            *slot = if i % 5 == 0 {
18089                suffix | SAINT_MIN
18090            } else {
18091                suffix
18092            };
18093        }
18094
18095        let mut single = input.clone();
18096        let mut threaded = input;
18097        let mut single_state = alloc_thread_state(1).unwrap();
18098        let mut threaded_state = alloc_thread_state(4).unwrap();
18099        let single_name = renumber_and_gather_lms_suffixes_omp(
18100            &mut single,
18101            n as SaSint,
18102            m as SaSint,
18103            0,
18104            1,
18105            &mut single_state,
18106        );
18107        let threaded_name = renumber_and_gather_lms_suffixes_omp(
18108            &mut threaded,
18109            n as SaSint,
18110            m as SaSint,
18111            0,
18112            4,
18113            &mut threaded_state,
18114        );
18115
18116        assert_eq!(threaded_name, single_name);
18117        assert_eq!(threaded, single);
18118    }
18119
18120    #[test]
18121    fn libsais64_renumber_and_gather_lms_suffixes_omp_gathers_when_names_are_not_distinct() {
18122        let mut sa = vec![1 | SAINT_MIN, 3, 0, 0];
18123        let mut thread_state = alloc_thread_state(2).unwrap();
18124
18125        let name = renumber_and_gather_lms_suffixes_omp(&mut sa, 4, 2, 0, 2, &mut thread_state);
18126
18127        assert_eq!(name, 1);
18128        assert_eq!(sa[3], 1);
18129    }
18130
18131    #[test]
18132    fn libsais64_renumber_and_gather_lms_suffixes_omp_matches_upstream_c_helper() {
18133        let mut sa_rust = vec![1 | SAINT_MIN, 3, 0, 0];
18134        let mut sa_c = sa_rust.clone();
18135        let mut thread_state = alloc_thread_state(2).unwrap();
18136
18137        let rust_name =
18138            renumber_and_gather_lms_suffixes_omp(&mut sa_rust, 4, 2, 0, 2, &mut thread_state);
18139        let c_name = unsafe {
18140            probe_libsais64_renumber_and_gather_lms_suffixes_omp(sa_c.as_mut_ptr(), 4, 2, 0, 2)
18141        };
18142
18143        assert_eq!(rust_name, c_name);
18144        assert_eq!(sa_rust, sa_c);
18145    }
18146
18147    #[test]
18148    fn libsais64_renumber_distinct_lms_suffixes_32s_4k_masks_sources_and_writes_second_half() {
18149        let mut sa = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18150
18151        let name = renumber_distinct_lms_suffixes_32s_4k(&mut sa, 2, 1, 0, 2);
18152
18153        assert_eq!(name, 3);
18154        assert_eq!(sa[0], 1);
18155        assert_eq!(sa[1], 3);
18156        assert_eq!(sa[2], 1);
18157        assert_eq!(sa[3], 2 | SAINT_MIN);
18158    }
18159
18160    #[test]
18161    fn libsais64_renumber_distinct_lms_suffixes_32s_4k_matches_upstream_c_helper() {
18162        let mut sa_rust = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18163        let mut sa_c = sa_rust.clone();
18164
18165        let rust_name = renumber_distinct_lms_suffixes_32s_4k(&mut sa_rust, 2, 1, 0, 2);
18166        let c_name = unsafe {
18167            probe_libsais64_renumber_distinct_lms_suffixes_32s_4k(sa_c.as_mut_ptr(), 2, 1, 0, 2)
18168        };
18169
18170        assert_eq!(rust_name, c_name);
18171        assert_eq!(sa_rust, sa_c);
18172    }
18173
18174    #[test]
18175    fn libsais64_mark_distinct_lms_suffixes_32s_propagates_previous_nonzero_marker() {
18176        let mut sa = vec![0, 0, SAINT_MIN | 5, 0, SAINT_MIN | 7];
18177
18178        mark_distinct_lms_suffixes_32s(&mut sa, 2, 0, 3);
18179
18180        assert_eq!(sa[2], 5);
18181        assert_eq!(sa[3], 0);
18182        assert_eq!(sa[4], SAINT_MIN | 7);
18183    }
18184
18185    #[test]
18186    fn libsais64_clamp_lms_suffixes_length_32s_keeps_only_negative_lengths() {
18187        let mut sa = vec![0, 0, SAINT_MIN | 5, 7, SAINT_MIN | 3];
18188
18189        clamp_lms_suffixes_length_32s(&mut sa, 2, 0, 3);
18190
18191        assert_eq!(sa[2], 5);
18192        assert_eq!(sa[3], 0);
18193        assert_eq!(sa[4], 3);
18194    }
18195
18196    #[test]
18197    fn libsais64_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp_marks_second_half_when_names_repeat(
18198    ) {
18199        let mut sa = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18200        let mut thread_state = alloc_thread_state(2).unwrap();
18201
18202        let name =
18203            renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(&mut sa, 4, 2, 2, &mut thread_state);
18204
18205        assert_eq!(name, 2);
18206        assert_eq!(sa[2], 1);
18207        assert_eq!(sa[3], SAINT_MIN | 2);
18208    }
18209
18210    #[test]
18211    fn libsais64_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp_matches_upstream_c_helper() {
18212        let mut sa_rust = vec![1 | SAINT_MIN, 3 | SAINT_MIN, 0, 0];
18213        let mut sa_c = sa_rust.clone();
18214        let mut thread_state = alloc_thread_state(2).unwrap();
18215
18216        let rust_name = renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
18217            &mut sa_rust,
18218            4,
18219            2,
18220            2,
18221            &mut thread_state,
18222        );
18223        let c_name = unsafe {
18224            probe_libsais64_renumber_and_mark_distinct_lms_suffixes_32s_4k_omp(
18225                sa_c.as_mut_ptr(),
18226                4,
18227                2,
18228                2,
18229            )
18230        };
18231
18232        assert_eq!(rust_name, c_name);
18233        assert_eq!(sa_rust, sa_c);
18234    }
18235
18236    #[test]
18237    fn libsais64_renumber_and_mark_distinct_lms_suffixes_32s_1k_omp_handles_single_lms_suffix() {
18238        let t = vec![2, 1, 0];
18239        let mut sa = vec![0; t.len()];
18240
18241        let name = renumber_and_mark_distinct_lms_suffixes_32s_1k_omp(&t, &mut sa, 3, 1, 1);
18242
18243        assert_eq!(name, 1);
18244        assert_eq!(sa[1], SAINT_MIN | 1);
18245    }
18246
18247    #[test]
18248    fn libsais64_main_32s_entry_matches_public_c_long_on_6k_branch() {
18249        assert_libsais64_main_32s_entry_matches_public_c_long_for_branch(300);
18250    }
18251
18252    #[test]
18253    fn libsais64_main_32s_entry_matches_public_c_long_on_4k_branch() {
18254        assert_libsais64_main_32s_entry_matches_public_c_long_for_branch(400);
18255    }
18256
18257    #[test]
18258    fn libsais64_main_32s_entry_matches_public_c_long_on_2k_branch() {
18259        assert_libsais64_main_32s_entry_matches_public_c_long_for_branch(700);
18260    }
18261
18262    #[test]
18263    fn libsais64_main_32s_entry_matches_public_c_long_on_1k_branch() {
18264        assert_libsais64_main_32s_entry_matches_public_c_long_for_branch(1501);
18265    }
18266
18267    #[test]
18268    fn libsais64_main_32s_entry_matches_public_c_long_on_recursive_repetitive_6k_case() {
18269        assert_libsais64_main_32s_entry_matches_public_c_long(
18270            make_libsais64_recursive_main_32s_text(24),
18271            300,
18272            0,
18273            true,
18274        );
18275    }
18276
18277    #[test]
18278    fn libsais64_main_32s_entry_matches_public_c_long_on_recursive_repetitive_1k_case() {
18279        assert_libsais64_main_32s_entry_matches_public_c_long(
18280            make_libsais64_recursive_main_32s_text(24),
18281            1501,
18282            0,
18283            true,
18284        );
18285    }
18286
18287    #[test]
18288    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_6k_case() {
18289        assert_libsais64_main_32s_entry_matches_public_c_long(
18290            make_libsais64_large_main_32s_stress_text(1024, 300),
18291            300,
18292            0,
18293            true,
18294        );
18295    }
18296
18297    #[test]
18298    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_6k_case_with_fs() {
18299        assert_libsais64_main_32s_entry_matches_public_c_long(
18300            make_libsais64_large_main_32s_stress_text(1024, 300),
18301            300,
18302            2048,
18303            false,
18304        );
18305    }
18306
18307    #[test]
18308    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_4k_case() {
18309        assert_libsais64_main_32s_entry_matches_public_c_long(
18310            make_libsais64_large_main_32s_stress_text(1024, 400),
18311            400,
18312            0,
18313            true,
18314        );
18315    }
18316
18317    #[test]
18318    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_4k_case_with_fs() {
18319        assert_libsais64_main_32s_entry_matches_public_c_long(
18320            make_libsais64_large_main_32s_stress_text(1024, 400),
18321            400,
18322            2048,
18323            false,
18324        );
18325    }
18326
18327    #[test]
18328    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_2k_case() {
18329        assert_libsais64_main_32s_entry_matches_public_c_long(
18330            make_libsais64_large_main_32s_stress_text(1024, 700),
18331            700,
18332            0,
18333            true,
18334        );
18335    }
18336
18337    #[test]
18338    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_2k_case_with_fs() {
18339        assert_libsais64_main_32s_entry_matches_public_c_long(
18340            make_libsais64_large_main_32s_stress_text(1024, 700),
18341            700,
18342            2048,
18343            false,
18344        );
18345    }
18346
18347    #[test]
18348    fn libsais64_main_32s_entry_matches_public_c_long_on_large_generated_1k_case_with_fs() {
18349        assert_libsais64_main_32s_entry_matches_public_c_long(
18350            make_libsais64_large_main_32s_stress_text(1024, 1501),
18351            1501,
18352            2048,
18353            false,
18354        );
18355    }
18356
18357    #[test]
18358    fn libsais64_reconstruct_lms_suffixes_maps_indices_from_tail_interval() {
18359        let mut sa = vec![0, 1, 2, 7, 11, 13];
18360
18361        reconstruct_lms_suffixes(&mut sa, 6, 3, 0, 3);
18362
18363        assert_eq!(&sa[..3], &[7, 11, 13]);
18364    }
18365
18366    #[test]
18367    fn libsais64_reconstruct_lms_suffixes_omp_wraps_sequential_version() {
18368        let mut sa = vec![0, 1, 2, 7, 11, 13];
18369
18370        reconstruct_lms_suffixes_omp(&mut sa, 6, 3, 2);
18371
18372        assert_eq!(&sa[..3], &[7, 11, 13]);
18373    }
18374
18375    #[test]
18376    fn libsais64_lms_late_omp_wrappers_use_block_partitions_for_large_inputs() {
18377        let m = 65_600usize;
18378        let n = 2 * m;
18379        let mut input = vec![0; n];
18380        for (i, slot) in input[..m].iter_mut().enumerate() {
18381            let suffix = (2 * i + 1) as SaSint;
18382            *slot = if i % 5 == 0 {
18383                suffix | SAINT_MIN
18384            } else {
18385                suffix
18386            };
18387        }
18388
18389        let mut single = input.clone();
18390        let mut threaded = input.clone();
18391        let mut thread_state = alloc_thread_state(4).unwrap();
18392        let single_name = renumber_lms_suffixes_8u(&mut single, m as SaSint, 0, 0, m as FastSint);
18393        let threaded_name =
18394            renumber_lms_suffixes_8u_omp(&mut threaded, m as SaSint, 4, &mut thread_state);
18395        assert_eq!(threaded_name, single_name);
18396        assert_eq!(threaded, single);
18397
18398        let mut single = input.clone();
18399        let mut threaded = input.clone();
18400        let mut single_state = alloc_thread_state(1).unwrap();
18401        let mut threaded_state = alloc_thread_state(4).unwrap();
18402        let single_name = renumber_and_gather_lms_suffixes_omp(
18403            &mut single,
18404            n as SaSint,
18405            m as SaSint,
18406            0,
18407            1,
18408            &mut single_state,
18409        );
18410        let threaded_name = renumber_and_gather_lms_suffixes_omp(
18411            &mut threaded,
18412            n as SaSint,
18413            m as SaSint,
18414            0,
18415            4,
18416            &mut threaded_state,
18417        );
18418        assert_eq!(threaded_name, single_name);
18419        assert_eq!(threaded, single);
18420
18421        let mut single = input.clone();
18422        let mut threaded = input;
18423        let marked_count = single[..m].iter().filter(|&&value| value < 0).count();
18424        let _ = gather_marked_lms_suffixes(&mut single, 0, n as FastSint, 0, m as FastSint);
18425        gather_marked_lms_suffixes_omp(&mut threaded, n as SaSint, 0, 0, 4, &mut thread_state);
18426        assert_eq!(&threaded[n - marked_count..], &single[n - marked_count..]);
18427    }
18428
18429    #[test]
18430    fn libsais64_reconstruct_lms_suffixes_omp_uses_block_partition_for_large_inputs() {
18431        let m = 65_600usize;
18432        let n = 2 * m;
18433        let mut input = vec![0; n];
18434        for (i, slot) in input[..m].iter_mut().enumerate() {
18435            *slot = (m - 1 - i) as SaSint;
18436        }
18437        for (i, slot) in input[m..].iter_mut().enumerate() {
18438            *slot = (i * 17 + 3) as SaSint;
18439        }
18440
18441        let mut single = input.clone();
18442        let mut threaded = input;
18443        reconstruct_lms_suffixes(&mut single, n as SaSint, m as SaSint, 0, m as FastSint);
18444        reconstruct_lms_suffixes_omp(&mut threaded, n as SaSint, m as SaSint, 4);
18445
18446        assert_eq!(threaded, single);
18447    }
18448
18449    #[test]
18450    fn libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_marks_new_unique_names() {
18451        let mut t = vec![0, 0, 0, 0];
18452        let mut sa = vec![0, 2, -1, 5];
18453
18454        let f = renumber_unique_and_nonunique_lms_suffixes_32s(&mut t, &mut sa, 2, 0, 0, 2);
18455
18456        assert_eq!(f, 1);
18457        assert_eq!(t[0], SAINT_MIN);
18458        assert_eq!(sa[2], SAINT_MIN);
18459        assert_eq!(sa[3], 4);
18460    }
18461
18462    #[test]
18463    fn libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_matches_upstream_c_helper() {
18464        let mut t_rust = vec![0, 0, 0, 0];
18465        let mut sa_rust = vec![0, 2, -1, 5];
18466        let mut t_c = t_rust.clone();
18467        let mut sa_c = sa_rust.clone();
18468
18469        let rust_f =
18470            renumber_unique_and_nonunique_lms_suffixes_32s(&mut t_rust, &mut sa_rust, 2, 0, 0, 2);
18471        let c_f = unsafe {
18472            probe_libsais64_renumber_unique_and_nonunique_lms_suffixes_32s(
18473                t_c.as_mut_ptr(),
18474                sa_c.as_mut_ptr(),
18475                2,
18476                0,
18477                0,
18478                2,
18479            )
18480        };
18481
18482        assert_eq!(rust_f, c_f);
18483        assert_eq!(t_rust, t_c);
18484        assert_eq!(sa_rust, sa_c);
18485    }
18486
18487    #[test]
18488    fn libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_omp_matches_upstream_c_helper() {
18489        let mut t_rust = vec![0, 0, 0, 0];
18490        let mut sa_rust = vec![0, 2, -1, 5];
18491        let mut t_c = t_rust.clone();
18492        let mut sa_c = sa_rust.clone();
18493        let mut thread_state = alloc_thread_state(1).unwrap();
18494
18495        let rust_f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(
18496            &mut t_rust,
18497            &mut sa_rust,
18498            2,
18499            1,
18500            &mut thread_state,
18501        );
18502        let c_f = unsafe {
18503            probe_libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_omp(
18504                t_c.as_mut_ptr(),
18505                sa_c.as_mut_ptr(),
18506                2,
18507                1,
18508            )
18509        };
18510
18511        assert_eq!(rust_f, c_f);
18512        assert_eq!(t_rust, t_c);
18513        assert_eq!(sa_rust, sa_c);
18514    }
18515
18516    #[test]
18517    fn libsais64_renumber_unique_and_nonunique_lms_suffixes_32s_omp_uses_block_partition() {
18518        let m = 65_600usize;
18519        let n = 2 * m;
18520        let t = vec![0; n];
18521        let mut sa = vec![0; n];
18522        for i in 0..m {
18523            sa[i] = (2 * i) as SaSint;
18524            sa[m + i] = if i % 5 == 0 {
18525                -((i as SaSint) + 1)
18526            } else {
18527                i as SaSint + 7
18528            };
18529        }
18530
18531        let mut single_t = t.clone();
18532        let mut single_sa = sa.clone();
18533        let mut threaded_t = t;
18534        let mut threaded_sa = sa;
18535        let mut thread_state = alloc_thread_state(4).unwrap();
18536        let single_f = renumber_unique_and_nonunique_lms_suffixes_32s(
18537            &mut single_t,
18538            &mut single_sa,
18539            m as SaSint,
18540            0,
18541            0,
18542            m as FastSint,
18543        );
18544        let threaded_f = renumber_unique_and_nonunique_lms_suffixes_32s_omp(
18545            &mut threaded_t,
18546            &mut threaded_sa,
18547            m as SaSint,
18548            4,
18549            &mut thread_state,
18550        );
18551
18552        assert_eq!(threaded_f, single_f);
18553        assert_eq!(threaded_t, single_t);
18554        assert_eq!(threaded_sa, single_sa);
18555    }
18556
18557    #[test]
18558    fn libsais64_compact_unique_and_nonunique_lms_suffixes_32s_splits_unique_and_nonunique_ranges()
18559    {
18560        let mut sa = vec![0, 0, 0, 0, SAINT_MIN, 4];
18561        let mut l = 2;
18562        let mut r = 6;
18563
18564        compact_unique_and_nonunique_lms_suffixes_32s(&mut sa, 2, &mut l, &mut r, 0, 2);
18565
18566        assert_eq!(l, 2);
18567        assert_eq!(r, 6);
18568        assert_eq!(sa[2], 0);
18569        assert_eq!(sa[3] & SAINT_MAX, 0);
18570    }
18571
18572    #[test]
18573    fn libsais64_compact_lms_suffixes_32s_omp_runs_renumber_then_compaction() {
18574        let mut t = vec![0, 0, 0, 0];
18575        let mut sa = vec![0, 2, -1, 5, 77, 88];
18576        let mut thread_state = alloc_thread_state(2).unwrap();
18577
18578        let f = compact_lms_suffixes_32s_omp(&mut t, &mut sa, 4, 2, 2, 2, &mut thread_state);
18579
18580        assert_eq!(f, 1);
18581        assert_eq!(sa[2] & SAINT_MAX, 0);
18582        assert_eq!(sa[5], 3);
18583    }
18584
18585    #[test]
18586    fn libsais64_compact_unique_and_nonunique_lms_suffixes_32s_omp_uses_block_partition() {
18587        let n = 131_200usize;
18588        let m = 65_600usize;
18589        let fs = m + 32;
18590        let half_n = n >> 1;
18591        let f = m / 5;
18592        let mut sa = vec![0; n + fs];
18593        for i in 0..half_n {
18594            sa[m + i] = if i % 5 == 0 {
18595                SAINT_MIN | i as SaSint
18596            } else {
18597                i as SaSint + 1
18598            };
18599        }
18600        for i in 0..f {
18601            sa[m - f + i] = (10_000 + i) as SaSint;
18602        }
18603
18604        let mut single = sa.clone();
18605        let mut threaded = sa;
18606        let mut single_state = alloc_thread_state(1).unwrap();
18607        let mut threaded_state = alloc_thread_state(4).unwrap();
18608        compact_unique_and_nonunique_lms_suffixes_32s_omp(
18609            &mut single,
18610            n as SaSint,
18611            m as SaSint,
18612            fs as SaSint,
18613            f as SaSint,
18614            1,
18615            &mut single_state,
18616        );
18617        compact_unique_and_nonunique_lms_suffixes_32s_omp(
18618            &mut threaded,
18619            n as SaSint,
18620            m as SaSint,
18621            fs as SaSint,
18622            f as SaSint,
18623            4,
18624            &mut threaded_state,
18625        );
18626
18627        let unique_dst = n + fs - m;
18628        assert_eq!(
18629            &threaded[unique_dst..unique_dst + f],
18630            &single[unique_dst..unique_dst + f]
18631        );
18632    }
18633
18634    #[test]
18635    fn libsais64_compact_lms_suffixes_32s_omp_uses_large_input_paths() {
18636        let n = 131_200usize;
18637        let m = 65_600usize;
18638        let fs = m + 32;
18639        let t = vec![0; n];
18640        let mut sa = vec![0; n + fs];
18641        for i in 0..m {
18642            sa[i] = (2 * i) as SaSint;
18643            sa[m + i] = if i % 5 == 0 {
18644                -((i as SaSint) + 1)
18645            } else {
18646                i as SaSint + 7
18647            };
18648        }
18649
18650        let mut single_t = t.clone();
18651        let mut single_sa = sa.clone();
18652        let mut threaded_t = t;
18653        let mut threaded_sa = sa;
18654        let mut single_state = alloc_thread_state(1).unwrap();
18655        let mut threaded_state = alloc_thread_state(4).unwrap();
18656        let single_f = compact_lms_suffixes_32s_omp(
18657            &mut single_t,
18658            &mut single_sa,
18659            n as SaSint,
18660            m as SaSint,
18661            fs as SaSint,
18662            1,
18663            &mut single_state,
18664        );
18665        let threaded_f = compact_lms_suffixes_32s_omp(
18666            &mut threaded_t,
18667            &mut threaded_sa,
18668            n as SaSint,
18669            m as SaSint,
18670            fs as SaSint,
18671            4,
18672            &mut threaded_state,
18673        );
18674
18675        assert_eq!(threaded_f, single_f);
18676        assert_eq!(threaded_t, single_t);
18677        let unique_dst = n + fs - m;
18678        let unique_len = usize::try_from(threaded_f).expect("f must be non-negative");
18679        assert_eq!(
18680            &threaded_sa[unique_dst..unique_dst + unique_len],
18681            &single_sa[unique_dst..unique_dst + unique_len]
18682        );
18683    }
18684
18685    #[test]
18686    fn libsais64_merge_unique_lms_suffixes_32s_noops_for_empty_block() {
18687        let mut t = vec![1, SAINT_MIN, 2, SAINT_MIN];
18688        let mut sa = vec![0, 0, 1, 3];
18689        let before_t = t.clone();
18690        let before_sa = sa.clone();
18691
18692        merge_unique_lms_suffixes_32s(&mut t, &mut sa, 4, 1, 0, 0, 0);
18693
18694        assert_eq!(t, before_t);
18695        assert_eq!(sa, before_sa);
18696    }
18697
18698    #[test]
18699    fn libsais64_merge_unique_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
18700        let n = 65_600usize;
18701        let m = 1_024usize;
18702        let mut t = vec![1; n];
18703        for i in (0..n).step_by(257) {
18704            t[i] = SAINT_MIN | ((i % 251) as SaSint);
18705        }
18706        let f = t.iter().filter(|&&value| value < 0).count();
18707        let mut sa = vec![-1; n];
18708        let src = n - m - 1;
18709        for i in 0..f {
18710            sa[src + i] = i as SaSint;
18711        }
18712
18713        let mut single_t = t.clone();
18714        let mut single_sa = sa.clone();
18715        let mut threaded_t = t;
18716        let mut threaded_sa = sa;
18717        let mut thread_state = alloc_thread_state(4).unwrap();
18718        merge_unique_lms_suffixes_32s_omp(
18719            &mut single_t,
18720            &mut single_sa,
18721            n as SaSint,
18722            m as SaSint,
18723            1,
18724            &mut [],
18725        );
18726        merge_unique_lms_suffixes_32s_omp(
18727            &mut threaded_t,
18728            &mut threaded_sa,
18729            n as SaSint,
18730            m as SaSint,
18731            4,
18732            &mut thread_state,
18733        );
18734
18735        assert_eq!(threaded_t, single_t);
18736        assert_eq!(threaded_sa, single_sa);
18737    }
18738
18739    #[test]
18740    fn libsais64_merge_nonunique_lms_suffixes_32s_noops_for_empty_block() {
18741        let mut sa = vec![0, 7, 0, 13, 11];
18742        let before = sa.clone();
18743
18744        merge_nonunique_lms_suffixes_32s(&mut sa, 4, 1, 0, 0, 0);
18745
18746        assert_eq!(sa, before);
18747    }
18748
18749    #[test]
18750    fn libsais64_merge_compacted_lms_suffixes_32s_omp_preserves_input_text_and_fills_zero_slots() {
18751        let mut t = vec![1, 2, 3, 4];
18752        let mut sa = vec![0, 1, 2, 3, 4, 5];
18753        let before_t = t.clone();
18754        let mut thread_state = alloc_thread_state(2).unwrap();
18755
18756        merge_compacted_lms_suffixes_32s_omp(&mut t, &mut sa, 4, 1, 1, 2, &mut thread_state);
18757
18758        assert_eq!(t, before_t);
18759        assert_eq!(sa[0], 3);
18760        assert_eq!(sa[1], 1);
18761    }
18762
18763    #[test]
18764    fn libsais64_merge_nonunique_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
18765        let n = 131_200usize;
18766        let m = 65_600usize;
18767        let f = 7usize;
18768        let mut sa = vec![1; n];
18769        let zero_count = (0..m).filter(|i| i % 17 == 0).count();
18770        for i in (0..m).step_by(17) {
18771            sa[i] = 0;
18772        }
18773        let src = n - m - 1 + f;
18774        for i in 0..zero_count {
18775            sa[src + i] = 10_000 + i as SaSint;
18776        }
18777
18778        let mut single = sa.clone();
18779        let mut threaded = sa;
18780        let mut thread_state = alloc_thread_state(4).unwrap();
18781        merge_nonunique_lms_suffixes_32s_omp(
18782            &mut single,
18783            n as SaSint,
18784            m as SaSint,
18785            f as SaSint,
18786            1,
18787            &mut [],
18788        );
18789        merge_nonunique_lms_suffixes_32s_omp(
18790            &mut threaded,
18791            n as SaSint,
18792            m as SaSint,
18793            f as SaSint,
18794            4,
18795            &mut thread_state,
18796        );
18797
18798        assert_eq!(threaded, single);
18799    }
18800
18801    #[test]
18802    fn libsais64_merge_compacted_lms_suffixes_32s_omp_uses_block_partition_for_large_inputs() {
18803        let n = 131_200usize;
18804        let m = 65_600usize;
18805        let mut t = vec![1; n];
18806        for i in (0..n).step_by(257) {
18807            t[i] = SAINT_MIN | ((i % 251) as SaSint);
18808        }
18809        let f = t.iter().filter(|&&value| value < 0).count();
18810
18811        let mut sa = vec![1; n];
18812        let zero_count = (0..m).filter(|i| i % 17 == 0).count();
18813        for i in (0..m).step_by(17) {
18814            sa[i] = 0;
18815        }
18816        let unique_src = n - m - 1;
18817        for i in 0..f {
18818            sa[unique_src + i] = i as SaSint;
18819        }
18820        for i in 0..zero_count {
18821            sa[unique_src + f + i] = 10_000 + i as SaSint;
18822        }
18823
18824        let mut single_t = t.clone();
18825        let mut single_sa = sa.clone();
18826        let mut threaded_t = t;
18827        let mut threaded_sa = sa;
18828        let mut single_state = alloc_thread_state(1).unwrap();
18829        let mut threaded_state = alloc_thread_state(4).unwrap();
18830        merge_compacted_lms_suffixes_32s_omp(
18831            &mut single_t,
18832            &mut single_sa,
18833            n as SaSint,
18834            m as SaSint,
18835            f as SaSint,
18836            1,
18837            &mut single_state,
18838        );
18839        merge_compacted_lms_suffixes_32s_omp(
18840            &mut threaded_t,
18841            &mut threaded_sa,
18842            n as SaSint,
18843            m as SaSint,
18844            f as SaSint,
18845            4,
18846            &mut threaded_state,
18847        );
18848
18849        assert_eq!(threaded_t, single_t);
18850        assert_eq!(threaded_sa, single_sa);
18851    }
18852
18853    #[test]
18854    fn libsais64_final_bwt_left_to_right_8u_block_omp_uses_thread_buckets() {
18855        let block_start = 20_000usize;
18856        let block_size = 16_384usize;
18857        let n = block_start + block_size + 8;
18858        let t = vec![1_u8; n];
18859        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18860
18861        let mut expected_sa = vec![0; n];
18862        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18863        let mut threaded_sa = expected_sa.clone();
18864        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18865        let mut threaded_bucket = expected_bucket.clone();
18866        let mut thread_state = alloc_thread_state(4).unwrap();
18867
18868        final_bwt_scan_left_to_right_8u(
18869            &t,
18870            &mut expected_sa,
18871            &mut expected_bucket,
18872            block_start as FastSint,
18873            block_size as FastSint,
18874        );
18875        final_bwt_scan_left_to_right_8u_block_omp(
18876            &t,
18877            &mut threaded_sa,
18878            ALPHABET_SIZE as SaSint,
18879            &mut threaded_bucket,
18880            block_start as FastSint,
18881            block_size as FastSint,
18882            4,
18883            &mut thread_state,
18884        );
18885
18886        assert_eq!(threaded_sa, expected_sa);
18887        assert_eq!(threaded_bucket, expected_bucket);
18888    }
18889
18890    #[test]
18891    fn libsais64_final_bwt_aux_left_to_right_8u_block_omp_uses_thread_buckets() {
18892        let block_start = 20_000usize;
18893        let block_size = 16_384usize;
18894        let n = block_start + block_size + 8;
18895        let t = vec![1_u8; n];
18896        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18897
18898        let mut expected_sa = vec![0; n];
18899        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18900        let mut threaded_sa = expected_sa.clone();
18901        let mut expected_i = vec![0; n];
18902        let mut threaded_i = vec![0; n];
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_bwt_aux_scan_left_to_right_8u(
18908            &t,
18909            &mut expected_sa,
18910            0,
18911            &mut expected_i,
18912            &mut expected_bucket,
18913            block_start as FastSint,
18914            block_size as FastSint,
18915        );
18916        final_bwt_aux_scan_left_to_right_8u_block_omp(
18917            &t,
18918            &mut threaded_sa,
18919            ALPHABET_SIZE as SaSint,
18920            0,
18921            &mut threaded_i,
18922            &mut threaded_bucket,
18923            block_start as FastSint,
18924            block_size as FastSint,
18925            4,
18926            &mut thread_state,
18927        );
18928
18929        assert_eq!(threaded_sa, expected_sa);
18930        assert_eq!(threaded_i, expected_i);
18931        assert_eq!(threaded_bucket, expected_bucket);
18932    }
18933
18934    #[test]
18935    fn libsais64_final_sorting_right_to_left_8u_block_omp_uses_thread_buckets() {
18936        let block_start = 20_000usize;
18937        let block_size = 16_384usize;
18938        let n = block_start + block_size + 8;
18939        let t = vec![1_u8; n];
18940        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18941
18942        let mut expected_sa = vec![0; n];
18943        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18944        let mut threaded_sa = expected_sa.clone();
18945        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18946        expected_bucket[1] = n as SaSint;
18947        let mut threaded_bucket = expected_bucket.clone();
18948        let mut thread_state = alloc_thread_state(4).unwrap();
18949
18950        final_sorting_scan_right_to_left_8u(
18951            &t,
18952            &mut expected_sa,
18953            &mut expected_bucket,
18954            block_start as FastSint,
18955            block_size as FastSint,
18956        );
18957        final_sorting_scan_right_to_left_8u_block_omp(
18958            &t,
18959            &mut threaded_sa,
18960            ALPHABET_SIZE as SaSint,
18961            &mut threaded_bucket,
18962            block_start as FastSint,
18963            block_size as FastSint,
18964            4,
18965            &mut thread_state,
18966        );
18967
18968        assert_eq!(threaded_sa, expected_sa);
18969        assert_eq!(threaded_bucket, expected_bucket);
18970    }
18971
18972    #[test]
18973    fn libsais64_final_bwt_right_to_left_8u_block_omp_uses_thread_buckets() {
18974        let block_start = 20_000usize;
18975        let block_size = 16_384usize;
18976        let n = block_start + block_size + 8;
18977        let t = vec![1_u8; n];
18978        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
18979
18980        let mut expected_sa = vec![0; n];
18981        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
18982        let mut threaded_sa = expected_sa.clone();
18983        let mut expected_bucket = vec![0; ALPHABET_SIZE];
18984        expected_bucket[1] = n as SaSint;
18985        let mut threaded_bucket = expected_bucket.clone();
18986        let mut thread_state = alloc_thread_state(4).unwrap();
18987
18988        final_bwt_scan_right_to_left_8u(
18989            &t,
18990            &mut expected_sa,
18991            &mut expected_bucket,
18992            block_start as FastSint,
18993            block_size as FastSint,
18994        );
18995        final_bwt_scan_right_to_left_8u_block_omp(
18996            &t,
18997            &mut threaded_sa,
18998            ALPHABET_SIZE as SaSint,
18999            &mut threaded_bucket,
19000            block_start as FastSint,
19001            block_size as FastSint,
19002            4,
19003            &mut thread_state,
19004        );
19005
19006        assert_eq!(threaded_sa, expected_sa);
19007        assert_eq!(threaded_bucket, expected_bucket);
19008    }
19009
19010    #[test]
19011    fn libsais64_final_bwt_aux_right_to_left_8u_block_omp_uses_thread_buckets() {
19012        let block_start = 20_000usize;
19013        let block_size = 16_384usize;
19014        let n = block_start + block_size + 8;
19015        let t = vec![1_u8; n];
19016        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19017
19018        let mut expected_sa = vec![0; n];
19019        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19020        let mut threaded_sa = expected_sa.clone();
19021        let mut expected_i = vec![0; n];
19022        let mut threaded_i = vec![0; n];
19023        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19024        expected_bucket[1] = n as SaSint;
19025        let mut threaded_bucket = expected_bucket.clone();
19026        let mut thread_state = alloc_thread_state(4).unwrap();
19027
19028        final_bwt_aux_scan_right_to_left_8u(
19029            &t,
19030            &mut expected_sa,
19031            0,
19032            &mut expected_i,
19033            &mut expected_bucket,
19034            block_start as FastSint,
19035            block_size as FastSint,
19036        );
19037        final_bwt_aux_scan_right_to_left_8u_block_omp(
19038            &t,
19039            &mut threaded_sa,
19040            ALPHABET_SIZE as SaSint,
19041            0,
19042            &mut threaded_i,
19043            &mut threaded_bucket,
19044            block_start as FastSint,
19045            block_size as FastSint,
19046            4,
19047            &mut thread_state,
19048        );
19049
19050        assert_eq!(threaded_sa, expected_sa);
19051        assert_eq!(threaded_i, expected_i);
19052        assert_eq!(threaded_bucket, expected_bucket);
19053    }
19054
19055    #[test]
19056    fn libsais64_final_gsa_right_to_left_8u_block_omp_uses_thread_buckets() {
19057        let block_start = 20_000usize;
19058        let block_size = 16_384usize;
19059        let n = block_start + block_size + 8;
19060        let t = vec![1_u8; n];
19061        let suffixes: Vec<SaSint> = (2..2 + block_size).map(|i| i as SaSint).collect();
19062
19063        let mut expected_sa = vec![0; n];
19064        expected_sa[block_start..block_start + block_size].copy_from_slice(&suffixes);
19065        let mut threaded_sa = expected_sa.clone();
19066        let mut expected_bucket = vec![0; ALPHABET_SIZE];
19067        expected_bucket[1] = n as SaSint;
19068        let mut threaded_bucket = expected_bucket.clone();
19069        let mut thread_state = alloc_thread_state(4).unwrap();
19070
19071        final_gsa_scan_right_to_left_8u(
19072            &t,
19073            &mut expected_sa,
19074            &mut expected_bucket,
19075            block_start as FastSint,
19076            block_size as FastSint,
19077        );
19078        final_gsa_scan_right_to_left_8u_block_omp(
19079            &t,
19080            &mut threaded_sa,
19081            ALPHABET_SIZE as SaSint,
19082            &mut threaded_bucket,
19083            block_start as FastSint,
19084            block_size as FastSint,
19085            4,
19086            &mut thread_state,
19087        );
19088
19089        assert_eq!(threaded_sa, expected_sa);
19090        assert_eq!(threaded_bucket, expected_bucket);
19091    }
19092
19093    #[test]
19094    fn libsais64_count_and_gather_lms_suffixes_8u_omp_uses_block_partition_for_large_inputs() {
19095        let n = 65_600usize;
19096        let text: Vec<u8> = (0..n)
19097            .map(|i| 1 + ((i * 37 + i / 17) % 251) as u8)
19098            .collect();
19099
19100        let mut sa_threaded = vec![-99; n];
19101        let mut sa_scalar = vec![-99; n];
19102        let mut buckets_threaded = vec![0; 4 * ALPHABET_SIZE];
19103        let mut buckets_scalar = vec![0; 4 * ALPHABET_SIZE];
19104        let mut thread_state = alloc_thread_state(4).unwrap();
19105
19106        let m_threaded = count_and_gather_lms_suffixes_8u_omp(
19107            &text,
19108            &mut sa_threaded,
19109            n as SaSint,
19110            &mut buckets_threaded,
19111            4,
19112            &mut thread_state,
19113        );
19114        let m_scalar = count_and_gather_lms_suffixes_8u(
19115            &text,
19116            &mut sa_scalar,
19117            n as SaSint,
19118            &mut buckets_scalar,
19119            0,
19120            n as FastSint,
19121        );
19122
19123        assert_eq!(m_threaded, m_scalar);
19124        assert_eq!(
19125            &sa_threaded[n - m_threaded as usize..],
19126            &sa_scalar[n - m_scalar as usize..]
19127        );
19128        assert_eq!(buckets_threaded, buckets_scalar);
19129    }
19130
19131    #[test]
19132    fn libsais64_gather_lms_suffixes_8u_omp_uses_thread_state_for_large_inputs() {
19133        let n = 65_600usize;
19134        let text: Vec<u8> = (0..n)
19135            .map(|i| 1 + ((i * 37 + i / 17) % 251) as u8)
19136            .collect();
19137        let mut thread_state = alloc_thread_state(4).unwrap();
19138        let mut count_sa = vec![-99; n];
19139        let mut buckets = vec![0; 4 * ALPHABET_SIZE];
19140        let m = count_and_gather_lms_suffixes_8u_omp(
19141            &text,
19142            &mut count_sa,
19143            n as SaSint,
19144            &mut buckets,
19145            4,
19146            &mut thread_state,
19147        );
19148
19149        let mut threaded = vec![-99; n];
19150        let mut scalar = vec![-99; n];
19151        gather_lms_suffixes_8u_omp(&text, &mut threaded, n as SaSint, 4, &mut thread_state);
19152        gather_lms_suffixes_8u(
19153            &text,
19154            &mut scalar,
19155            n as SaSint,
19156            n as FastSint - 1,
19157            0,
19158            n as FastSint,
19159        );
19160
19161        assert_eq!(&threaded[n - m as usize..], &scalar[n - m as usize..]);
19162    }
19163
19164    #[test]
19165    fn libsais64_count_and_gather_lms_suffixes_32s_4k_updates_counts_and_suffixes() {
19166        let t = vec![2, 1, 3, 1, 0];
19167        let mut sa = vec![0; t.len()];
19168        let mut buckets = vec![0; 4 * 4];
19169        let m = count_and_gather_lms_suffixes_32s_4k(
19170            &t,
19171            &mut sa,
19172            t.len() as SaSint,
19173            4,
19174            &mut buckets,
19175            0,
19176            t.len() as FastSint,
19177        );
19178        assert!(m >= 0);
19179        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19180    }
19181
19182    #[test]
19183    fn libsais64_count_and_gather_lms_suffixes_32s_2k_updates_counts_and_suffixes() {
19184        let t = vec![2, 1, 3, 1, 0];
19185        let mut sa = vec![0; t.len()];
19186        let mut buckets = vec![0; 2 * 4];
19187        let m = count_and_gather_lms_suffixes_32s_2k(
19188            &t,
19189            &mut sa,
19190            t.len() as SaSint,
19191            4,
19192            &mut buckets,
19193            0,
19194            t.len() as FastSint,
19195        );
19196        assert!(m >= 0);
19197        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19198    }
19199
19200    #[test]
19201    fn libsais64_count_and_gather_compacted_lms_suffixes_32s_2k_updates_counts_and_suffixes() {
19202        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
19203        let mut sa = vec![0; t.len()];
19204        let mut buckets = vec![0; 2 * 4];
19205        let m = count_and_gather_compacted_lms_suffixes_32s_2k(
19206            &t,
19207            &mut sa,
19208            t.len() as SaSint,
19209            4,
19210            &mut buckets,
19211            0,
19212            t.len() as FastSint,
19213        );
19214        assert!(m >= 0);
19215        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19216    }
19217
19218    #[test]
19219    fn libsais64_count_and_gather_lms_suffixes_32s_4k_nofs_omp_wraps_sequential_version() {
19220        let t = vec![2, 1, 3, 1, 0];
19221        let mut sa = vec![0; t.len()];
19222        let mut buckets = vec![0; 4 * 4];
19223        let m = count_and_gather_lms_suffixes_32s_4k_nofs_omp(
19224            &t,
19225            &mut sa,
19226            t.len() as SaSint,
19227            4,
19228            &mut buckets,
19229            2,
19230        );
19231        assert!(m >= 0);
19232        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19233    }
19234
19235    #[test]
19236    fn libsais64_count_and_gather_lms_suffixes_32s_2k_nofs_omp_wraps_sequential_version() {
19237        let t = vec![2, 1, 3, 1, 0];
19238        let mut sa = vec![0; t.len()];
19239        let mut buckets = vec![0; 2 * 4];
19240        let m = count_and_gather_lms_suffixes_32s_2k_nofs_omp(
19241            &t,
19242            &mut sa,
19243            t.len() as SaSint,
19244            4,
19245            &mut buckets,
19246            2,
19247        );
19248        assert!(m >= 0);
19249        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19250    }
19251
19252    #[test]
19253    fn libsais64_count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp_wraps_sequential_version()
19254    {
19255        let t = vec![2, SAINT_MIN | 1, 3, 1, 0];
19256        let mut sa = vec![0; t.len()];
19257        let mut buckets = vec![0; 2 * 4];
19258        let m = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
19259            &t,
19260            &mut sa,
19261            t.len() as SaSint,
19262            4,
19263            &mut buckets,
19264            2,
19265        );
19266        assert!(m >= 0);
19267        assert_eq!(buckets.iter().sum::<SaSint>(), t.len() as SaSint);
19268    }
19269
19270    #[test]
19271    fn libsais64_count_and_gather_lms_suffixes_32s_nofs_omp_uses_large_input_paths() {
19272        let n = 65_600usize;
19273        let k = 257usize;
19274        let text: Vec<SaSint> = (0..n)
19275            .map(|i| 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint)
19276            .collect();
19277
19278        let mut sa_threaded = vec![-99; n];
19279        let mut sa_scalar = vec![-99; n];
19280        let mut buckets_threaded = vec![0; 4 * k];
19281        let mut buckets_scalar = vec![0; 4 * k];
19282        let m_threaded = count_and_gather_lms_suffixes_32s_4k_nofs_omp(
19283            &text,
19284            &mut sa_threaded,
19285            n as SaSint,
19286            k as SaSint,
19287            &mut buckets_threaded,
19288            4,
19289        );
19290        let m_scalar = count_and_gather_lms_suffixes_32s_4k(
19291            &text,
19292            &mut sa_scalar,
19293            n as SaSint,
19294            k as SaSint,
19295            &mut buckets_scalar,
19296            0,
19297            n as FastSint,
19298        );
19299        assert_eq!(m_threaded, m_scalar);
19300        assert_eq!(
19301            &sa_threaded[n - m_threaded as usize..],
19302            &sa_scalar[n - m_scalar as usize..]
19303        );
19304        assert_eq!(buckets_threaded, buckets_scalar);
19305
19306        let mut sa_threaded = vec![-99; n];
19307        let mut sa_scalar = vec![-99; n];
19308        let mut buckets_threaded = vec![0; 2 * k];
19309        let mut buckets_scalar = vec![0; 2 * k];
19310        let m_threaded = count_and_gather_lms_suffixes_32s_2k_nofs_omp(
19311            &text,
19312            &mut sa_threaded,
19313            n as SaSint,
19314            k as SaSint,
19315            &mut buckets_threaded,
19316            4,
19317        );
19318        let m_scalar = count_and_gather_lms_suffixes_32s_2k(
19319            &text,
19320            &mut sa_scalar,
19321            n as SaSint,
19322            k as SaSint,
19323            &mut buckets_scalar,
19324            0,
19325            n as FastSint,
19326        );
19327        assert_eq!(m_threaded, m_scalar);
19328        assert_eq!(
19329            &sa_threaded[n - m_threaded as usize..],
19330            &sa_scalar[n - m_scalar as usize..]
19331        );
19332        assert_eq!(buckets_threaded, buckets_scalar);
19333    }
19334
19335    #[test]
19336    fn libsais64_count_and_gather_lms_suffixes_32s_fs_omp_uses_large_input_paths() {
19337        let n = 65_600usize;
19338        let k = 257usize;
19339        let text: Vec<SaSint> = (0..n)
19340            .map(|i| 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint)
19341            .collect();
19342        let mut thread_state = alloc_thread_state(4).unwrap();
19343
19344        let mut sa_threaded = vec![-99; n];
19345        let mut sa_scalar = vec![-99; n];
19346        let mut buckets_threaded = vec![0; 4 * k];
19347        let mut buckets_scalar = vec![0; 4 * k];
19348        let m_threaded = count_and_gather_lms_suffixes_32s_4k_fs_omp(
19349            &text,
19350            &mut sa_threaded,
19351            n as SaSint,
19352            k as SaSint,
19353            &mut buckets_threaded,
19354            0,
19355            4,
19356            &mut thread_state,
19357        );
19358        let m_scalar = count_and_gather_lms_suffixes_32s_4k(
19359            &text,
19360            &mut sa_scalar,
19361            n as SaSint,
19362            k as SaSint,
19363            &mut buckets_scalar,
19364            0,
19365            n as FastSint,
19366        );
19367        assert_eq!(m_threaded, m_scalar);
19368        assert_eq!(
19369            &sa_threaded[n - m_threaded as usize..],
19370            &sa_scalar[n - m_scalar as usize..]
19371        );
19372        assert_eq!(buckets_threaded, buckets_scalar);
19373
19374        let mut sa_threaded = vec![-99; n];
19375        let mut sa_scalar = vec![-99; n];
19376        let mut buckets_threaded = vec![0; 2 * k];
19377        let mut buckets_scalar = vec![0; 2 * k];
19378        let m_threaded = count_and_gather_lms_suffixes_32s_2k_fs_omp(
19379            &text,
19380            &mut sa_threaded,
19381            n as SaSint,
19382            k as SaSint,
19383            &mut buckets_threaded,
19384            0,
19385            4,
19386            &mut thread_state,
19387        );
19388        let m_scalar = count_and_gather_lms_suffixes_32s_2k(
19389            &text,
19390            &mut sa_scalar,
19391            n as SaSint,
19392            k as SaSint,
19393            &mut buckets_scalar,
19394            0,
19395            n as FastSint,
19396        );
19397        assert_eq!(m_threaded, m_scalar);
19398        assert_eq!(
19399            &sa_threaded[n - m_threaded as usize..],
19400            &sa_scalar[n - m_scalar as usize..]
19401        );
19402        assert_eq!(buckets_threaded, buckets_scalar);
19403    }
19404
19405    #[test]
19406    fn libsais64_count_and_gather_compacted_lms_suffixes_32s_nofs_omp_uses_large_input_path() {
19407        let n = 65_600usize;
19408        let k = 257usize;
19409        let text: Vec<SaSint> = (0..n)
19410            .map(|i| {
19411                let value = 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint;
19412                if i % 19 == 0 {
19413                    value | SAINT_MIN
19414                } else {
19415                    value
19416                }
19417            })
19418            .collect();
19419
19420        let mut sa_threaded = vec![-99; n];
19421        let mut sa_split = vec![-99; n];
19422        let mut buckets_threaded = vec![0; 2 * k];
19423        let mut buckets_split = vec![0; 2 * k];
19424        let m_threaded = count_and_gather_compacted_lms_suffixes_32s_2k_nofs_omp(
19425            &text,
19426            &mut sa_threaded,
19427            n as SaSint,
19428            k as SaSint,
19429            &mut buckets_threaded,
19430            4,
19431        );
19432        count_compacted_lms_suffixes_32s_2k(&text, n as SaSint, k as SaSint, &mut buckets_split);
19433        let m_split = gather_compacted_lms_suffixes_32s(&text, &mut sa_split, n as SaSint);
19434
19435        assert_eq!(m_threaded, m_split);
19436        assert_eq!(
19437            &sa_threaded[n - m_threaded as usize..],
19438            &sa_split[n - m_split as usize..]
19439        );
19440        assert_eq!(buckets_threaded, buckets_split);
19441    }
19442
19443    #[test]
19444    fn libsais64_count_and_gather_compacted_lms_suffixes_32s_fs_omp_uses_large_input_path() {
19445        let n = 65_600usize;
19446        let k = 257usize;
19447        let text: Vec<SaSint> = (0..n)
19448            .map(|i| {
19449                let value = 1 + ((i * 37 + i / 17) % (k - 1)) as SaSint;
19450                if i % 19 == 0 {
19451                    value | SAINT_MIN
19452                } else {
19453                    value
19454                }
19455            })
19456            .collect();
19457
19458        let mut sa_threaded = vec![-99; 2 * n];
19459        let mut sa_scalar = vec![-99; n];
19460        let mut buckets_threaded = vec![0; 2 * k];
19461        let mut buckets_scalar = vec![0; 2 * k];
19462        let mut thread_state = alloc_thread_state(4).unwrap();
19463        count_and_gather_compacted_lms_suffixes_32s_2k_fs_omp(
19464            &text,
19465            &mut sa_threaded,
19466            n as SaSint,
19467            k as SaSint,
19468            &mut buckets_threaded,
19469            0,
19470            4,
19471            &mut thread_state,
19472        );
19473        let m_scalar = count_and_gather_compacted_lms_suffixes_32s_2k(
19474            &text,
19475            &mut sa_scalar,
19476            n as SaSint,
19477            k as SaSint,
19478            &mut buckets_scalar,
19479            0,
19480            n as FastSint,
19481        );
19482
19483        assert_eq!(
19484            &sa_threaded[n - m_scalar as usize..n],
19485            &sa_scalar[n - m_scalar as usize..]
19486        );
19487        assert_eq!(buckets_threaded, buckets_scalar);
19488    }
19489
19490    #[test]
19491    fn libsais64_plcp_lcp_omp_wrappers_match_single_thread_on_large_inputs() {
19492        let n = 65_600usize;
19493        let text: Vec<u8> = (0..n).map(|i| (1 + (i % 251)) as u8).collect();
19494        let sa: Vec<SaSint> = (0..n as SaSint).collect();
19495
19496        let mut plcp_single = vec![0; n];
19497        let mut plcp_threaded = vec![0; n];
19498        compute_phi_omp(&sa, &mut plcp_single, n as SaSint, 1);
19499        compute_phi_omp(&sa, &mut plcp_threaded, n as SaSint, 4);
19500        assert_eq!(plcp_threaded, plcp_single);
19501
19502        compute_plcp_omp(&text, &mut plcp_single, n as SaSint, 1);
19503        compute_plcp_omp(&text, &mut plcp_threaded, n as SaSint, 4);
19504        assert_eq!(plcp_threaded, plcp_single);
19505
19506        let mut lcp_single = vec![0; n];
19507        let mut lcp_threaded = vec![0; n];
19508        compute_lcp_omp(&plcp_single, &sa, &mut lcp_single, n as SaSint, 1);
19509        compute_lcp_omp(&plcp_threaded, &sa, &mut lcp_threaded, n as SaSint, 4);
19510        assert_eq!(lcp_threaded, lcp_single);
19511    }
19512
19513    fn assert_libsais64_matches_c(text: &[u8]) {
19514        let mut rust_sa = vec![0; text.len()];
19515        let mut c_sa = vec![0; text.len()];
19516
19517        let rust_rc = libsais64(text, &mut rust_sa, 0, None);
19518        let c_rc = unsafe {
19519            probe_public_libsais64(text.as_ptr(), c_sa.as_mut_ptr(), text.len() as SaSint, 0)
19520        };
19521
19522        assert_eq!(rust_rc, c_rc);
19523        assert_eq!(rust_sa, c_sa);
19524    }
19525
19526    fn assert_libsais64_gsa_matches_c(text: &[u8]) {
19527        let mut rust_sa = vec![0; text.len()];
19528        let mut c_sa = vec![0; text.len()];
19529
19530        let rust_rc = libsais64_gsa(text, &mut rust_sa, 0, None);
19531        let c_rc = unsafe {
19532            probe_public_libsais64_gsa(text.as_ptr(), c_sa.as_mut_ptr(), text.len() as SaSint, 0)
19533        };
19534
19535        assert_eq!(rust_rc, c_rc);
19536        assert_eq!(rust_sa, c_sa);
19537    }
19538
19539    fn assert_libsais64_long_matches_c(text: &[SaSint], k: SaSint) {
19540        let mut rust_t = text.to_vec();
19541        let mut c_t = text.to_vec();
19542        let mut rust_sa = vec![0; text.len()];
19543        let mut c_sa = vec![0; text.len()];
19544
19545        let rust_rc = libsais64_long(&mut rust_t, &mut rust_sa, k, 0);
19546        let c_rc = unsafe {
19547            probe_public_libsais64_long(
19548                c_t.as_mut_ptr(),
19549                c_sa.as_mut_ptr(),
19550                c_t.len() as SaSint,
19551                k,
19552                0,
19553            )
19554        };
19555
19556        assert_eq!(rust_rc, c_rc);
19557        assert_eq!(rust_t, c_t);
19558        assert_eq!(rust_sa, c_sa);
19559    }
19560
19561    fn assert_libsais64_bwt_matches_c(text: &[u8]) {
19562        let mut rust_u = vec![0; text.len()];
19563        let mut rust_a = vec![0; text.len()];
19564        let mut c_u = vec![0; text.len()];
19565        let mut c_a = vec![0; text.len()];
19566
19567        let rust_rc = libsais64_bwt(text, &mut rust_u, &mut rust_a, 0, None);
19568        let c_rc = unsafe {
19569            probe_public_libsais64_bwt(
19570                text.as_ptr(),
19571                c_u.as_mut_ptr(),
19572                c_a.as_mut_ptr(),
19573                text.len() as SaSint,
19574                0,
19575            )
19576        };
19577
19578        assert_eq!(rust_rc, c_rc);
19579        assert_eq!(rust_u, c_u);
19580    }
19581
19582    fn assert_libsais64_bwt_aux_matches_c(text: &[u8], r: SaSint) {
19583        let aux_len = if text.is_empty() {
19584            0
19585        } else {
19586            (text.len() - 1) / r as usize + 1
19587        };
19588        let mut rust_u = vec![0; text.len()];
19589        let mut rust_a = vec![0; text.len()];
19590        let mut rust_i = vec![0; aux_len];
19591        let mut c_u = vec![0; text.len()];
19592        let mut c_a = vec![0; text.len()];
19593        let mut c_i = vec![0; aux_len];
19594
19595        let rust_rc = libsais64_bwt_aux(text, &mut rust_u, &mut rust_a, 0, None, r, &mut rust_i);
19596        let c_rc = unsafe {
19597            probe_public_libsais64_bwt_aux(
19598                text.as_ptr(),
19599                c_u.as_mut_ptr(),
19600                c_a.as_mut_ptr(),
19601                text.len() as SaSint,
19602                0,
19603                r,
19604                c_i.as_mut_ptr(),
19605            )
19606        };
19607
19608        assert_eq!(rust_rc, c_rc);
19609        assert_eq!(rust_u, c_u);
19610        assert_eq!(rust_i, c_i);
19611    }
19612
19613    fn assert_libsais64_freq_outputs_match_c(text: &[u8], gsa_text: &[u8]) {
19614        let mut rust_sa = vec![0; text.len()];
19615        let mut c_sa = vec![0; text.len()];
19616        let mut rust_freq = vec![-1; ALPHABET_SIZE];
19617        let mut c_freq = vec![-1; ALPHABET_SIZE];
19618
19619        let rust_rc = libsais64(text, &mut rust_sa, 0, Some(&mut rust_freq));
19620        let c_rc = unsafe {
19621            probe_public_libsais64_freq(
19622                text.as_ptr(),
19623                c_sa.as_mut_ptr(),
19624                text.len() as SaSint,
19625                0,
19626                c_freq.as_mut_ptr(),
19627            )
19628        };
19629        assert_eq!(rust_rc, c_rc);
19630        assert_eq!(rust_sa, c_sa);
19631        assert_eq!(rust_freq, c_freq);
19632
19633        let mut rust_gsa = vec![0; gsa_text.len()];
19634        let mut c_gsa = vec![0; gsa_text.len()];
19635        rust_freq.fill(-1);
19636        c_freq.fill(-1);
19637        let rust_rc = libsais64_gsa(gsa_text, &mut rust_gsa, 0, Some(&mut rust_freq));
19638        let c_rc = unsafe {
19639            probe_public_libsais64_gsa_freq(
19640                gsa_text.as_ptr(),
19641                c_gsa.as_mut_ptr(),
19642                gsa_text.len() as SaSint,
19643                0,
19644                c_freq.as_mut_ptr(),
19645            )
19646        };
19647        assert_eq!(rust_rc, c_rc);
19648        assert_eq!(rust_gsa, c_gsa);
19649        assert_eq!(rust_freq, c_freq);
19650
19651        let mut rust_u = vec![0; text.len()];
19652        let mut rust_a = vec![0; text.len()];
19653        let mut c_u = vec![0; text.len()];
19654        let mut c_a = vec![0; text.len()];
19655        rust_freq.fill(-1);
19656        c_freq.fill(-1);
19657        let rust_rc = libsais64_bwt(text, &mut rust_u, &mut rust_a, 0, Some(&mut rust_freq));
19658        let c_rc = unsafe {
19659            probe_public_libsais64_bwt_freq(
19660                text.as_ptr(),
19661                c_u.as_mut_ptr(),
19662                c_a.as_mut_ptr(),
19663                text.len() as SaSint,
19664                0,
19665                c_freq.as_mut_ptr(),
19666            )
19667        };
19668        assert_eq!(rust_rc, c_rc);
19669        assert_eq!(rust_u, c_u);
19670        assert_eq!(rust_freq, c_freq);
19671
19672        let r = 4;
19673        let aux_len = (text.len() - 1) / r as usize + 1;
19674        let mut rust_i = vec![0; aux_len];
19675        let mut c_i = vec![0; aux_len];
19676        rust_freq.fill(-1);
19677        c_freq.fill(-1);
19678        let rust_rc = libsais64_bwt_aux(
19679            text,
19680            &mut rust_u,
19681            &mut rust_a,
19682            0,
19683            Some(&mut rust_freq),
19684            r,
19685            &mut rust_i,
19686        );
19687        let c_rc = unsafe {
19688            probe_public_libsais64_bwt_aux_freq(
19689                text.as_ptr(),
19690                c_u.as_mut_ptr(),
19691                c_a.as_mut_ptr(),
19692                text.len() as SaSint,
19693                0,
19694                c_freq.as_mut_ptr(),
19695                r,
19696                c_i.as_mut_ptr(),
19697            )
19698        };
19699        assert_eq!(rust_rc, c_rc);
19700        assert_eq!(rust_u, c_u);
19701        assert_eq!(rust_i, c_i);
19702        assert_eq!(rust_freq, c_freq);
19703    }
19704
19705    fn assert_libsais64_unbwt_matches_c(text: &[u8]) {
19706        let mut bwt = vec![0; text.len()];
19707        let mut work = vec![0; text.len()];
19708        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, None);
19709        assert!(primary >= 0);
19710
19711        let mut rust_u = vec![0; text.len()];
19712        let mut rust_a = vec![0; text.len() + 1];
19713        let mut c_u = vec![0; text.len()];
19714        let mut c_a = vec![0; text.len() + 1];
19715
19716        let rust_rc = libsais64_unbwt(&bwt, &mut rust_u, &mut rust_a, None, primary);
19717        let c_rc = unsafe {
19718            probe_public_libsais64_unbwt(
19719                bwt.as_ptr(),
19720                c_u.as_mut_ptr(),
19721                c_a.as_mut_ptr(),
19722                bwt.len() as SaSint,
19723                primary,
19724            )
19725        };
19726
19727        assert_eq!(rust_rc, c_rc);
19728        assert_eq!(rust_u, c_u);
19729        assert_eq!(rust_u, text);
19730    }
19731
19732    fn assert_libsais64_unbwt_aux_matches_c(text: &[u8], r: SaSint) {
19733        let mut bwt = vec![0; text.len()];
19734        let mut work = vec![0; text.len()];
19735        let mut aux = vec![0; (text.len() - 1) / r as usize + 1];
19736        let bwt_rc = libsais64_bwt_aux(text, &mut bwt, &mut work, 0, None, r, &mut aux);
19737        assert_eq!(bwt_rc, 0);
19738
19739        let mut rust_u = vec![0; text.len()];
19740        let mut rust_a = vec![0; text.len() + 1];
19741        let mut c_u = vec![0; text.len()];
19742        let mut c_a = vec![0; text.len() + 1];
19743
19744        let rust_rc = libsais64_unbwt_aux(&bwt, &mut rust_u, &mut rust_a, None, r, &aux);
19745        let c_rc = unsafe {
19746            probe_public_libsais64_unbwt_aux(
19747                bwt.as_ptr(),
19748                c_u.as_mut_ptr(),
19749                c_a.as_mut_ptr(),
19750                bwt.len() as SaSint,
19751                r,
19752                aux.as_ptr(),
19753            )
19754        };
19755
19756        assert_eq!(rust_rc, c_rc);
19757        assert_eq!(rust_u, c_u);
19758        assert_eq!(rust_u, text);
19759    }
19760
19761    fn assert_libsais64_unbwt_freq_matches_c(text: &[u8]) {
19762        let mut freq = vec![0; ALPHABET_SIZE];
19763        let mut bwt = vec![0; text.len()];
19764        let mut work = vec![0; text.len()];
19765        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
19766        assert!(primary >= 0);
19767
19768        let mut rust_u = vec![0; text.len()];
19769        let mut rust_a = vec![0; text.len() + 1];
19770        let mut c_u = vec![0; text.len()];
19771        let mut c_a = vec![0; text.len() + 1];
19772
19773        let rust_rc = libsais64_unbwt(&bwt, &mut rust_u, &mut rust_a, Some(&freq), primary);
19774        let c_rc = unsafe {
19775            probe_public_libsais64_unbwt_freq(
19776                bwt.as_ptr(),
19777                c_u.as_mut_ptr(),
19778                c_a.as_mut_ptr(),
19779                bwt.len() as SaSint,
19780                freq.as_ptr(),
19781                primary,
19782            )
19783        };
19784        assert_eq!(rust_rc, c_rc);
19785        assert_eq!(rust_u, c_u);
19786        assert_eq!(rust_u, text);
19787
19788        let r = 4;
19789        let mut aux = vec![0; (text.len() - 1) / r as usize + 1];
19790        let bwt_rc = libsais64_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), r, &mut aux);
19791        assert_eq!(bwt_rc, 0);
19792
19793        rust_u.fill(0);
19794        rust_a.fill(0);
19795        c_u.fill(0);
19796        c_a.fill(0);
19797        let rust_rc = libsais64_unbwt_aux(&bwt, &mut rust_u, &mut rust_a, Some(&freq), r, &aux);
19798        let c_rc = unsafe {
19799            probe_public_libsais64_unbwt_aux_freq(
19800                bwt.as_ptr(),
19801                c_u.as_mut_ptr(),
19802                c_a.as_mut_ptr(),
19803                bwt.len() as SaSint,
19804                freq.as_ptr(),
19805                r,
19806                aux.as_ptr(),
19807            )
19808        };
19809        assert_eq!(rust_rc, c_rc);
19810        assert_eq!(rust_u, c_u);
19811        assert_eq!(rust_u, text);
19812    }
19813
19814    fn assert_libsais64_plcp_lcp_matches_c(text: &[u8]) {
19815        let mut sa = vec![0; text.len()];
19816        let sa_rc = libsais64(text, &mut sa, 0, None);
19817        assert_eq!(sa_rc, 0);
19818
19819        let mut rust_plcp = vec![0; text.len()];
19820        let mut c_plcp = vec![0; text.len()];
19821        let rust_plcp_rc = libsais64_plcp(text, &sa, &mut rust_plcp);
19822        let c_plcp_rc = unsafe {
19823            probe_public_libsais64_plcp(
19824                text.as_ptr(),
19825                sa.as_ptr(),
19826                c_plcp.as_mut_ptr(),
19827                text.len() as SaSint,
19828            )
19829        };
19830        assert_eq!(rust_plcp_rc, c_plcp_rc);
19831        assert_eq!(rust_plcp, c_plcp);
19832
19833        let mut rust_lcp = vec![0; text.len()];
19834        let mut c_lcp = vec![0; text.len()];
19835        let rust_lcp_rc = libsais64_lcp(&rust_plcp, &sa, &mut rust_lcp);
19836        let c_lcp_rc = unsafe {
19837            probe_public_libsais64_lcp(
19838                c_plcp.as_ptr(),
19839                sa.as_ptr(),
19840                c_lcp.as_mut_ptr(),
19841                text.len() as SaSint,
19842            )
19843        };
19844        assert_eq!(rust_lcp_rc, c_lcp_rc);
19845        assert_eq!(rust_lcp, c_lcp);
19846    }
19847
19848    fn assert_libsais64_plcp_gsa_matches_c(text: &[u8]) {
19849        let mut sa = vec![0; text.len()];
19850        assert_eq!(libsais64_gsa(text, &mut sa, 0, None), 0);
19851
19852        let mut rust_plcp = vec![0; text.len()];
19853        let mut c_plcp = vec![0; text.len()];
19854        let rust_rc = libsais64_plcp_gsa(text, &sa, &mut rust_plcp);
19855        let c_rc = unsafe {
19856            probe_public_libsais64_plcp_gsa(
19857                text.as_ptr(),
19858                sa.as_ptr(),
19859                c_plcp.as_mut_ptr(),
19860                text.len() as SaSint,
19861            )
19862        };
19863
19864        assert_eq!(rust_rc, c_rc);
19865        assert_eq!(rust_plcp, c_plcp);
19866    }
19867
19868    fn assert_libsais64_bwt_aux_round_trips(text: &[u8], r: SaSint) {
19869        let mut bwt = vec![0; text.len()];
19870        let mut work = vec![0; text.len()];
19871        let mut restored = vec![0; text.len()];
19872        let mut aux = vec![0; (text.len() - 1) / r as usize + 1];
19873
19874        let bwt_rc = libsais64_bwt_aux(text, &mut bwt, &mut work, 0, None, r, &mut aux);
19875        assert_eq!(bwt_rc, 0);
19876
19877        let unbwt_rc = libsais64_unbwt_aux(&bwt, &mut restored, &mut work, None, r, &aux);
19878        assert_eq!(unbwt_rc, 0);
19879        assert_eq!(restored, text);
19880    }
19881
19882    #[test]
19883    fn public_libsais64_matches_upstream_c() {
19884        for text in [
19885            b"".as_slice(),
19886            b"a",
19887            b"banana",
19888            b"mississippi",
19889            b"abracadabra",
19890            b"AAAAAAAAAAAAAAAA",
19891            b"zyxwvutsrqponmlk",
19892        ] {
19893            assert_libsais64_matches_c(text);
19894        }
19895    }
19896
19897    #[test]
19898    fn public_libsais64_bwt_matches_upstream_c() {
19899        for text in [
19900            b"".as_slice(),
19901            b"a",
19902            b"banana",
19903            b"mississippi",
19904            b"abracadabra",
19905            b"AAAAAAAAAAAAAAAA",
19906            b"zyxwvutsrqponmlk",
19907        ] {
19908            assert_libsais64_bwt_matches_c(text);
19909        }
19910    }
19911
19912    #[test]
19913    fn public_libsais64_gsa_matches_upstream_c() {
19914        for text in [
19915            b"\0".as_slice(),
19916            b"banana\0",
19917            b"ban\0ana\0",
19918            b"miss\0issippi\0",
19919            b"a\0a\0a\0",
19920        ] {
19921            assert_libsais64_gsa_matches_c(text);
19922        }
19923    }
19924
19925    #[test]
19926    fn public_libsais64_long_matches_upstream_c() {
19927        for (text, k) in [
19928            (&[][..], 0),
19929            (&[0][..], 1),
19930            (&[1, 2, 1, 0][..], 3),
19931            (&[2, 1, 2, 1, 0][..], 3),
19932            (&[3, 3, 3, 2, 1, 0][..], 4),
19933        ] {
19934            assert_libsais64_long_matches_c(text, k);
19935        }
19936    }
19937
19938    #[test]
19939    fn public_libsais64_plcp_lcp_matches_upstream_c() {
19940        for text in [
19941            b"".as_slice(),
19942            b"a",
19943            b"banana",
19944            b"mississippi",
19945            b"abracadabra",
19946            b"AAAAAAAAAAAAAAAA",
19947            b"zyxwvutsrqponmlk",
19948        ] {
19949            assert_libsais64_plcp_lcp_matches_c(text);
19950        }
19951    }
19952
19953    #[test]
19954    fn public_libsais64_plcp_gsa_matches_upstream_c() {
19955        for text in [
19956            b"\0".as_slice(),
19957            b"banana\0",
19958            b"ban\0ana\0",
19959            b"miss\0issippi\0",
19960            b"a\0a\0a\0",
19961        ] {
19962            assert_libsais64_plcp_gsa_matches_c(text);
19963        }
19964    }
19965
19966    #[test]
19967    fn libsais64_bwt_and_unbwt_round_trip_small_text() {
19968        let t = b"banana";
19969        let mut bwt = vec![0u8; t.len()];
19970        let mut a = vec![0; t.len()];
19971
19972        let primary = libsais64_bwt(t, &mut bwt, &mut a, 0, None);
19973        assert!(primary > 0);
19974
19975        let mut restored = vec![0u8; t.len()];
19976        let result = libsais64_unbwt(&bwt, &mut restored, &mut a, None, primary);
19977
19978        assert_eq!(result, 0);
19979        assert_eq!(restored, t);
19980    }
19981
19982    #[test]
19983    fn libsais64_bwt_aux_and_unbwt_aux_round_trip_small_text() {
19984        let t = b"mississippi";
19985        let mut bwt = vec![0u8; t.len()];
19986        let mut a = vec![0; t.len()];
19987        let mut samples = vec![0; 4];
19988
19989        let result = libsais64_bwt_aux(t, &mut bwt, &mut a, 0, None, 4, &mut samples);
19990        assert_eq!(result, 0);
19991
19992        let mut restored = vec![0u8; t.len()];
19993        let result = libsais64_unbwt_aux(&bwt, &mut restored, &mut a, None, 4, &samples);
19994
19995        assert_eq!(result, 0);
19996        assert_eq!(restored, t);
19997    }
19998
19999    #[test]
20000    fn libsais64_bwt_aux_and_unbwt_aux_omp_round_trip_small_text() {
20001        let t = b"mississippi";
20002        let mut bwt = vec![0u8; t.len()];
20003        let mut a = vec![0; t.len()];
20004        let mut samples = vec![0; 4];
20005
20006        let result = libsais64_bwt_aux(t, &mut bwt, &mut a, 0, None, 4, &mut samples);
20007        assert_eq!(result, 0);
20008
20009        let mut restored = vec![0u8; t.len()];
20010        let result = libsais64_unbwt_aux_omp(&bwt, &mut restored, &mut a, None, 4, &samples, 2);
20011
20012        assert_eq!(result, 0);
20013        assert_eq!(restored, t);
20014    }
20015
20016    #[test]
20017    fn libsais64_real_world_round_trip_on_upstream_readme() {
20018        let t = include_bytes!("../libsais/README.md");
20019        let mut bwt = vec![0u8; t.len()];
20020        let mut a = vec![0; t.len()];
20021
20022        let primary = libsais64_bwt(t, &mut bwt, &mut a, 0, None);
20023        assert!(primary > 0);
20024
20025        let mut restored = vec![0u8; t.len()];
20026        let result = libsais64_unbwt(&bwt, &mut restored, &mut a, None, primary);
20027
20028        assert_eq!(result, 0);
20029        assert_eq!(restored, t);
20030    }
20031
20032    #[test]
20033    fn libsais64_real_world_aux_omp_round_trip_on_upstream_c_source() {
20034        let t = include_bytes!("../libsais/src/libsais.c");
20035        let mut bwt = vec![0u8; t.len()];
20036        let mut a = vec![0; t.len()];
20037        let r = 128;
20038        let mut samples = vec![0; (t.len() - 1) / usize::try_from(r).expect("fits") + 1];
20039
20040        let result = libsais64_bwt_aux(t, &mut bwt, &mut a, 0, None, r, &mut samples);
20041        assert_eq!(result, 0);
20042
20043        let mut restored = vec![0u8; t.len()];
20044        let result = libsais64_unbwt_aux_omp(&bwt, &mut restored, &mut a, None, r, &samples, 2);
20045
20046        assert_eq!(result, 0);
20047        assert_eq!(restored, t);
20048    }
20049
20050    #[test]
20051    fn libsais64_bwt_aux_rejects_undersized_sampling_array() {
20052        let t = b"upstream source text";
20053        let mut bwt = vec![0u8; t.len()];
20054        let mut a = vec![0; t.len()];
20055        let mut samples = vec![0; 1];
20056
20057        let result = libsais64_bwt_aux(t, &mut bwt, &mut a, 0, None, 2, &mut samples);
20058
20059        assert_eq!(result, -1);
20060
20061        let result = libsais64_bwt_aux(t, &mut bwt, &mut a, 0, None, 0, &mut samples);
20062
20063        assert_eq!(result, -1);
20064    }
20065
20066    #[test]
20067    fn libsais64_bwt_aux_omp_rejects_invalid_sampling_rate_without_panicking() {
20068        let t = b"upstream source text";
20069        let mut bwt = vec![0u8; t.len()];
20070        let mut a = vec![0; t.len()];
20071        let mut samples = vec![0; 4];
20072
20073        let result = libsais64_bwt_aux_omp(t, &mut bwt, &mut a, 0, None, 0, &mut samples, 2);
20074
20075        assert_eq!(result, -1);
20076    }
20077
20078    #[test]
20079    fn public_libsais64_empty_and_singleton_inputs_follow_public_contract() {
20080        let mut empty_sa = Vec::new();
20081        let mut empty_freq = vec![-1; ALPHABET_SIZE];
20082        assert_eq!(libsais64(b"", &mut empty_sa, 0, Some(&mut empty_freq)), 0);
20083        assert!(empty_freq.iter().all(|&value| value == 0));
20084
20085        empty_freq.fill(-1);
20086        assert_eq!(
20087            libsais64_omp(b"", &mut empty_sa, 0, Some(&mut empty_freq), 2),
20088            0
20089        );
20090        assert!(empty_freq.iter().all(|&value| value == 0));
20091
20092        empty_freq.fill(-1);
20093        assert_eq!(
20094            libsais64_gsa(b"", &mut empty_sa, 0, Some(&mut empty_freq)),
20095            0
20096        );
20097        assert!(empty_freq.iter().all(|&value| value == 0));
20098
20099        let mut empty_bwt = Vec::new();
20100        let mut empty_work = Vec::new();
20101        empty_freq.fill(-1);
20102        assert_eq!(
20103            libsais64_bwt(
20104                b"",
20105                &mut empty_bwt,
20106                &mut empty_work,
20107                0,
20108                Some(&mut empty_freq)
20109            ),
20110            0
20111        );
20112        assert!(empty_freq.iter().all(|&value| value == 0));
20113
20114        let mut empty_aux = vec![-1];
20115        empty_freq.fill(-1);
20116        assert_eq!(
20117            libsais64_bwt_aux(
20118                b"",
20119                &mut empty_bwt,
20120                &mut empty_work,
20121                0,
20122                Some(&mut empty_freq),
20123                2,
20124                &mut empty_aux
20125            ),
20126            0
20127        );
20128        assert_eq!(empty_aux[0], 0);
20129        assert!(empty_freq.iter().all(|&value| value == 0));
20130
20131        let text = b"z";
20132        let mut sa = vec![-1; 1];
20133        let mut freq = vec![-1; ALPHABET_SIZE];
20134        assert_eq!(libsais64(text, &mut sa, 0, Some(&mut freq)), 0);
20135        assert_eq!(sa, vec![0]);
20136        assert_eq!(freq[b'z' as usize], 1);
20137        assert_eq!(freq.iter().sum::<SaSint>(), 1);
20138
20139        sa.fill(-1);
20140        freq.fill(-1);
20141        let mut ctx = create_ctx().expect("context");
20142        assert_eq!(
20143            libsais64_ctx(&mut ctx, text, &mut sa, 0, Some(&mut freq)),
20144            0
20145        );
20146        assert_eq!(sa, vec![0]);
20147        assert_eq!(freq[b'z' as usize], 1);
20148        assert_eq!(freq.iter().sum::<SaSint>(), 1);
20149
20150        sa.fill(-1);
20151        freq.fill(-1);
20152        assert_eq!(libsais64_omp(text, &mut sa, 0, Some(&mut freq), 2), 0);
20153        assert_eq!(sa, vec![0]);
20154        assert_eq!(freq[b'z' as usize], 1);
20155        assert_eq!(freq.iter().sum::<SaSint>(), 1);
20156
20157        let mut gsa_sa = vec![-1; 1];
20158        let mut gsa_freq = vec![-1; ALPHABET_SIZE];
20159        assert_eq!(libsais64_gsa(b"\0", &mut gsa_sa, 0, Some(&mut gsa_freq)), 0);
20160        assert_eq!(gsa_sa, vec![0]);
20161        assert_eq!(gsa_freq[0], 1);
20162        assert_eq!(gsa_freq.iter().sum::<SaSint>(), 1);
20163
20164        let mut bwt = vec![0; 1];
20165        let mut work = vec![0; 1];
20166        freq.fill(-1);
20167        assert_eq!(
20168            libsais64_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq)),
20169            1
20170        );
20171        assert_eq!(bwt, text);
20172        assert_eq!(freq[b'z' as usize], 1);
20173        assert_eq!(freq.iter().sum::<SaSint>(), 1);
20174
20175        let mut aux = vec![-1];
20176        bwt.fill(0);
20177        work.fill(0);
20178        freq.fill(-1);
20179        assert_eq!(
20180            libsais64_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), 2, &mut aux),
20181            0
20182        );
20183        assert_eq!(bwt, text);
20184        assert_eq!(aux[0], 1);
20185        assert_eq!(freq[b'z' as usize], 1);
20186        assert_eq!(freq.iter().sum::<SaSint>(), 1);
20187    }
20188
20189    #[test]
20190    fn public_libsais64_rejects_invalid_aux_sampling_without_panicking() {
20191        let text = b"banana";
20192        let mut u = vec![0; text.len()];
20193        let mut a = vec![0; text.len() + 1];
20194        let mut aux = vec![0; 2];
20195
20196        assert_eq!(
20197            libsais64_bwt_aux(text, &mut u, &mut a, 0, None, 0, &mut aux),
20198            -1
20199        );
20200        assert_eq!(
20201            libsais64_bwt_aux(text, &mut u, &mut a, 0, None, 3, &mut aux),
20202            -1
20203        );
20204        assert_eq!(libsais64_unbwt_aux(text, &mut u, &mut a, None, 0, &aux), -1);
20205        assert_eq!(
20206            libsais64_unbwt_aux_omp(text, &mut u, &mut a, None, 0, &aux, 1),
20207            -1
20208        );
20209    }
20210
20211    #[test]
20212    fn libsais64_unbwt_aux_rejects_invalid_sampling_range() {
20213        let t = b"abc";
20214        let mut u = vec![0u8; t.len()];
20215        let mut a = vec![0; t.len()];
20216
20217        let result = libsais64_unbwt_aux(t, &mut u, &mut a, None, 2, &[0, 4]);
20218
20219        assert_eq!(result, -1);
20220
20221        assert_eq!(libsais64_unbwt_aux(t, &mut u, &mut a, None, 0, &[1]), -1);
20222
20223        let mut ctx = unbwt_create_ctx().expect("context");
20224        assert_eq!(
20225            libsais64_unbwt_aux_ctx(&mut ctx, t, &mut u, &mut a, None, 0, &[1]),
20226            -1
20227        );
20228        assert_eq!(
20229            libsais64_unbwt_aux_omp(t, &mut u, &mut a, None, 0, &[1], 2),
20230            -1
20231        );
20232    }
20233
20234    #[test]
20235    fn public_libsais64_omp_rejects_undersized_suffix_arrays() {
20236        let text = b"banana";
20237        let mut short_sa = vec![0; text.len() - 1];
20238        let mut int_text = vec![1, 2, 1, 0];
20239        let mut short_int_sa = vec![0; int_text.len() - 1];
20240
20241        assert_eq!(libsais64_omp(text, &mut short_sa, 0, None, 1), -1);
20242        assert_eq!(
20243            libsais64_gsa_omp(b"banana\0", &mut short_sa, 0, None, 1),
20244            -1
20245        );
20246        assert_eq!(
20247            libsais64_int_omp(&mut int_text, &mut short_int_sa, 3, 0, 1),
20248            -1
20249        );
20250    }
20251
20252    #[test]
20253    #[ignore = "large real-data regression; requires local yeast FASTA fixture"]
20254    fn public_libsais64_omp_handles_minibwa_yeast_two_strand_index_input() {
20255        let path = "/data/henriksson/github/claude/star/.tmp/yeast_conformance/Saccharomyces_cerevisiae.R64-1-1.dna.toplevel.fa";
20256        let Ok(fasta) = std::fs::read_to_string(path) else {
20257            eprintln!("skipping missing fixture: {path}");
20258            return;
20259        };
20260        let mut forward = Vec::new();
20261        for line in fasta.lines() {
20262            if line.starts_with('>') {
20263                continue;
20264            }
20265            forward.extend(line.as_bytes().iter().filter_map(|&c| match c {
20266                b'A' | b'a' => Some(0),
20267                b'C' | b'c' => Some(1),
20268                b'G' | b'g' => Some(2),
20269                b'T' | b't' => Some(3),
20270                _ => None,
20271            }));
20272        }
20273        assert!(
20274            forward.len() > 12_000_000,
20275            "fixture should exercise the large-input 64-bit path"
20276        );
20277
20278        let mut text = Vec::with_capacity(forward.len() * 2);
20279        text.extend_from_slice(&forward);
20280        text.extend(forward.iter().rev().map(|&c| 3 - c));
20281
20282        const FS: SaSint = 10_000;
20283        let mut sa = vec![0; text.len() + FS as usize];
20284        assert_eq!(libsais64_omp(&text, &mut sa, FS, None, 4), 0);
20285    }
20286
20287    #[test]
20288    #[ignore = "large real-data regression; requires local minibwa yeast fixture"]
20289    fn public_libsais64_omp_matches_plain_on_minibwa_yeast_two_strand_index_input() {
20290        let l2b_path =
20291            "/data/henriksson/github/claude/minibwa/.tmp/compare-yeast-now/ref.split.rust.l2b";
20292        let fasta_path =
20293            "/data/henriksson/github/claude/minibwa/.tmp/large-real/yeast/ref.sanitized.fa";
20294        let forward = if let Ok(bytes) = std::fs::read(l2b_path) {
20295            assert!(bytes.len() >= 64, "short l2b fixture: {l2b_path}");
20296            assert_eq!(&bytes[..4], b"L2B\x01", "bad l2b magic in {l2b_path}");
20297            let n_ctg = u64::from_le_bytes(bytes[8..16].try_into().unwrap()) as usize;
20298            let tot_len = u64::from_le_bytes(bytes[16..24].try_into().unwrap()) as usize;
20299            let n_ambi = u64::from_le_bytes(bytes[24..32].try_into().unwrap()) as usize;
20300            let n_mask = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
20301            let n_pac = u64::from_le_bytes(bytes[56..64].try_into().unwrap()) as usize;
20302            let pac_start = 64 + 8 * n_ctg + 16 * n_ambi + 16 * n_mask;
20303            assert!(
20304                bytes.len() >= pac_start + 8 * n_pac,
20305                "truncated l2b pac in {l2b_path}"
20306            );
20307            let mut pac = Vec::with_capacity(n_pac);
20308            for chunk in bytes[pac_start..pac_start + 8 * n_pac].chunks_exact(8) {
20309                pac.push(u64::from_le_bytes(chunk.try_into().unwrap()));
20310            }
20311            (0..tot_len)
20312                .map(|i| ((pac[i >> 5] >> ((i & 31) << 1)) & 3) as u8)
20313                .collect::<Vec<_>>()
20314        } else if let Ok(fasta) = std::fs::read_to_string(fasta_path) {
20315            let mut rng = 11u64;
20316            let mut forward = Vec::new();
20317            for line in fasta.lines() {
20318                if line.starts_with('>') {
20319                    continue;
20320                }
20321                forward.extend(line.bytes().map(|b| {
20322                    let mut c = match b {
20323                        b'A' | b'a' => 0,
20324                        b'C' | b'c' => 1,
20325                        b'G' | b'g' => 2,
20326                        b'T' | b't' | b'U' | b'u' => 3,
20327                        _ => {
20328                            rng = rng.wrapping_add(0x9e3779b97f4a7c15);
20329                            let mut z = rng;
20330                            z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
20331                            z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
20332                            4 | ((z ^ (z >> 31)) & 3) as u8
20333                        }
20334                    };
20335                    if b < b'A' || b > b'Z' {
20336                        c |= 1 << 3;
20337                    }
20338                    c & 3
20339                }));
20340            }
20341            forward
20342        } else {
20343            eprintln!("skipping missing fixtures: {l2b_path} and {fasta_path}");
20344            return;
20345        };
20346        assert!(
20347            forward.len() > 12_000_000,
20348            "fixture should exercise the minibwa yeast index workload"
20349        );
20350
20351        let mut text = Vec::with_capacity(forward.len() * 2);
20352        text.extend_from_slice(&forward);
20353        text.extend(forward.iter().rev().map(|&c| 3 - c));
20354
20355        const FS: SaSint = 10_000;
20356        let mut plain_sa = vec![0; text.len() + FS as usize + 1];
20357        let mut omp_sa = vec![0; text.len() + FS as usize + 1];
20358        assert_eq!(libsais64(&text, &mut plain_sa[1..], FS, None), 0);
20359        assert_eq!(libsais64_omp(&text, &mut omp_sa[1..], FS, None, 4), 0);
20360        plain_sa[0] = text.len() as SaSint;
20361        omp_sa[0] = text.len() as SaSint;
20362        if let Some(i) = plain_sa[..=text.len()]
20363            .iter()
20364            .zip(&omp_sa[..=text.len()])
20365            .position(|(plain, omp)| plain != omp)
20366        {
20367            panic!(
20368                "first suffix-array diff at {i}: plain={} omp={}",
20369                plain_sa[i], omp_sa[i]
20370            );
20371        }
20372    }
20373
20374    #[test]
20375    #[ignore = "large real-data regression; requires local minibwa or STAR yeast FASTA fixture"]
20376    fn direct_libsais64_main_handles_minibwa_yeast_two_strand_index_input() {
20377        let minibwa_path =
20378            "/data/henriksson/github/claude/minibwa/.tmp/large-real/yeast/ref.sanitized.fa";
20379        let star_path = "/data/henriksson/github/claude/star/.tmp/yeast_conformance/Saccharomyces_cerevisiae.R64-1-1.dna.toplevel.fa";
20380        let (path, fasta) = if let Ok(fasta) = std::fs::read_to_string(minibwa_path) {
20381            (minibwa_path, fasta)
20382        } else if let Ok(fasta) = std::fs::read_to_string(star_path) {
20383            (star_path, fasta)
20384        } else {
20385            eprintln!("skipping missing fixtures: {minibwa_path} and {star_path}");
20386            return;
20387        };
20388        let mut forward = Vec::new();
20389        for line in fasta.lines() {
20390            if line.starts_with('>') {
20391                continue;
20392            }
20393            forward.extend(line.as_bytes().iter().filter_map(|&c| match c {
20394                b'A' | b'a' => Some(0),
20395                b'C' | b'c' => Some(1),
20396                b'G' | b'g' => Some(2),
20397                b'T' | b't' => Some(3),
20398                _ => None,
20399            }));
20400        }
20401        assert!(
20402            forward.len() > 12_000_000,
20403            "fixture {path} should exercise the minibwa yeast index workload"
20404        );
20405
20406        let mut text = Vec::with_capacity(forward.len() * 2);
20407        text.extend_from_slice(&forward);
20408        text.extend(forward.iter().rev().map(|&c| 3 - c));
20409
20410        const FS: SaSint = 10_000;
20411        let mut sa = vec![0; text.len() + FS as usize];
20412        assert_eq!(
20413            libsais64_main(&text, &mut sa, LIBSAIS_FLAGS_NONE, 0, None, FS, None, 1),
20414            0
20415        );
20416    }
20417
20418    #[test]
20419    #[ignore = "large real-data regression; requires local yeast FASTA fixture"]
20420    fn public_libsais64_matches_c_on_minibwa_yeast_two_strand_index_input() {
20421        let path = "/data/henriksson/github/claude/star/.tmp/yeast_conformance/Saccharomyces_cerevisiae.R64-1-1.dna.toplevel.fa";
20422        let Ok(fasta) = std::fs::read_to_string(path) else {
20423            eprintln!("skipping missing fixture: {path}");
20424            return;
20425        };
20426        let mut forward = Vec::new();
20427        for line in fasta.lines() {
20428            if line.starts_with('>') {
20429                continue;
20430            }
20431            forward.extend(line.as_bytes().iter().filter_map(|&c| match c {
20432                b'A' | b'a' => Some(0),
20433                b'C' | b'c' => Some(1),
20434                b'G' | b'g' => Some(2),
20435                b'T' | b't' => Some(3),
20436                _ => None,
20437            }));
20438        }
20439        let mut text = Vec::with_capacity(forward.len() * 2);
20440        text.extend_from_slice(&forward);
20441        text.extend(forward.iter().rev().map(|&c| 3 - c));
20442
20443        const FS: SaSint = 10_000;
20444        let mut rust_sa = vec![0; text.len() + FS as usize];
20445        let mut c_sa = vec![0; text.len() + FS as usize];
20446        let rust_rc = libsais64(&text, &mut rust_sa, FS, None);
20447        let c_rc = unsafe {
20448            probe_public_libsais64(text.as_ptr(), c_sa.as_mut_ptr(), text.len() as SaSint, FS)
20449        };
20450        assert_eq!(rust_rc, c_rc);
20451        if let Some(i) = rust_sa[..text.len()]
20452            .iter()
20453            .zip(&c_sa[..text.len()])
20454            .position(|(r, c)| r != c)
20455        {
20456            panic!(
20457                "first suffix-array diff at {i}: rust={} c={}",
20458                rust_sa[i], c_sa[i]
20459            );
20460        }
20461    }
20462
20463    #[test]
20464    #[ignore = "large real-data regression; requires local yeast FASTA fixture"]
20465    fn public_libsais64_omp_matches_c_on_minibwa_yeast_two_strand_index_input() {
20466        let path = "/data/henriksson/github/claude/star/.tmp/yeast_conformance/Saccharomyces_cerevisiae.R64-1-1.dna.toplevel.fa";
20467        let Ok(fasta) = std::fs::read_to_string(path) else {
20468            eprintln!("skipping missing fixture: {path}");
20469            return;
20470        };
20471        let mut forward = Vec::new();
20472        for line in fasta.lines() {
20473            if line.starts_with('>') {
20474                continue;
20475            }
20476            forward.extend(line.as_bytes().iter().filter_map(|&c| match c {
20477                b'A' | b'a' => Some(0),
20478                b'C' | b'c' => Some(1),
20479                b'G' | b'g' => Some(2),
20480                b'T' | b't' => Some(3),
20481                _ => None,
20482            }));
20483        }
20484        let mut text = Vec::with_capacity(forward.len() * 2);
20485        text.extend_from_slice(&forward);
20486        text.extend(forward.iter().rev().map(|&c| 3 - c));
20487
20488        const FS: SaSint = 10_000;
20489        let mut rust_sa = vec![0; text.len() + FS as usize];
20490        let mut c_sa = vec![0; text.len() + FS as usize];
20491        let rust_rc = libsais64_omp(&text, &mut rust_sa, FS, None, 4);
20492        let c_rc = unsafe {
20493            probe_public_libsais64(text.as_ptr(), c_sa.as_mut_ptr(), text.len() as SaSint, FS)
20494        };
20495        assert_eq!(rust_rc, c_rc);
20496        if let Some(i) = rust_sa[..text.len()]
20497            .iter()
20498            .zip(&c_sa[..text.len()])
20499            .position(|(r, c)| r != c)
20500        {
20501            panic!(
20502                "first omp suffix-array diff at {i}: rust={} c={}",
20503                rust_sa[i], c_sa[i]
20504            );
20505        }
20506    }
20507
20508    #[test]
20509    fn public_libsais64_ctx_rejects_invalid_public_arguments() {
20510        let text = b"banana";
20511        let mut ctx = create_ctx().unwrap();
20512        let mut short_sa = vec![0; text.len() - 1];
20513        let mut full_sa = vec![0; text.len()];
20514        let mut short_freq = vec![0; ALPHABET_SIZE - 1];
20515        let mut short_u = vec![0; text.len() - 1];
20516        let mut full_u = vec![0; text.len()];
20517        let mut short_a = vec![0; text.len() - 1];
20518        let mut full_a = vec![0; text.len()];
20519        let mut aux = vec![0; 2];
20520
20521        assert_eq!(libsais64_ctx(&mut ctx, text, &mut short_sa, 0, None), -1);
20522        assert_eq!(
20523            libsais64_ctx(&mut ctx, text, &mut full_sa, 0, Some(&mut short_freq)),
20524            -1
20525        );
20526        assert_eq!(
20527            libsais64_gsa_ctx(&mut ctx, b"banana", &mut full_sa, 0, None),
20528            -1
20529        );
20530        assert_eq!(
20531            libsais64_gsa_ctx(&mut ctx, b"banana\0", &mut short_sa, 0, None),
20532            -1
20533        );
20534        assert_eq!(
20535            libsais64_bwt_ctx(&mut ctx, text, &mut short_u, &mut full_a, 0, None),
20536            -1
20537        );
20538        assert_eq!(
20539            libsais64_bwt_ctx(&mut ctx, text, &mut full_u, &mut short_a, 0, None),
20540            -1
20541        );
20542        assert_eq!(
20543            libsais64_bwt_ctx(
20544                &mut ctx,
20545                text,
20546                &mut full_u,
20547                &mut full_a,
20548                0,
20549                Some(&mut short_freq)
20550            ),
20551            -1
20552        );
20553        assert_eq!(
20554            libsais64_bwt_aux_ctx(
20555                &mut ctx,
20556                text,
20557                &mut full_u,
20558                &mut full_a,
20559                0,
20560                None,
20561                0,
20562                &mut aux
20563            ),
20564            -1
20565        );
20566        assert_eq!(
20567            libsais64_bwt_aux_ctx(
20568                &mut ctx,
20569                text,
20570                &mut full_u,
20571                &mut full_a,
20572                0,
20573                None,
20574                3,
20575                &mut aux
20576            ),
20577            -1
20578        );
20579        assert_eq!(
20580            libsais64_bwt_aux_ctx(
20581                &mut ctx,
20582                text,
20583                &mut full_u,
20584                &mut full_a,
20585                0,
20586                None,
20587                4,
20588                &mut []
20589            ),
20590            -1
20591        );
20592
20593        let mut missing_thread_state_ctx = Context {
20594            buckets: vec![0; 8 * ALPHABET_SIZE],
20595            thread_state: None,
20596            threads: 2,
20597        };
20598        assert_eq!(
20599            libsais64_ctx(&mut missing_thread_state_ctx, text, &mut full_sa, 0, None),
20600            -2
20601        );
20602
20603        let mut zero_thread_ctx = Context {
20604            buckets: vec![0; 8 * ALPHABET_SIZE],
20605            thread_state: None,
20606            threads: 0,
20607        };
20608        assert_eq!(
20609            libsais64_ctx(&mut zero_thread_ctx, text, &mut full_sa, 0, None),
20610            -2
20611        );
20612
20613        let mut short_thread_state_ctx = create_ctx_main(2).expect("context");
20614        short_thread_state_ctx
20615            .thread_state
20616            .as_mut()
20617            .expect("thread state")
20618            .truncate(1);
20619        assert_eq!(
20620            libsais64_ctx(&mut short_thread_state_ctx, text, &mut full_sa, 0, None),
20621            -2
20622        );
20623    }
20624
20625    #[test]
20626    fn public_libsais64_unbwt_ctx_rejects_invalid_public_arguments() {
20627        let text = b"banana";
20628        let mut bwt = vec![0; text.len()];
20629        let mut work = vec![0; text.len()];
20630        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, None);
20631        let mut ctx = unbwt_create_ctx().unwrap();
20632
20633        let mut short_u = vec![0; text.len() - 1];
20634        let mut full_u = vec![0; text.len()];
20635        let mut short_a = vec![0; text.len() - 1];
20636        let mut full_a = vec![0; text.len()];
20637        let short_freq = vec![0; ALPHABET_SIZE - 1];
20638        let good_aux = vec![primary, 4];
20639
20640        assert_eq!(
20641            libsais64_unbwt_ctx(&mut ctx, &bwt, &mut short_u, &mut full_a, None, primary),
20642            -1
20643        );
20644        assert_eq!(
20645            libsais64_unbwt_ctx(&mut ctx, &bwt, &mut full_u, &mut short_a, None, primary),
20646            -1
20647        );
20648        assert_eq!(
20649            libsais64_unbwt_ctx(
20650                &mut ctx,
20651                &bwt,
20652                &mut full_u,
20653                &mut full_a,
20654                Some(&short_freq),
20655                primary
20656            ),
20657            -1
20658        );
20659        assert_eq!(
20660            libsais64_unbwt_ctx(&mut ctx, &bwt, &mut full_u, &mut full_a, None, 0),
20661            -1
20662        );
20663        assert_eq!(
20664            libsais64_unbwt_aux_ctx(&mut ctx, &bwt, &mut full_u, &mut full_a, None, 3, &good_aux),
20665            -1
20666        );
20667        assert_eq!(
20668            libsais64_unbwt_aux_ctx(
20669                &mut ctx,
20670                &bwt,
20671                &mut full_u,
20672                &mut full_a,
20673                None,
20674                4,
20675                &[primary]
20676            ),
20677            -1
20678        );
20679
20680        let mut malformed_ctx = UnbwtContext {
20681            bucket2: Vec::new(),
20682            fastbits: Vec::new(),
20683            buckets: None,
20684            threads: 1,
20685        };
20686        assert_eq!(
20687            libsais64_unbwt_ctx(
20688                &mut malformed_ctx,
20689                &bwt,
20690                &mut full_u,
20691                &mut full_a,
20692                None,
20693                primary
20694            ),
20695            -2
20696        );
20697
20698        let mut missing_parallel_buckets_ctx = UnbwtContext {
20699            bucket2: vec![0; ALPHABET_SIZE * ALPHABET_SIZE],
20700            fastbits: vec![0; 1 + (1 << UNBWT_FASTBITS)],
20701            buckets: None,
20702            threads: 2,
20703        };
20704        assert_eq!(
20705            libsais64_unbwt_ctx(
20706                &mut missing_parallel_buckets_ctx,
20707                &bwt,
20708                &mut full_u,
20709                &mut full_a,
20710                None,
20711                primary
20712            ),
20713            -2
20714        );
20715    }
20716
20717    #[test]
20718    fn public_libsais64_lcp_helpers_reject_invalid_suffix_entries() {
20719        let text = b"banana";
20720        let mut plcp = vec![0; text.len()];
20721        let mut lcp = vec![0; text.len()];
20722        let int_text = vec![1, 2, 1, 0];
20723        let mut int_plcp = vec![0; int_text.len()];
20724
20725        assert_eq!(libsais64_plcp(text, &[0, 1, -1, 3, 4, 5], &mut plcp), -1);
20726        assert_eq!(libsais64_plcp(text, &[0, 1, 2, 3, 4, 6], &mut plcp), -1);
20727        assert_eq!(libsais64_lcp(&plcp, &[0, 1, -1, 3, 4, 5], &mut lcp), -1);
20728        assert_eq!(libsais64_lcp(&plcp, &[0, 1, 2, 3, 4, 6], &mut lcp), -1);
20729        assert_eq!(
20730            libsais64_plcp_int(&int_text, &[0, 1, -1, 3], &mut int_plcp),
20731            -1
20732        );
20733        assert_eq!(
20734            libsais64_plcp_int_omp(&int_text, &[0, 1, 2, 4], &mut int_plcp, 1),
20735            -1
20736        );
20737    }
20738
20739    #[test]
20740    fn public_libsais64_context_wrappers_match_direct_calls() {
20741        let text = b"banana";
20742        let gsa_text = b"ban\0ana\0";
20743        let mut ctx = create_ctx().unwrap();
20744
20745        let mut direct_sa = vec![0; text.len()];
20746        let mut ctx_sa = vec![0; text.len()];
20747        assert_eq!(libsais64(text, &mut direct_sa, 0, None), 0);
20748        assert_eq!(libsais64_ctx(&mut ctx, text, &mut ctx_sa, 0, None), 0);
20749        assert_eq!(ctx_sa, direct_sa);
20750
20751        let mut direct_gsa = vec![0; gsa_text.len()];
20752        let mut ctx_gsa = vec![0; gsa_text.len()];
20753        assert_eq!(libsais64_gsa(gsa_text, &mut direct_gsa, 0, None), 0);
20754        assert_eq!(
20755            libsais64_gsa_ctx(&mut ctx, gsa_text, &mut ctx_gsa, 0, None),
20756            0
20757        );
20758        assert_eq!(ctx_gsa, direct_gsa);
20759
20760        let mut direct_bwt = vec![0; text.len()];
20761        let mut direct_work = vec![0; text.len()];
20762        let mut ctx_bwt = vec![0; text.len()];
20763        let mut ctx_work = vec![0; text.len()];
20764        assert_eq!(
20765            libsais64_bwt(text, &mut direct_bwt, &mut direct_work, 0, None),
20766            libsais64_bwt_ctx(&mut ctx, text, &mut ctx_bwt, &mut ctx_work, 0, None)
20767        );
20768        assert_eq!(ctx_bwt, direct_bwt);
20769
20770        let mut direct_aux = vec![0; 2];
20771        let mut ctx_aux = vec![0; 2];
20772        assert_eq!(
20773            libsais64_bwt_aux(
20774                text,
20775                &mut direct_bwt,
20776                &mut direct_work,
20777                0,
20778                None,
20779                4,
20780                &mut direct_aux
20781            ),
20782            libsais64_bwt_aux_ctx(
20783                &mut ctx,
20784                text,
20785                &mut ctx_bwt,
20786                &mut ctx_work,
20787                0,
20788                None,
20789                4,
20790                &mut ctx_aux
20791            )
20792        );
20793        assert_eq!(ctx_bwt, direct_bwt);
20794        assert_eq!(ctx_aux, direct_aux);
20795    }
20796
20797    #[test]
20798    fn libsais64_ctx_matches_plain_entry_point_for_small_text() {
20799        let t = b"mississippi";
20800        let mut sa_plain = vec![0; t.len()];
20801        let mut sa_ctx = vec![0; t.len()];
20802        let plain = libsais64(t, &mut sa_plain, 0, None);
20803
20804        let mut ctx = create_ctx().expect("context");
20805        let with_ctx = libsais64_ctx(&mut ctx, t, &mut sa_ctx, 0, None);
20806
20807        assert_eq!(plain, 0);
20808        assert_eq!(with_ctx, 0);
20809        assert_eq!(sa_ctx, sa_plain);
20810    }
20811
20812    #[test]
20813    fn public_libsais64_unbwt_context_wrappers_match_direct_calls() {
20814        let text = b"banana";
20815        let mut bwt = vec![0; text.len()];
20816        let mut work = vec![0; text.len()];
20817        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, None);
20818        let mut ctx = unbwt_create_ctx().unwrap();
20819
20820        let mut direct = vec![0; text.len()];
20821        let mut direct_work = vec![0; text.len() + 1];
20822        let mut via_ctx = vec![0; text.len()];
20823        let mut ctx_work = vec![0; text.len() + 1];
20824        assert_eq!(
20825            libsais64_unbwt(&bwt, &mut direct, &mut direct_work, None, primary),
20826            0
20827        );
20828        assert_eq!(
20829            libsais64_unbwt_ctx(&mut ctx, &bwt, &mut via_ctx, &mut ctx_work, None, primary),
20830            0
20831        );
20832        assert_eq!(via_ctx, direct);
20833
20834        let mut aux = vec![0; 2];
20835        assert_eq!(
20836            libsais64_bwt_aux(text, &mut bwt, &mut work, 0, None, 4, &mut aux),
20837            0
20838        );
20839        assert_eq!(
20840            libsais64_unbwt_aux(&bwt, &mut direct, &mut direct_work, None, 4, &aux),
20841            0
20842        );
20843        assert_eq!(
20844            libsais64_unbwt_aux_ctx(&mut ctx, &bwt, &mut via_ctx, &mut ctx_work, None, 4, &aux),
20845            0
20846        );
20847        assert_eq!(via_ctx, direct);
20848    }
20849
20850    #[test]
20851    fn public_libsais64_ctx_frequency_wrappers_match_direct_calls() {
20852        let text = b"banana";
20853        let gsa_text = b"ban\0ana\0";
20854        let mut ctx = create_ctx().unwrap();
20855
20856        let mut direct_sa = vec![0; text.len()];
20857        let mut ctx_sa = vec![0; text.len()];
20858        let mut direct_freq = vec![-1; ALPHABET_SIZE];
20859        let mut ctx_freq = vec![-1; ALPHABET_SIZE];
20860        assert_eq!(
20861            libsais64(text, &mut direct_sa, 0, Some(&mut direct_freq)),
20862            0
20863        );
20864        assert_eq!(
20865            libsais64_ctx(&mut ctx, text, &mut ctx_sa, 0, Some(&mut ctx_freq)),
20866            0
20867        );
20868        assert_eq!(ctx_sa, direct_sa);
20869        assert_eq!(ctx_freq, direct_freq);
20870
20871        let mut direct_gsa = vec![0; gsa_text.len()];
20872        let mut ctx_gsa = vec![0; gsa_text.len()];
20873        direct_freq.fill(-1);
20874        ctx_freq.fill(-1);
20875        assert_eq!(
20876            libsais64_gsa(gsa_text, &mut direct_gsa, 0, Some(&mut direct_freq)),
20877            0
20878        );
20879        assert_eq!(
20880            libsais64_gsa_ctx(&mut ctx, gsa_text, &mut ctx_gsa, 0, Some(&mut ctx_freq)),
20881            0
20882        );
20883        assert_eq!(ctx_gsa, direct_gsa);
20884        assert_eq!(ctx_freq, direct_freq);
20885
20886        let mut direct_bwt = vec![0; text.len()];
20887        let mut direct_work = vec![0; text.len()];
20888        let mut ctx_bwt = vec![0; text.len()];
20889        let mut ctx_work = vec![0; text.len()];
20890        direct_freq.fill(-1);
20891        ctx_freq.fill(-1);
20892        assert_eq!(
20893            libsais64_bwt(
20894                text,
20895                &mut direct_bwt,
20896                &mut direct_work,
20897                0,
20898                Some(&mut direct_freq)
20899            ),
20900            libsais64_bwt_ctx(
20901                &mut ctx,
20902                text,
20903                &mut ctx_bwt,
20904                &mut ctx_work,
20905                0,
20906                Some(&mut ctx_freq)
20907            )
20908        );
20909        assert_eq!(ctx_bwt, direct_bwt);
20910        assert_eq!(ctx_freq, direct_freq);
20911
20912        let mut direct_aux = vec![0; 2];
20913        let mut ctx_aux = vec![0; 2];
20914        direct_freq.fill(-1);
20915        ctx_freq.fill(-1);
20916        assert_eq!(
20917            libsais64_bwt_aux(
20918                text,
20919                &mut direct_bwt,
20920                &mut direct_work,
20921                0,
20922                Some(&mut direct_freq),
20923                4,
20924                &mut direct_aux
20925            ),
20926            libsais64_bwt_aux_ctx(
20927                &mut ctx,
20928                text,
20929                &mut ctx_bwt,
20930                &mut ctx_work,
20931                0,
20932                Some(&mut ctx_freq),
20933                4,
20934                &mut ctx_aux
20935            )
20936        );
20937        assert_eq!(ctx_bwt, direct_bwt);
20938        assert_eq!(ctx_aux, direct_aux);
20939        assert_eq!(ctx_freq, direct_freq);
20940    }
20941
20942    #[test]
20943    fn public_libsais64_unbwt_ctx_frequency_wrappers_match_direct_calls() {
20944        let text = b"abracadabra";
20945        let mut freq = vec![0; ALPHABET_SIZE];
20946        let mut bwt = vec![0; text.len()];
20947        let mut work = vec![0; text.len()];
20948        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
20949        assert!(primary >= 0);
20950
20951        let mut ctx = unbwt_create_ctx().unwrap();
20952        let mut direct = vec![0; text.len()];
20953        let mut direct_work = vec![0; text.len() + 1];
20954        let mut via_ctx = vec![0; text.len()];
20955        let mut ctx_work = vec![0; text.len() + 1];
20956        assert_eq!(
20957            libsais64_unbwt(&bwt, &mut direct, &mut direct_work, Some(&freq), primary),
20958            libsais64_unbwt_ctx(
20959                &mut ctx,
20960                &bwt,
20961                &mut via_ctx,
20962                &mut ctx_work,
20963                Some(&freq),
20964                primary
20965            )
20966        );
20967        assert_eq!(via_ctx, direct);
20968        assert_eq!(via_ctx, text);
20969
20970        let mut aux = vec![0; (text.len() - 1) / 4 + 1];
20971        assert_eq!(
20972            libsais64_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), 4, &mut aux),
20973            0
20974        );
20975        direct.fill(0);
20976        direct_work.fill(0);
20977        via_ctx.fill(0);
20978        ctx_work.fill(0);
20979        assert_eq!(
20980            libsais64_unbwt_aux(&bwt, &mut direct, &mut direct_work, Some(&freq), 4, &aux),
20981            libsais64_unbwt_aux_ctx(
20982                &mut ctx,
20983                &bwt,
20984                &mut via_ctx,
20985                &mut ctx_work,
20986                Some(&freq),
20987                4,
20988                &aux
20989            )
20990        );
20991        assert_eq!(via_ctx, direct);
20992        assert_eq!(via_ctx, text);
20993    }
20994
20995    #[test]
20996    fn public_libsais64_omp_wrappers_match_direct_calls() {
20997        let text = b"banana";
20998        let gsa_text = b"ban\0ana\0";
20999
21000        let mut direct_sa = vec![0; text.len()];
21001        let mut omp_sa = vec![0; text.len()];
21002        assert_eq!(libsais64(text, &mut direct_sa, 0, None), 0);
21003        assert_eq!(libsais64_omp(text, &mut omp_sa, 0, None, 2), 0);
21004        assert_eq!(omp_sa, direct_sa);
21005        assert_eq!(libsais64_omp(text, &mut omp_sa, 0, None, -1), -1);
21006
21007        let mut direct_gsa = vec![0; gsa_text.len()];
21008        let mut omp_gsa = vec![0; gsa_text.len()];
21009        assert_eq!(libsais64_gsa(gsa_text, &mut direct_gsa, 0, None), 0);
21010        assert_eq!(libsais64_gsa_omp(gsa_text, &mut omp_gsa, 0, None, 2), 0);
21011        assert_eq!(omp_gsa, direct_gsa);
21012        assert_eq!(libsais64_gsa_omp(gsa_text, &mut omp_gsa, 0, None, -1), -1);
21013
21014        let int_text = vec![2, 1, 3, 1, 0];
21015        let mut direct_int_text = int_text.clone();
21016        let mut omp_int_text = int_text.clone();
21017        let mut direct_int_sa = vec![0; int_text.len()];
21018        let mut omp_int_sa = vec![0; int_text.len()];
21019        assert_eq!(
21020            libsais64_int(&mut direct_int_text, &mut direct_int_sa, 4, 0),
21021            0
21022        );
21023        assert_eq!(
21024            libsais64_int_omp(&mut omp_int_text, &mut omp_int_sa, 4, 0, 2),
21025            0
21026        );
21027        assert_eq!(omp_int_sa, direct_int_sa);
21028        assert_eq!(
21029            libsais64_int_omp(&mut omp_int_text, &mut omp_int_sa, 4, 0, -1),
21030            -1
21031        );
21032
21033        let long_text = vec![3, 1, 4, 1, 5, 0];
21034        let mut direct_long_text = long_text.clone();
21035        let mut omp_long_text = long_text.clone();
21036        let mut direct_long_sa = vec![0; long_text.len()];
21037        let mut omp_long_sa = vec![0; long_text.len()];
21038        assert_eq!(
21039            libsais64_long(&mut direct_long_text, &mut direct_long_sa, 6, 0),
21040            0
21041        );
21042        assert_eq!(
21043            libsais64_long_omp(&mut omp_long_text, &mut omp_long_sa, 6, 0, 2),
21044            0
21045        );
21046        assert_eq!(omp_long_sa, direct_long_sa);
21047        assert_eq!(
21048            libsais64_long_omp(&mut omp_long_text, &mut omp_long_sa, 6, 0, -1),
21049            -1
21050        );
21051
21052        let mut direct_bwt = vec![0; text.len()];
21053        let mut direct_work = vec![0; text.len()];
21054        let mut omp_bwt = vec![0; text.len()];
21055        let mut omp_work = vec![0; text.len()];
21056        assert_eq!(
21057            libsais64_bwt(text, &mut direct_bwt, &mut direct_work, 0, None),
21058            libsais64_bwt_omp(text, &mut omp_bwt, &mut omp_work, 0, None, 2)
21059        );
21060        assert_eq!(omp_bwt, direct_bwt);
21061        assert_eq!(
21062            libsais64_bwt_omp(text, &mut omp_bwt, &mut omp_work, 0, None, -1),
21063            -1
21064        );
21065
21066        let mut direct_aux = vec![0; 2];
21067        let mut omp_aux = vec![0; 2];
21068        assert_eq!(
21069            libsais64_bwt_aux(
21070                text,
21071                &mut direct_bwt,
21072                &mut direct_work,
21073                0,
21074                None,
21075                4,
21076                &mut direct_aux
21077            ),
21078            libsais64_bwt_aux_omp(
21079                text,
21080                &mut omp_bwt,
21081                &mut omp_work,
21082                0,
21083                None,
21084                4,
21085                &mut omp_aux,
21086                2
21087            )
21088        );
21089        assert_eq!(omp_bwt, direct_bwt);
21090        assert_eq!(omp_aux, direct_aux);
21091        assert_eq!(
21092            libsais64_bwt_aux_omp(
21093                text,
21094                &mut omp_bwt,
21095                &mut omp_work,
21096                0,
21097                None,
21098                4,
21099                &mut omp_aux,
21100                -1
21101            ),
21102            -1
21103        );
21104    }
21105
21106    #[test]
21107    fn public_libsais64_plcp_omp_wrappers_match_direct_calls() {
21108        let text = b"banana";
21109        let mut sa = vec![0; text.len()];
21110        assert_eq!(libsais64(text, &mut sa, 0, None), 0);
21111
21112        let mut direct_plcp = vec![0; text.len()];
21113        let mut omp_plcp = vec![0; text.len()];
21114        assert_eq!(libsais64_plcp(text, &sa, &mut direct_plcp), 0);
21115        assert_eq!(libsais64_plcp_omp(text, &sa, &mut omp_plcp, 2), 0);
21116        assert_eq!(omp_plcp, direct_plcp);
21117        assert_eq!(libsais64_plcp_omp(text, &sa, &mut omp_plcp, -1), -1);
21118
21119        let mut direct_lcp = vec![0; text.len()];
21120        let mut omp_lcp = vec![0; text.len()];
21121        assert_eq!(libsais64_lcp(&direct_plcp, &sa, &mut direct_lcp), 0);
21122        assert_eq!(libsais64_lcp_omp(&direct_plcp, &sa, &mut omp_lcp, 2), 0);
21123        assert_eq!(omp_lcp, direct_lcp);
21124        assert_eq!(libsais64_lcp_omp(&direct_plcp, &sa, &mut omp_lcp, -1), -1);
21125
21126        let gsa_text = b"ban\0ana\0";
21127        let mut gsa = vec![0; gsa_text.len()];
21128        assert_eq!(libsais64_gsa(gsa_text, &mut gsa, 0, None), 0);
21129        let mut direct_gsa_plcp = vec![0; gsa_text.len()];
21130        let mut omp_gsa_plcp = vec![0; gsa_text.len()];
21131        assert_eq!(libsais64_plcp_gsa(gsa_text, &gsa, &mut direct_gsa_plcp), 0);
21132        assert_eq!(
21133            libsais64_plcp_gsa_omp(gsa_text, &gsa, &mut omp_gsa_plcp, 2),
21134            0
21135        );
21136        assert_eq!(omp_gsa_plcp, direct_gsa_plcp);
21137        assert_eq!(
21138            libsais64_plcp_gsa_omp(gsa_text, &gsa, &mut omp_gsa_plcp, -1),
21139            -1
21140        );
21141
21142        let int_text = vec![2, 1, 3, 1, 0];
21143        let mut int_text_for_sa = int_text.clone();
21144        let mut int_sa = vec![0; int_text.len()];
21145        assert_eq!(libsais64_int(&mut int_text_for_sa, &mut int_sa, 4, 0), 0);
21146        let mut direct_int_plcp = vec![0; int_text.len()];
21147        let mut omp_int_plcp = vec![0; int_text.len()];
21148        assert_eq!(
21149            libsais64_plcp_int(&int_text, &int_sa, &mut direct_int_plcp),
21150            0
21151        );
21152        assert_eq!(
21153            libsais64_plcp_int_omp(&int_text, &int_sa, &mut omp_int_plcp, 2),
21154            0
21155        );
21156        assert_eq!(omp_int_plcp, direct_int_plcp);
21157        assert_eq!(
21158            libsais64_plcp_int_omp(&int_text, &int_sa, &mut omp_int_plcp, -1),
21159            -1
21160        );
21161    }
21162
21163    #[test]
21164    fn public_libsais64_omp_frequency_wrappers_match_direct_calls() {
21165        let text = b"banana";
21166        let gsa_text = b"ban\0ana\0";
21167
21168        let mut direct_sa = vec![0; text.len()];
21169        let mut omp_sa = vec![0; text.len()];
21170        let mut direct_freq = vec![-1; ALPHABET_SIZE];
21171        let mut omp_freq = vec![-1; ALPHABET_SIZE];
21172        assert_eq!(
21173            libsais64(text, &mut direct_sa, 0, Some(&mut direct_freq)),
21174            0
21175        );
21176        assert_eq!(
21177            libsais64_omp(text, &mut omp_sa, 0, Some(&mut omp_freq), 2),
21178            0
21179        );
21180        assert_eq!(omp_sa, direct_sa);
21181        assert_eq!(omp_freq, direct_freq);
21182
21183        let mut direct_gsa = vec![0; gsa_text.len()];
21184        let mut omp_gsa = vec![0; gsa_text.len()];
21185        direct_freq.fill(-1);
21186        omp_freq.fill(-1);
21187        assert_eq!(
21188            libsais64_gsa(gsa_text, &mut direct_gsa, 0, Some(&mut direct_freq)),
21189            0
21190        );
21191        assert_eq!(
21192            libsais64_gsa_omp(gsa_text, &mut omp_gsa, 0, Some(&mut omp_freq), 2),
21193            0
21194        );
21195        assert_eq!(omp_gsa, direct_gsa);
21196        assert_eq!(omp_freq, direct_freq);
21197
21198        let mut direct_bwt = vec![0; text.len()];
21199        let mut direct_work = vec![0; text.len()];
21200        let mut omp_bwt = vec![0; text.len()];
21201        let mut omp_work = vec![0; text.len()];
21202        direct_freq.fill(-1);
21203        omp_freq.fill(-1);
21204        assert_eq!(
21205            libsais64_bwt(
21206                text,
21207                &mut direct_bwt,
21208                &mut direct_work,
21209                0,
21210                Some(&mut direct_freq)
21211            ),
21212            libsais64_bwt_omp(text, &mut omp_bwt, &mut omp_work, 0, Some(&mut omp_freq), 2)
21213        );
21214        assert_eq!(omp_bwt, direct_bwt);
21215        assert_eq!(omp_freq, direct_freq);
21216
21217        let mut direct_aux = vec![0; 2];
21218        let mut omp_aux = vec![0; 2];
21219        direct_freq.fill(-1);
21220        omp_freq.fill(-1);
21221        assert_eq!(
21222            libsais64_bwt_aux(
21223                text,
21224                &mut direct_bwt,
21225                &mut direct_work,
21226                0,
21227                Some(&mut direct_freq),
21228                4,
21229                &mut direct_aux
21230            ),
21231            libsais64_bwt_aux_omp(
21232                text,
21233                &mut omp_bwt,
21234                &mut omp_work,
21235                0,
21236                Some(&mut omp_freq),
21237                4,
21238                &mut omp_aux,
21239                2
21240            )
21241        );
21242        assert_eq!(omp_bwt, direct_bwt);
21243        assert_eq!(omp_aux, direct_aux);
21244        assert_eq!(omp_freq, direct_freq);
21245    }
21246
21247    #[test]
21248    fn public_libsais64_unbwt_omp_frequency_wrappers_match_direct_calls() {
21249        let text = b"abracadabra";
21250        let mut freq = vec![0; ALPHABET_SIZE];
21251        let mut bwt = vec![0; text.len()];
21252        let mut work = vec![0; text.len()];
21253        let primary = libsais64_bwt(text, &mut bwt, &mut work, 0, Some(&mut freq));
21254        assert!(primary >= 0);
21255
21256        let mut direct = vec![0; text.len()];
21257        let mut direct_work = vec![0; text.len() + 1];
21258        let mut omp = vec![0; text.len()];
21259        let mut omp_work = vec![0; text.len() + 1];
21260        assert_eq!(
21261            libsais64_unbwt(&bwt, &mut direct, &mut direct_work, Some(&freq), primary),
21262            libsais64_unbwt_omp(&bwt, &mut omp, &mut omp_work, Some(&freq), primary, 2)
21263        );
21264        assert_eq!(omp, direct);
21265        assert_eq!(omp, text);
21266
21267        let mut aux = vec![0; (text.len() - 1) / 4 + 1];
21268        assert_eq!(
21269            libsais64_bwt_aux(text, &mut bwt, &mut work, 0, Some(&mut freq), 4, &mut aux),
21270            0
21271        );
21272        direct.fill(0);
21273        direct_work.fill(0);
21274        omp.fill(0);
21275        omp_work.fill(0);
21276        assert_eq!(
21277            libsais64_unbwt_aux(&bwt, &mut direct, &mut direct_work, Some(&freq), 4, &aux),
21278            libsais64_unbwt_aux_omp(&bwt, &mut omp, &mut omp_work, Some(&freq), 4, &aux, 2)
21279        );
21280        assert_eq!(omp, direct);
21281        assert_eq!(omp, text);
21282    }
21283
21284    #[test]
21285    fn public_libsais64_bwt_aux_matches_upstream_c() {
21286        for text in [
21287            b"banana".as_slice(),
21288            b"mississippi",
21289            b"abracadabra",
21290            b"AAAAAAAAAAAAAAAA",
21291            b"zyxwvutsrqponmlk",
21292        ] {
21293            assert_libsais64_bwt_aux_matches_c(text, 4);
21294        }
21295    }
21296
21297    #[test]
21298    fn public_libsais64_frequency_outputs_match_upstream_c() {
21299        assert_libsais64_freq_outputs_match_c(b"banana", b"ban\0ana\0");
21300    }
21301
21302    #[test]
21303    fn public_libsais64_unbwt_with_frequency_matches_upstream_c() {
21304        assert_libsais64_unbwt_freq_matches_c(b"abracadabra");
21305    }
21306
21307    #[test]
21308    fn public_libsais64_unbwt_matches_upstream_c() {
21309        for text in [
21310            b"a".as_slice(),
21311            b"banana",
21312            b"mississippi",
21313            b"abracadabra",
21314            b"AAAAAAAAAAAAAAAA",
21315            b"zyxwvutsrqponmlk",
21316        ] {
21317            assert_libsais64_unbwt_matches_c(text);
21318        }
21319    }
21320
21321    #[test]
21322    fn public_libsais64_unbwt_aux_matches_upstream_c() {
21323        for text in [
21324            b"banana".as_slice(),
21325            b"mississippi",
21326            b"abracadabra",
21327            b"AAAAAAAAAAAAAAAA",
21328            b"zyxwvutsrqponmlk",
21329        ] {
21330            assert_libsais64_unbwt_aux_matches_c(text, 4);
21331        }
21332    }
21333
21334    #[test]
21335    fn public_libsais64_bwt_aux_round_trips() {
21336        for text in [
21337            b"banana".as_slice(),
21338            b"mississippi",
21339            b"abracadabra",
21340            b"AAAAAAAAAAAAAAAA",
21341            b"zyxwvutsrqponmlk",
21342        ] {
21343            assert_libsais64_bwt_aux_round_trips(text, 4);
21344        }
21345    }
21346
21347    fn deterministic_large_input(seed: u64, len: usize, alphabet: u32) -> Vec<u8> {
21348        let mut rng = seed | 1;
21349        (0..len)
21350            .map(|_| {
21351                rng = rng.wrapping_add(0x9e3779b97f4a7c15);
21352                let mut z = rng;
21353                z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
21354                z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
21355                z ^= z >> 31;
21356                (z % u64::from(alphabet)) as u8
21357            })
21358            .collect()
21359    }
21360
21361    /// Regression test for the OMP-to-rayon port of libsais64: with threads>1
21362    /// (n>=65_536 to hit the parallel branch) the SA must equal the single-thread SA.
21363    #[test]
21364    fn libsais64_omp_threads_4_matches_single_thread_on_large_input() {
21365        let text = deterministic_large_input(0xBADF00D, 250_000, 8);
21366        let mut sa_single = vec![0 as SaSint; text.len() + 1];
21367        let mut sa_omp = vec![0 as SaSint; text.len() + 1];
21368        assert_eq!(libsais64(&text, &mut sa_single, 0, None), 0);
21369        assert_eq!(libsais64_omp(&text, &mut sa_omp, 0, None, 4), 0);
21370        assert_eq!(
21371            sa_single, sa_omp,
21372            "libsais64 threads=4 SA must match threads=1 SA"
21373        );
21374    }
21375
21376    /// Guard against nested-rayon-pool deadlocks for libsais64.
21377    #[test]
21378    fn libsais64_omp_does_not_deadlock_when_invoked_from_rayon_worker() {
21379        use rayon::prelude::*;
21380        let text = deterministic_large_input(0x5EED64, 100_000, 4);
21381        let inputs: Vec<&[u8]> = vec![&text, &text, &text, &text];
21382        let results: Vec<SaSint> = inputs
21383            .par_iter()
21384            .map(|t| {
21385                let mut sa = vec![0 as SaSint; t.len() + 1];
21386                libsais64_omp(t, &mut sa, 0, None, 4)
21387            })
21388            .collect();
21389        assert!(results.iter().all(|&rc| rc == 0));
21390    }
21391}