use crate::compressor::core::error::CoreError as EntropyError;
pub(crate) fn count(
count: &mut [u32],
max_symbol_value: &mut u32,
src: &[u8],
check: bool,
) -> Result<u32, EntropyError> {
let bound = *max_symbol_value as usize;
let count = &mut count[..=bound];
if src.len() < PARALLEL_THRESHOLD {
let result = count_simple(count, max_symbol_value, src);
debug_assert!(check || result.is_ok(), "trusted input above its bound");
return result;
}
let full = count_parallel(src);
let mut max = 255usize;
while full[max] == 0 {
max -= 1;
}
if max > bound {
debug_assert!(check, "trusted input above its bound");
return Err(EntropyError::MaxSymbolValueTooSmall);
}
count.copy_from_slice(&full[..=bound]);
*max_symbol_value = max as u32;
Ok(full.iter().copied().max().unwrap_or(0))
}
const PARALLEL_THRESHOLD: usize = 1500;
fn count_simple(
count: &mut [u32],
max_symbol_value: &mut u32,
src: &[u8],
) -> Result<u32, EntropyError> {
count.fill(0);
if let Ok(full) = <&mut [u32; 256]>::try_from(&mut *count) {
for &byte in src {
full[usize::from(byte)] += 1;
}
} else {
for &byte in src {
let Some(cell) = count.get_mut(usize::from(byte)) else {
return Err(EntropyError::MaxSymbolValueTooSmall);
};
*cell += 1;
}
}
let Some(max) = count.iter().rposition(|&c| c != 0) else {
*max_symbol_value = 0;
return Ok(0);
};
*max_symbol_value = max as u32;
Ok(count[..=max].iter().copied().max().unwrap_or(0))
}
fn count_parallel(src: &[u8]) -> [u32; 256] {
let mut tables = [[0u32; 256]; 4];
let (words, tail) = src.as_chunks::<4>();
for word in words {
tables[0][usize::from(word[0])] += 1;
tables[1][usize::from(word[1])] += 1;
tables[2][usize::from(word[2])] += 1;
tables[3][usize::from(word[3])] += 1;
}
for &byte in tail {
tables[0][usize::from(byte)] += 1;
}
let [mut total, second, third, fourth] = tables;
for (symbol, cell) in total.iter_mut().enumerate() {
*cell += second[symbol] + third[symbol] + fourth[symbol];
}
total
}
pub(crate) fn add(count: &mut [u32; 256], src: &[u8]) {
for &byte in src {
count[usize::from(byte)] += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_report_the_largest_symbol_and_count() {
let mut counts = [0u32; 256];
let mut max = 255;
let largest = count(&mut counts, &mut max, b"aabbbz", true).expect("count");
assert_eq!(largest, 3);
assert_eq!(max, u32::from(b'z'));
assert_eq!(counts[usize::from(b'b')], 3);
}
#[test]
fn empty_input_resets_the_bound() {
let mut counts = [7u32; 8];
let mut max = 7;
assert_eq!(count(&mut counts, &mut max, &[], true), Ok(0));
assert_eq!(max, 0);
assert_eq!(counts, [0; 8]);
}
#[test]
fn a_symbol_above_a_checked_bound_is_an_error() {
let mut counts = [0u32; 4];
let mut max = 3;
assert_eq!(
count(&mut counts, &mut max, &[1, 9], true),
Err(EntropyError::MaxSymbolValueTooSmall)
);
}
fn reference(src: &[u8]) -> ([u32; 256], u32, u32) {
let mut full = [0u32; 256];
for &b in src {
full[usize::from(b)] += 1;
}
let max = full.iter().rposition(|&c| c != 0).unwrap_or(0) as u32;
(full, max, full.iter().copied().max().unwrap_or(0))
}
#[test]
fn simple_and_interleaved_counts_match_a_plain_count() {
let mut seed = 0x2545_F491_4F6C_DD1Du64;
for len in [1usize, 7, 1499, 1500, 1501, 1503, 4096, 10_007] {
for bound in [255u32, 52, 3] {
let src: alloc::vec::Vec<u8> = (0..len)
.map(|_| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed % u64::from(bound + 1)) as u8
})
.collect();
let (full, max, largest) = reference(&src);
let mut counts = alloc::vec![9u32; bound as usize + 1];
let mut got_max = bound;
assert_eq!(count(&mut counts, &mut got_max, &src, true), Ok(largest));
assert_eq!(got_max, max, "{len} {bound}");
assert_eq!(counts[..], full[..=bound as usize], "{len} {bound}");
}
}
}
#[test]
fn a_symbol_above_the_bound_is_an_error_on_both_paths() {
for len in [10usize, 2000] {
let mut src = alloc::vec![1u8; len];
src[len / 2] = 9;
let mut counts = [0u32; 4];
let mut max = 3;
assert_eq!(
count(&mut counts, &mut max, &src, true),
Err(EntropyError::MaxSymbolValueTooSmall)
);
}
}
#[test]
fn add_accumulates() {
let mut counts = [0u32; 256];
add(&mut counts, b"aa");
add(&mut counts, b"a");
assert_eq!(counts[usize::from(b'a')], 3);
}
}