pub trait InverseBwt: Default + Send {
fn invert(&mut self, ll: &[u8], counts: &[u32; 256], orig_ptr: u32, out: &mut Vec<u8>);
}
#[derive(Default)]
pub struct Plain {
tt: TVector,
}
impl InverseBwt for Plain {
fn invert(&mut self, ll: &[u8], counts: &[u32; 256], orig_ptr: u32, out: &mut Vec<u8>) {
build(ll, counts, &mut self.tt);
walk(&self.tt, orig_ptr, out);
}
}
#[derive(Default)]
pub struct Segmented {
tt: TVector,
seg: SegScratch,
}
impl InverseBwt for Segmented {
fn invert(&mut self, ll: &[u8], counts: &[u32; 256], orig_ptr: u32, out: &mut Vec<u8>) {
build(ll, counts, &mut self.tt);
walk_segments_room(&mut self.tt, orig_ptr, out, &mut self.seg);
}
}
#[derive(Default)]
pub struct TVector(Vec<u32>);
impl TVector {
pub fn new() -> Self {
TVector(Vec::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub fn build(ll: &[u8], counts: &[u32; 256], tt: &mut TVector) {
let tt = &mut tt.0;
tt.clear();
tt.extend(ll.iter().map(|&c| c as u32));
let mut ftab = [0u32; 256];
let mut cum = 0u32;
for i in 0..256 {
ftab[i] = cum;
cum += counts[i];
}
let mut i = 0;
while i + 16 <= ll.len() {
let (a, b) = (read_u64(ll, i), read_u64(ll, i + 8));
if a == b && a == a.rotate_left(8) {
let c = ll[i] as usize;
let k = ftab[c] as usize;
let base = (i as u32) << 8;
for (j, e) in tt[k..k + 16].iter_mut().enumerate() {
*e = (*e & 0xff) | (base + ((j as u32) << 8));
}
ftab[c] += 16;
} else {
link(ll, tt, &mut ftab, i..i + 16);
}
i += 16;
}
link(ll, tt, &mut ftab, i..ll.len());
}
#[inline(always)]
fn link(ll: &[u8], tt: &mut [u32], ftab: &mut [u32; 256], rows: std::ops::Range<usize>) {
for i in rows {
let c = ll[i];
let k = ftab[c as usize] as usize;
tt[k] = (tt[k] & 0xff) | (i as u32) << 8;
ftab[c as usize] += 1;
}
}
#[inline(always)]
fn read_u64(b: &[u8], i: usize) -> u64 {
u64::from_ne_bytes(b[i..i + 8].try_into().unwrap())
}
pub fn walk(tt: &TVector, orig_ptr: u32, out: &mut Vec<u8>) {
let t = &tt.0;
let n = t.len();
out.clear();
out.reserve(n);
let mut p = t[orig_ptr as usize];
for _ in 0..n {
p = unsafe { *t.get_unchecked((p >> 8) as usize) };
out.push(p as u8);
}
}
const LANES: usize = 12;
const SEGMENTS: usize = 256;
const FLAGGED_LANES: usize = 32;
const FLAGGED_SEGMENTS: usize = 512;
const PREFETCH: bool = true;
const MIN_SEGMENTED: usize = 1 << 16;
#[derive(Default)]
pub struct SegScratch {
starts: Vec<u32>,
bitmap: Vec<u64>,
bufs: Vec<Vec<u8>>,
next: Vec<usize>,
}
pub fn walk_fast(tt: &TVector, orig_ptr: u32, out: &mut Vec<u8>, s: &mut SegScratch) {
if tt.len() < MIN_SEGMENTED {
walk(tt, orig_ptr, out)
} else {
walk_segments(tt, orig_ptr, out, s)
}
}
pub fn walk_segments(tt: &TVector, orig_ptr: u32, out: &mut Vec<u8>, s: &mut SegScratch) {
let t = &tt.0;
let n = t.len();
out.clear();
let r0 = t[orig_ptr as usize] >> 8;
random_rows(n, SEGMENTS - 1, &mut s.starts);
s.starts.push(r0);
s.starts.sort_unstable();
s.starts.dedup();
let starts = &s.starts;
let ns = starts.len();
s.bitmap.clear();
s.bitmap.resize(n.div_ceil(64), 0);
for &r in starts {
s.bitmap[(r >> 6) as usize] |= 1 << (r & 63);
}
let cap = 2 * n / ns + 64;
s.bufs.resize_with(ns.max(s.bufs.len()), Vec::new);
for b in &mut s.bufs[..ns] {
b.clear();
b.reserve(cap);
}
s.next.clear();
s.next.resize(ns, usize::MAX);
let seg_of = |row: u32| starts.binary_search(&row).unwrap_or(usize::MAX);
let mut lane_seg = [usize::MAX; LANES];
let mut lane_row = [0u32; LANES];
let mut queued = 0;
for k in 0..LANES {
if queued < ns {
lane_seg[k] = queued;
lane_row[k] = starts[queued];
queued += 1;
}
}
let mut active = lane_seg.iter().filter(|&&x| x != usize::MAX).count();
let bm = &s.bitmap;
let bufs = &mut s.bufs;
while active > 0 {
for k in 0..LANES {
let seg = lane_seg[k];
if seg == usize::MAX {
continue;
}
let q = unsafe { *t.get_unchecked(lane_row[k] as usize) };
let row = q >> 8;
bufs[seg].push(q as u8);
lane_row[k] = row;
if unsafe { *bm.get_unchecked((row >> 6) as usize) } >> (row & 63) & 1 != 0 {
s.next[seg] = seg_of(row);
if queued < ns {
lane_seg[k] = queued;
lane_row[k] = starts[queued];
queued += 1;
} else {
lane_seg[k] = usize::MAX;
active -= 1;
}
}
}
}
out.reserve(n);
let mut seg = seg_of(r0);
while out.len() < n {
let b = &bufs[seg];
out.extend_from_slice(&b[..b.len().min(n - out.len())]);
seg = s.next[seg];
}
}
const START: u32 = 1 << 31;
const LINK: u32 = !START;
#[derive(Clone, Copy)]
struct Lane {
row: u32,
seg: usize,
p: *mut u8,
end: *mut u8,
}
pub fn walk_segments_flagged(
tt: &mut TVector,
orig_ptr: u32,
out: &mut Vec<u8>,
s: &mut SegScratch,
) {
if tt.len() < MIN_SEGMENTED {
return walk(tt, orig_ptr, out);
}
walk_segments_flagged_with::<FLAGGED_LANES, PREFETCH>(tt, orig_ptr, out, s, FLAGGED_SEGMENTS)
}
pub fn walk_segments_flagged_with<const L: usize, const PF: bool>(
tt: &mut TVector,
orig_ptr: u32,
out: &mut Vec<u8>,
s: &mut SegScratch,
segments: usize,
) {
let n = tt.0.len();
out.clear();
if n == 0 {
return;
}
let r0 = tt.0[orig_ptr as usize] >> 8;
random_rows(n, segments - 1, &mut s.starts);
s.starts.push(r0);
s.starts.sort_unstable();
s.starts.dedup();
let ns = s.starts.len();
for &r in &s.starts {
tt.0[r as usize] |= START;
}
let cap = 2 * n / ns + 64;
s.bufs.resize_with(ns.max(s.bufs.len()), Vec::new);
for b in &mut s.bufs[..ns] {
b.clear();
b.reserve(cap);
}
s.next.clear();
s.next.resize(ns, usize::MAX);
let starts = &s.starts;
let seg_of = |row: u32| starts.binary_search(&row).unwrap_or(usize::MAX);
let t = tt.0.as_ptr();
let bufs = &mut s.bufs;
let next = &mut s.next;
let take = |seg: usize, bufs: &mut Vec<Vec<u8>>| -> Lane {
let b = &mut bufs[seg];
let base = b.as_mut_ptr();
let q = unsafe { *t.add(starts[seg] as usize) } & LINK;
debug_assert!(b.capacity() > 1);
unsafe { *base = q as u8 };
Lane {
row: q >> 8,
seg,
p: unsafe { base.add(1) },
end: unsafe { base.add(b.capacity()) },
}
};
let finish = |l: &Lane, bufs: &mut Vec<Vec<u8>>, next: &mut Vec<usize>| {
let b = &mut bufs[l.seg];
unsafe { b.set_len(l.p.offset_from(b.as_ptr()) as usize) };
next[l.seg] = seg_of(l.row);
};
let grow = |l: &mut Lane, bufs: &mut Vec<Vec<u8>>| {
let b = &mut bufs[l.seg];
let len = unsafe { l.p.offset_from(b.as_ptr()) } as usize;
unsafe { b.set_len(len) };
b.reserve(b.capacity());
l.p = unsafe { b.as_mut_ptr().add(len) };
l.end = unsafe { b.as_mut_ptr().add(b.capacity()) };
};
let mut queued = 0;
let mut lanes: [Lane; L] = std::array::from_fn(|_| Lane {
row: 0,
seg: usize::MAX,
p: std::ptr::null_mut(),
end: std::ptr::null_mut(),
});
let mut active = 0;
while active < L && queued < ns {
lanes[active] = take(queued, bufs);
if PF {
prefetch(t, lanes[active].row);
}
queued += 1;
active += 1;
}
if active == L {
'full: loop {
for l in lanes.iter_mut() {
let q = unsafe { *t.add(l.row as usize) };
if q & START != 0 {
finish(l, bufs, next);
if queued == ns {
break 'full;
}
*l = take(queued, bufs);
if PF {
prefetch(t, l.row);
}
queued += 1;
continue;
}
l.row = q >> 8;
if PF {
prefetch(t, l.row);
}
unsafe {
*l.p = q as u8;
l.p = l.p.add(1);
}
if l.p == l.end {
grow(l, bufs);
}
}
}
}
let mut live: Vec<Lane> = lanes[..active]
.iter()
.copied()
.filter(|l| next_unset(l, next))
.collect();
while !live.is_empty() {
let mut i = 0;
while i < live.len() {
let l = &mut live[i];
let q = unsafe { *t.add(l.row as usize) };
if q & START != 0 {
finish(l, bufs, next);
live.swap_remove(i);
continue;
}
l.row = q >> 8;
if PF {
prefetch(t, l.row);
}
unsafe {
*l.p = q as u8;
l.p = l.p.add(1);
}
if l.p == l.end {
grow(l, bufs);
}
i += 1;
}
}
for &r in starts {
tt.0[r as usize] &= LINK;
}
out.reserve(n);
let mut seg = seg_of(r0);
while out.len() < n {
let b = &bufs[seg];
out.extend_from_slice(&b[..b.len().min(n - out.len())]);
seg = next[seg];
}
}
#[cfg(test)]
thread_local! {
static GROWS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static PASS_ENDS_FULL: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static MID_PASS_TAKES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static FULL_PHASE_ENDS_MID_PASS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static TAIL_ENTERED_FULL: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
macro_rules! transition {
($c:ident) => {
$c.with(|c| c.set(c.get() + 1))
};
}
#[cfg(not(test))]
macro_rules! transition {
($c:ident) => {};
}
pub fn walk_segments_room(tt: &mut TVector, orig_ptr: u32, out: &mut Vec<u8>, s: &mut SegScratch) {
if tt.len() < MIN_SEGMENTED {
return walk(tt, orig_ptr, out);
}
walk_segments_room_with::<FLAGGED_LANES, PREFETCH>(tt, orig_ptr, out, s, FLAGGED_SEGMENTS)
}
pub fn walk_segments_room_with<const L: usize, const PF: bool>(
tt: &mut TVector,
orig_ptr: u32,
out: &mut Vec<u8>,
s: &mut SegScratch,
segments: usize,
) {
let n = tt.0.len();
out.clear();
if n == 0 {
return;
}
let r0 = tt.0[orig_ptr as usize] >> 8;
random_rows(n, segments - 1, &mut s.starts);
s.starts.push(r0);
s.starts.sort_unstable();
s.starts.dedup();
let ns = s.starts.len();
for &r in &s.starts {
tt.0[r as usize] |= START;
}
let cap = 2 * n / ns + 64;
s.bufs.resize_with(ns.max(s.bufs.len()), Vec::new);
for b in &mut s.bufs[..ns] {
b.clear();
b.reserve(cap);
}
s.next.clear();
s.next.resize(ns, usize::MAX);
let starts = &s.starts;
let seg_of = |row: u32| starts.binary_search(&row).unwrap_or(usize::MAX);
let t = tt.0.as_ptr();
let bufs = &mut s.bufs;
let next = &mut s.next;
let take = |seg: usize, bufs: &mut Vec<Vec<u8>>| -> Lane {
let b = &mut bufs[seg];
let base = b.as_mut_ptr();
let q = unsafe { *t.add(starts[seg] as usize) } & LINK;
unsafe { *base = q as u8 };
Lane {
row: q >> 8,
seg,
p: unsafe { base.add(1) },
end: unsafe { base.add(b.capacity()) },
}
};
let finish = |l: &Lane, bufs: &mut Vec<Vec<u8>>, next: &mut Vec<usize>| {
let b = &mut bufs[l.seg];
unsafe { b.set_len(l.p.offset_from(b.as_ptr()) as usize) };
next[l.seg] = seg_of(l.row);
};
let grow = |l: &mut Lane, bufs: &mut Vec<Vec<u8>>| {
transition!(GROWS);
let b = &mut bufs[l.seg];
let len = unsafe { l.p.offset_from(b.as_ptr()) } as usize;
unsafe { b.set_len(len) };
b.reserve(b.capacity());
l.p = unsafe { b.as_mut_ptr().add(len) };
l.end = unsafe { b.as_mut_ptr().add(b.capacity()) };
};
let mut queued = 0;
let mut lanes: [Lane; L] = std::array::from_fn(|_| Lane {
row: 0,
seg: usize::MAX,
p: std::ptr::null_mut(),
end: std::ptr::null_mut(),
});
let mut active = 0;
while active < L && queued < ns {
lanes[active] = take(queued, bufs);
if PF {
prefetch(t, lanes[active].row);
}
queued += 1;
active += 1;
}
if active == L {
'full: loop {
let mut room = cap - 1;
for l in lanes.iter_mut() {
if l.p == l.end {
grow(l, bufs);
}
room = room.min(unsafe { l.end.offset_from(l.p) } as usize);
}
debug_assert!(room > 0);
if lanes
.iter()
.any(|l| unsafe { l.end.offset_from(l.p) } as usize == room)
{
transition!(PASS_ENDS_FULL);
}
let mut base: [*mut u8; L] = std::array::from_fn(|k| lanes[k].p);
for off in 0..room {
for k in 0..L {
let l = &mut lanes[k];
let q = unsafe { *t.add(l.row as usize) };
if q & START != 0 {
l.p = base[k].wrapping_add(off);
finish(l, bufs, next);
if queued == ns {
for (j, lj) in lanes.iter_mut().enumerate() {
if j < k {
lj.p = base[j].wrapping_add(off + 1);
} else if j > k {
lj.p = base[j].wrapping_add(off);
}
}
transition!(FULL_PHASE_ENDS_MID_PASS);
break 'full;
}
transition!(MID_PASS_TAKES);
*l = take(queued, bufs);
if PF {
prefetch(t, l.row);
}
queued += 1;
base[k] = l.p.wrapping_sub(off + 1);
continue;
}
l.row = q >> 8;
if PF {
prefetch(t, l.row);
}
debug_assert!(base[k].wrapping_add(off) < l.end);
unsafe { *base[k].wrapping_add(off) = q as u8 };
}
}
for (l, b) in lanes.iter_mut().zip(base) {
l.p = b.wrapping_add(room);
}
}
}
let mut live: Vec<Lane> = lanes[..active]
.iter()
.copied()
.filter(|l| next_unset(l, next))
.collect();
for l in live.iter_mut() {
if l.p == l.end {
transition!(TAIL_ENTERED_FULL);
grow(l, bufs);
}
debug_assert!(l.p < l.end);
}
while !live.is_empty() {
let mut i = 0;
while i < live.len() {
let l = &mut live[i];
let q = unsafe { *t.add(l.row as usize) };
if q & START != 0 {
finish(l, bufs, next);
live.swap_remove(i);
continue;
}
l.row = q >> 8;
if PF {
prefetch(t, l.row);
}
unsafe {
*l.p = q as u8;
l.p = l.p.add(1);
}
if l.p == l.end {
grow(l, bufs);
}
i += 1;
}
}
for &r in starts {
tt.0[r as usize] &= LINK;
}
out.reserve(n);
let mut seg = seg_of(r0);
while out.len() < n {
let b = &bufs[seg];
out.extend_from_slice(&b[..b.len().min(n - out.len())]);
seg = next[seg];
}
}
#[inline(always)]
fn prefetch(t: *const u32, row: u32) {
let p = t.wrapping_add(row as usize);
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::x86_64::_mm_prefetch::<{ core::arch::x86_64::_MM_HINT_T0 }>(p as *const i8)
};
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("prfm pldl1keep, [{p}]", p = in(reg) p, options(nostack, preserves_flags, readonly))
};
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = p;
}
fn next_unset(l: &Lane, next: &[usize]) -> bool {
l.seg != usize::MAX && next[l.seg] == usize::MAX
}
fn random_rows(n: usize, m: usize, rows: &mut Vec<u32>) {
rows.clear();
let mut x = 0x2545_f491_4f6c_dd1du64 ^ n as u64;
rows.extend((0..m).map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x % n as u64) as u32
}));
}
#[cfg(test)]
mod tests {
use super::*;
fn segment_oracle(tt: &TVector, starts: &[u32]) -> Vec<(Vec<u8>, usize)> {
let t = &tt.0;
let mut index = vec![usize::MAX; t.len()];
for (i, &r) in starts.iter().enumerate() {
index[r as usize] = i;
}
starts
.iter()
.map(|&r| {
let mut bytes = vec![t[r as usize] as u8];
let mut row = (t[r as usize] >> 8) as usize;
while index[row] == usize::MAX {
bytes.push(t[row] as u8);
row = (t[row] >> 8) as usize;
}
(bytes, index[row])
})
.collect()
}
fn check_segments(tt: &TVector, s: &SegScratch, what: &str) {
let want = segment_oracle(tt, &s.starts);
for (i, (bytes, next)) in want.iter().enumerate() {
assert!(s.bufs[i] == *bytes, "{what}: segment {i}'s bytes");
assert_eq!(s.next[i], *next, "{what}: segment {i}'s successor");
}
}
fn count(c: &'static std::thread::LocalKey<std::cell::Cell<usize>>) -> usize {
c.with(|c| c.get())
}
fn check(ll: &[u8]) {
let mut counts = [0u32; 256];
for &c in ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(ll, &counts, &mut tt);
let (mut plain, mut flagged, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
for orig_ptr in [0, ll.len() as u32 / 3, ll.len() as u32 - 1] {
walk(&tt, orig_ptr, &mut plain);
walk_segments_flagged(&mut tt, orig_ptr, &mut flagged, &mut seg);
assert!(plain == flagged, "orig_ptr {orig_ptr}");
if ll.len() >= MIN_SEGMENTED {
check_segments(&tt, &seg, "flagged walk");
}
walk_segments_room(&mut tt, orig_ptr, &mut flagged, &mut seg);
assert!(plain == flagged, "room walk, orig_ptr {orig_ptr}");
if ll.len() >= MIN_SEGMENTED {
check_segments(&tt, &seg, "room walk");
}
assert!(tt.0.iter().all(|&e| e & START == 0));
for segments in [2, 40, 512, 4096] {
walk_segments_room_with::<32, true>(
&mut tt,
orig_ptr,
&mut flagged,
&mut seg,
segments,
);
assert!(plain == flagged, "room walk, {segments} segments");
walk_segments_room_with::<5, false>(
&mut tt,
orig_ptr,
&mut flagged,
&mut seg,
segments,
);
assert!(plain == flagged, "room walk, 5 lanes, {segments} segments");
}
assert!(tt.0.iter().all(|&e| e & START == 0));
for segments in [1, 256, 1024] {
walk_segments_flagged_with::<12, false>(
&mut tt,
orig_ptr,
&mut flagged,
&mut seg,
segments,
);
assert!(plain == flagged, "12 lanes, {segments} segments");
walk_segments_flagged_with::<7, true>(
&mut tt,
orig_ptr,
&mut flagged,
&mut seg,
segments,
);
assert!(plain == flagged, "7 lanes, prefetch, {segments} segments");
assert!(tt.0.iter().all(|&e| e & START == 0));
}
}
}
#[test]
fn run_stores_equal_one_pass() {
fn one_pass(ll: &[u8], counts: &[u32; 256]) -> Vec<u32> {
let mut ftab = [0u32; 256];
let mut cum = 0;
for c in 0..256 {
ftab[c] = cum;
cum += counts[c];
}
let mut tt: Vec<u32> = ll.iter().map(|&c| c as u32).collect();
for (i, &c) in ll.iter().enumerate() {
let k = ftab[c as usize] as usize;
tt[k] |= (i as u32) << 8;
ftab[c as usize] += 1;
}
tt
}
let mut x = 0x2545_f491_4f6c_dd1du64;
let mut rnd = move || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x
};
let mut inputs: Vec<Vec<u8>> = (0..=40).map(|n| vec![7u8; n]).collect();
for _ in 0..40 {
let mut ll = Vec::new();
while ll.len() < 3000 {
let c = (rnd() % 5) as u8;
let run = [1, 2, 15, 16, 17, 31, 32, 33, 48, 100][(rnd() % 10) as usize];
ll.extend(std::iter::repeat_n(c, run));
}
ll.truncate(3000 - (rnd() % 16) as usize);
inputs.push(ll);
}
inputs.push((0..5000).map(|_| rnd() as u8).collect());
inputs.push(
(0..5000)
.map(|i| if i < 1003 { (i % 3) as u8 } else { 9 })
.collect(),
);
for ll in &inputs {
let mut counts = [0u32; 256];
for &c in ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(ll, &counts, &mut tt);
assert!(tt.0 == one_pass(ll, &counts), "length {}", ll.len());
}
}
#[test]
fn room_walk_short_cycles() {
let n = MIN_SEGMENTED + 77;
for ll in [
vec![3u8; n],
(0..n).map(|i| (i % 2) as u8).collect::<Vec<u8>>(),
] {
let mut counts = [0u32; 256];
for &c in &ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(&ll, &counts, &mut tt);
let (mut plain, mut room, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
for orig_ptr in [0, 1, n as u32 / 2, n as u32 - 1] {
walk(&tt, orig_ptr, &mut plain);
for segments in [33, 64, 512, 4096] {
walk_segments_room_with::<32, true>(
&mut tt, orig_ptr, &mut room, &mut seg, segments,
);
assert!(plain == room, "orig_ptr {orig_ptr}, {segments} segments");
}
}
}
}
#[test]
fn room_walk_crafted_permutations() {
let mut x = 99u64;
let mut rnd = move |m: u64| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x % m
};
for case in 0..200 {
let n = MIN_SEGMENTED + rnd(200_000) as usize;
let alphabet = 1 + rnd(if case % 2 == 0 { 3 } else { 256 });
let mut ll: Vec<u8> = (0..n).map(|_| (rnd(alphabet) * 7) as u8).collect();
if case % 5 == 0 {
let p = 1 + rnd(5) as usize;
for i in p..n {
ll[i] = ll[i % p];
}
}
let orig_ptr = rnd(n as u64) as u32;
let mut counts = [0u32; 256];
for &c in &ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(&ll, &counts, &mut tt);
let (mut plain, mut room, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
walk(&tt, orig_ptr, &mut plain);
walk_segments_room(&mut tt, orig_ptr, &mut room, &mut seg);
assert!(plain == room, "case {case} n {n} alphabet {alphabet}");
check_segments(&tt, &seg, "room walk");
walk_segments_flagged(&mut tt, orig_ptr, &mut room, &mut seg);
assert!(plain == room, "flagged walk, case {case}");
check_segments(&tt, &seg, "flagged walk");
}
assert_reached();
}
fn assert_reached() {
for (name, c) in [
("a buffer grown", &GROWS),
("a pass ending with a buffer full", &PASS_ENDS_FULL),
("a segment taken mid-pass", &MID_PASS_TAKES),
("the full phase ending mid-pass", &FULL_PHASE_ENDS_MID_PASS),
("a lane coming into the tail full", &TAIL_ENTERED_FULL),
] {
assert!(count(c) > 0, "never reached: {name}");
}
}
#[test]
fn flagged_walk_equals_plain() {
let n = MIN_SEGMENTED + 1000;
let mut x = 0x9e37_79b9_7f4a_7c15u64;
let random: Vec<u8> = (0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x % 7) as u8
})
.collect();
check(&random);
let periodic: Vec<u8> = (0..n).map(|i| b"abc"[i % 3]).collect();
check(&periodic);
}
#[test]
fn segmented_walks_on_any_length() {
let mut x = 7u64;
for n in [
1,
2,
3,
31,
32,
33,
100,
1000,
MIN_SEGMENTED - 1,
MIN_SEGMENTED,
MIN_SEGMENTED + 1,
] {
let ll: Vec<u8> = (0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x % 3) as u8
})
.collect();
let mut counts = [0u32; 256];
for &c in &ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(&ll, &counts, &mut tt);
let (mut plain, mut out, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
for orig_ptr in [0, n as u32 / 2, n as u32 - 1] {
walk(&tt, orig_ptr, &mut plain);
walk_segments_room(&mut tt, orig_ptr, &mut out, &mut seg);
assert!(plain == out, "room walk, n {n}");
walk_segments_flagged(&mut tt, orig_ptr, &mut out, &mut seg);
assert!(plain == out, "flagged walk, n {n}");
for segments in [1, 2, 33, 256] {
let what = format!("n {n}, {segments} segments");
walk_segments_room_with::<32, true>(
&mut tt, orig_ptr, &mut out, &mut seg, segments,
);
assert!(plain == out, "room walk, {what}");
check_segments(&tt, &seg, &what);
walk_segments_room_with::<5, false>(
&mut tt, orig_ptr, &mut out, &mut seg, segments,
);
assert!(plain == out, "room walk, 5 lanes, {what}");
check_segments(&tt, &seg, &what);
walk_segments_flagged_with::<32, true>(
&mut tt, orig_ptr, &mut out, &mut seg, segments,
);
assert!(plain == out, "flagged walk, {what}");
check_segments(&tt, &seg, &what);
walk_segments_flagged_with::<7, false>(
&mut tt, orig_ptr, &mut out, &mut seg, segments,
);
assert!(plain == out, "flagged walk, 7 lanes, {what}");
check_segments(&tt, &seg, &what);
}
assert!(tt.0.iter().all(|&e| e & START == 0));
}
}
}
#[test]
fn miri_room_walk_handoffs() {
for seed in [170_778u64, 8_824] {
let mut x = seed.wrapping_mul(0x9e37_79b9_7f4a_7c15) | 1;
let mut rnd = move |m: u64| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x % m
};
let n = 200 + rnd(4000) as usize;
let alphabet = 1 + rnd(if seed % 2 == 0 { 3 } else { 256 });
let ll: Vec<u8> = (0..n).map(|_| (rnd(alphabet) * 7) as u8).collect();
let orig_ptr = rnd(n as u64) as u32;
let mut counts = [0u32; 256];
for &c in &ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(&ll, &counts, &mut tt);
let (mut plain, mut out, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
walk(&tt, orig_ptr, &mut plain);
walk_segments_room_with::<32, true>(&mut tt, orig_ptr, &mut out, &mut seg, 256);
assert!(plain == out, "room walk, seed {seed}");
check_segments(&tt, &seg, "room walk");
walk_segments_flagged_with::<32, true>(&mut tt, orig_ptr, &mut out, &mut seg, 256);
assert!(plain == out, "flagged walk, seed {seed}");
check_segments(&tt, &seg, "flagged walk");
}
assert_reached();
let n = 600;
for ll in [vec![3u8; n], (0..n).map(|i| (i % 2) as u8).collect()] {
let mut counts = [0u32; 256];
for &c in &ll {
counts[c as usize] += 1;
}
let mut tt = TVector::new();
build(&ll, &counts, &mut tt);
let (mut plain, mut out, mut seg) = (Vec::new(), Vec::new(), SegScratch::default());
walk(&tt, 1, &mut plain);
for segments in [33, 256] {
walk_segments_room_with::<32, true>(&mut tt, 1, &mut out, &mut seg, segments);
assert!(plain == out, "room walk, {segments} segments");
check_segments(&tt, &seg, "room walk");
walk_segments_flagged_with::<32, true>(&mut tt, 1, &mut out, &mut seg, segments);
assert!(plain == out, "flagged walk, {segments} segments");
check_segments(&tt, &seg, "flagged walk");
}
}
}
}