use alloc::vec::Vec;
use core::cmp::Ordering;
#[cfg(feature = "std")]
type CachedDescription = std::sync::OnceLock<Option<Vec<u8>>>;
use crate::{
bit_io::BitWriter,
fse::fse_encoder::{self, FSEEncoder},
histogram,
};
pub(crate) struct HuffmanEncoder<'output, 'table, V: AsMut<Vec<u8>>> {
table: &'table HuffmanTable,
writer: &'output mut BitWriter<V>,
}
impl<V: AsMut<Vec<u8>>> HuffmanEncoder<'_, '_, V> {
pub fn new<'o, 't>(
table: &'t HuffmanTable,
writer: &'o mut BitWriter<V>,
) -> HuffmanEncoder<'o, 't, V> {
HuffmanEncoder { table, writer }
}
pub fn encode(&mut self, data: &[u8], with_table: bool) {
if with_table {
self.write_table();
}
Self::encode_stream(self.table, self.writer, data);
}
pub fn encode4x(&mut self, data: &[u8], with_table: bool) {
assert!(
data.len() >= 12,
"upstream zstd HUF_compress4X requires srcSize >= 12"
);
if with_table {
self.write_table();
}
let jt_bit_idx = self.writer.index();
self.writer.write_bits(0u16, 16);
self.writer.write_bits(0u16, 16);
self.writer.write_bits(0u16, 16);
let segment_size = data.len().div_ceil(4);
let segments = [
&data[..segment_size],
&data[segment_size..segment_size * 2],
&data[segment_size * 2..segment_size * 3],
&data[segment_size * 3..],
];
let table_log = self.table.table_log();
let packed_codes = self.table.packed_codes();
let mut stream_sizes = [0u16; 3];
for (i, segment) in segments.iter().enumerate() {
let bytes_written = self.writer.with_aligned_output_mut(|output| {
let dst_capacity = Self::huf_tight_compress_bound(segment.len(), table_log);
let mut bit_c = super::huf_cstream::HufCStream::new(output, dst_capacity).expect(
"HufCStream::new returned None — dst_capacity (from \
huf_tight_compress_bound) must be > 8 for a non-empty segment",
);
Self::encode_one_stream(packed_codes, &mut bit_c, segment, table_log);
bit_c.close()
});
assert!(
bytes_written > 0,
"HufCStream::close overflowed dst_capacity for segment {i}; \
huf_tight_compress_bound is undersized for table_log={table_log}",
);
if i < 3 {
assert!(
bytes_written <= u16::MAX as usize,
"Huffman stream exceeded 64 KiB jump-table limit",
);
stream_sizes[i] = bytes_written as u16;
}
}
self.writer.change_bits(jt_bit_idx, stream_sizes[0], 16);
self.writer
.change_bits(jt_bit_idx + 16, stream_sizes[1], 16);
self.writer
.change_bits(jt_bit_idx + 32, stream_sizes[2], 16);
}
#[inline(always)]
fn huf_tight_compress_bound(src_size: usize, table_log: u32) -> usize {
((src_size * table_log as usize) >> 3) + 8 + 16
}
fn encode_one_stream(
table: &[u64],
bit_c: &mut super::huf_cstream::HufCStream<'_>,
data: &[u8],
table_log: u32,
) {
match table_log {
11 => Self::encode_one_stream_unrolled::<5, true, false>(table, bit_c, data),
10 => Self::encode_one_stream_unrolled::<5, true, true>(table, bit_c, data),
9 => Self::encode_one_stream_unrolled::<6, true, false>(table, bit_c, data),
8 => Self::encode_one_stream_unrolled::<7, true, false>(table, bit_c, data),
7 => Self::encode_one_stream_unrolled::<8, true, false>(table, bit_c, data),
_ => Self::encode_one_stream_unrolled::<4, false, false>(table, bit_c, data),
}
}
fn encode_one_stream_unrolled<
const K_UNROLL: usize,
const K_FAST_FLUSH: bool,
const K_LAST_FAST: bool,
>(
table: &[u64],
bit_c: &mut super::huf_cstream::HufCStream<'_>,
data: &[u8],
) {
bit_c.encode_unrolled::<K_UNROLL, K_FAST_FLUSH, K_LAST_FAST>(table, data);
}
fn encode_stream<VV: AsMut<Vec<u8>>>(
table: &HuffmanTable,
writer: &mut BitWriter<VV>,
data: &[u8],
) {
for symbol in data.iter().rev() {
let (code, num_bits) = table.codes[*symbol as usize];
debug_assert!(num_bits > 0);
writer.write_bits(code, num_bits as usize);
}
let bits_to_fill = writer.misaligned();
if bits_to_fill == 0 {
writer.write_bits(1u32, 8);
} else {
writer.write_bits(1u32, bits_to_fill);
}
}
pub(super) fn weights(&self) -> Vec<u8> {
self.table.weights()
}
fn write_table(&mut self) {
#[cfg(feature = "std")]
{
if let Some(cached) = self.table.cached_encoded_weight_description.get() {
if let Some(fse_description) = cached.as_deref() {
self.writer.write_bits(fse_description.len() as u8, 8);
self.writer.append_bytes(fse_description);
return;
}
let weights = self.weights();
let weights = &weights[..weights.len() - 1];
Self::write_raw_weight_description(self.writer, weights);
return;
}
let weights = self.weights();
let weights = &weights[..weights.len() - 1];
if let Some(fse_description) = self
.table
.cached_encoded_weight_description_with_weights(weights)
{
self.writer.write_bits(fse_description.len() as u8, 8);
self.writer.append_bytes(fse_description);
} else {
Self::write_raw_weight_description(self.writer, weights);
}
}
#[cfg(not(feature = "std"))]
{
let weights = self.weights();
let weights = &weights[..weights.len() - 1];
if let Some(fse_description) = Self::encode_weight_description(weights) {
self.writer.write_bits(fse_description.len() as u8, 8);
self.writer.append_bytes(&fse_description);
} else {
Self::write_raw_weight_description(self.writer, weights);
}
}
}
fn encode_weight_description(weights: &[u8]) -> Option<Vec<u8>> {
if weights.len() <= 2 {
return None;
}
let mut counts = [0usize; 13];
for &weight in weights {
counts[weight as usize] += 1;
}
let max_count = counts.iter().copied().max().unwrap_or(0);
if max_count == weights.len() || max_count <= 1 {
return None;
}
let mut encoded = Vec::with_capacity(weights.len());
{
let mut writer = BitWriter::from(&mut encoded);
let mut encoder = FSEEncoder::new(
fse_encoder::build_table_from_symbol_counts(&counts, 6, false),
&mut writer,
);
encoder.encode_interleaved(weights);
writer.flush();
}
let raw_description_is_representable = weights.len() <= 128;
let raw_description_bytes = weights.len() / 2;
if encoded.len() > 1
&& (encoded.len() < raw_description_bytes || !raw_description_is_representable)
{
if encoded.len() >= 128 {
return None;
}
Some(encoded)
} else {
None
}
}
fn write_raw_weight_description<VV: AsMut<Vec<u8>>>(
writer: &mut BitWriter<VV>,
weights: &[u8],
) {
assert!(weights.len() <= 128);
writer.write_bits(weights.len() as u8 + 127, 8);
let pairs = weights.chunks_exact(2);
let remainder = pairs.remainder();
for pair in pairs {
let weight1 = pair[0];
let weight2 = pair[1];
assert!(weight1 < 16);
assert!(weight2 < 16);
writer.write_bits(weight2, 4);
writer.write_bits(weight1, 4);
}
if !remainder.is_empty() {
let weight = remainder[0];
assert!(weight < 16);
writer.write_bits(weight << 4, 8);
}
}
}
pub struct HuffmanTable {
codes: Vec<(u32, u8)>,
packed_codes: Vec<u64>,
table_log: u32,
#[cfg(feature = "std")]
cached_encoded_weight_description: CachedDescription,
}
impl Clone for HuffmanTable {
fn clone(&self) -> Self {
Self {
codes: self.codes.clone(),
packed_codes: self.packed_codes.clone(),
table_log: self.table_log,
#[cfg(feature = "std")]
cached_encoded_weight_description: self.cached_encoded_weight_description.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
self.codes.clone_from(&source.codes);
self.packed_codes.clone_from(&source.packed_codes);
self.table_log = source.table_log;
#[cfg(feature = "std")]
{
self.cached_encoded_weight_description =
source.cached_encoded_weight_description.clone();
}
}
}
#[cfg(feature = "bench_internals")]
pub static FORCE_CHEAP_HUF: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
#[cfg(feature = "bench_internals")]
pub fn set_force_cheap_huf(on: bool) {
FORCE_CHEAP_HUF.store(on, core::sync::atomic::Ordering::Relaxed);
}
impl HuffmanTable {
pub fn heap_size(&self) -> usize {
self.codes.capacity() * core::mem::size_of::<(u32, u8)>()
+ self.packed_codes.capacity() * core::mem::size_of::<u64>()
}
pub fn build_from_data(data: &[u8]) -> Self {
let mut counts = [0; 256];
let (max_symbol, _) = histogram::count_bytes(data, &mut counts);
Self::build_from_counts(&counts[..=max_symbol])
}
pub fn build_from_counts_gated(counts: &[usize], use_search: bool) -> Self {
if use_search {
Self::build_from_counts(counts)
} else {
Self::build_from_weights(&build_limited_weights(counts, cheap_huf_table_log(counts)))
}
}
pub fn build_from_counts(counts: &[usize]) -> Self {
assert!(counts.len() <= 256);
let symbol_cardinality = counts.iter().filter(|&&count| count > 0).count();
if symbol_cardinality <= 1 {
return Self::build_from_weights(&build_limited_weights(counts, 11));
}
#[cfg(feature = "bench_internals")]
if FORCE_CHEAP_HUF.load(core::sync::atomic::Ordering::Relaxed) {
return Self::build_from_weights(&build_limited_weights(
counts,
cheap_huf_table_log(counts),
));
}
let min_table_log = symbol_cardinality.ilog2() as usize + 1;
let mut best_size = usize::MAX - 1;
let mut work: Vec<HuffNode> = Vec::with_capacity(2 * counts.len());
let mut cand: Vec<usize> = Vec::with_capacity(counts.len());
let mut best: Vec<usize> = Vec::with_capacity(counts.len());
let mut best_found = false;
let mut weights_u8 = [0u8; 256];
let leaves = build_huffman_leaf_depths(counts);
for table_log in min_table_log..=11 {
if !limited_weights_into(&leaves, counts.len(), table_log, &mut work, &mut cand) {
cand = legacy_distributed_weights(counts);
}
let weights = &cand;
if !huffman_weight_sum_is_power_of_two(weights) {
continue;
}
let weight_sum: usize = weights
.iter()
.copied()
.filter(|&w| w > 0)
.map(|w| 1usize << (w - 1))
.sum();
let wtable_log = highest_bit_set(weight_sum) - 1;
let min_positive_weight = weights
.iter()
.copied()
.filter(|&w| w > 0)
.min()
.unwrap_or(1);
let max_bits = wtable_log + 1 - min_positive_weight;
if max_bits < table_log && table_log > min_table_log {
break;
}
let trimmed_len = weights.len().saturating_sub(1);
for (slot, &w) in weights_u8[..trimmed_len].iter_mut().zip(weights.iter()) {
debug_assert!(w <= u8::MAX as usize);
*slot = w as u8;
}
let trimmed = &weights_u8[..trimmed_len];
let Some(desc_size) = cheap_desc_size_proxy(trimmed) else {
continue;
};
let estimate_bits: usize = weights
.iter()
.zip(counts.iter())
.filter(|&(&w, _)| w > 0)
.map(|(&w, &count)| (wtable_log + 1 - w) * count)
.sum();
let payload = estimate_bits.div_ceil(8) + usize::from(estimate_bits.is_multiple_of(8));
let new_size = payload + desc_size;
if new_size > best_size + 1 {
break;
}
if new_size < best_size {
best_size = new_size;
core::mem::swap(&mut best, &mut cand);
best_found = true;
}
}
if best_found {
Self::build_from_weights(&best)
} else {
Self::build_from_weights(&build_limited_weights(counts, 11))
}
}
pub(crate) fn estimate_compressed_size(&self, data: &[u8]) -> Option<usize> {
let mut bits = 0usize;
for &symbol in data {
let (_, num_bits) = *self.codes.get(symbol as usize)?;
if num_bits == 0 {
return None;
}
bits += num_bits as usize;
}
let bytes = bits.div_ceil(8);
Some(bytes + usize::from(bits.is_multiple_of(8)))
}
pub(crate) fn try_table_description_size(&self) -> Option<usize> {
#[cfg(feature = "std")]
{
if let Some(fse_description) = self.cached_encoded_weight_description() {
return Some(fse_description.len() + 1);
}
let raw_weights_len = self.codes.len().saturating_sub(1);
if raw_weights_len <= 128 {
Some(raw_weights_len.div_ceil(2) + 1)
} else {
None
}
}
#[cfg(not(feature = "std"))]
{
let weights = self.weights();
let weights = &weights[..weights.len() - 1];
if let Some(fse_description) =
HuffmanEncoder::<Vec<u8>>::encode_weight_description(weights)
{
return Some(fse_description.len() + 1);
}
if weights.len() <= 128 {
Some(weights.len().div_ceil(2) + 1)
} else {
None
}
}
}
pub(crate) fn writeable_table_description_size(&self) -> Option<usize> {
self.try_table_description_size()
}
fn weights(&self) -> Vec<u8> {
let max = self.codes.iter().map(|(_, nb)| nb).max().unwrap();
self.codes
.iter()
.copied()
.map(|(_, nb)| if nb == 0 { 0 } else { max - nb + 1 })
.collect::<Vec<u8>>()
}
#[cfg(feature = "std")]
fn cached_encoded_weight_description(&self) -> Option<&[u8]> {
if let Some(cached) = self.cached_encoded_weight_description.get() {
return cached.as_deref();
}
let weights = self.weights();
let weights = &weights[..weights.len() - 1];
self.cached_encoded_weight_description_with_weights(weights)
}
#[cfg(feature = "std")]
fn cached_encoded_weight_description_with_weights(&self, weights: &[u8]) -> Option<&[u8]> {
self.cached_encoded_weight_description
.get_or_init(|| HuffmanEncoder::<Vec<u8>>::encode_weight_description(weights))
.as_deref()
}
pub(crate) fn estimate_compressed_size_from_counts(&self, counts: &[usize]) -> usize {
let bits = self
.codes
.iter()
.zip(counts.iter())
.map(|(&(_, bits), &count)| bits as usize * count)
.sum::<usize>();
bits.div_ceil(8) + usize::from(bits.is_multiple_of(8))
}
pub fn build_from_weights(weights: &[usize]) -> Self {
let weight_sum = weights
.iter()
.copied()
.filter(|&weight| weight > 0)
.map(|weight| 1 << (weight - 1))
.sum::<usize>();
if !weight_sum.is_power_of_two() {
panic!("This is an internal error");
}
let table_log = highest_bit_set(weight_sum) - 1;
let mut table = HuffmanTable {
codes: alloc::vec![(0, 0); weights.len()],
packed_codes: alloc::vec![0u64; weights.len()],
table_log: table_log as u32,
#[cfg(feature = "std")]
cached_encoded_weight_description: CachedDescription::new(),
};
let mut nb_per_rank = [0u16; 13];
for &weight in weights {
if weight > 0 {
let nb_bits = table_log + 1 - weight;
nb_per_rank[nb_bits] += 1;
}
}
let mut val_per_rank = [0u16; 13];
let mut min = 0u16;
for nb_bits in (1..=table_log).rev() {
val_per_rank[nb_bits] = min;
min = min.wrapping_add(nb_per_rank[nb_bits]) >> 1;
}
for (symbol, &weight) in weights.iter().enumerate() {
if weight == 0 {
continue;
}
let nb_bits = table_log + 1 - weight;
let value = val_per_rank[nb_bits];
val_per_rank[nb_bits] += 1;
table.codes[symbol] = (value as u32, nb_bits as u8);
table.packed_codes[symbol] =
super::huf_cstream::pack_huf_celt(value as u32, nb_bits as u8);
}
table
}
#[inline(always)]
pub(crate) fn packed_codes(&self) -> &[u64] {
&self.packed_codes
}
#[inline(always)]
pub(crate) fn table_log(&self) -> u32 {
self.table_log
}
pub fn can_encode(&self, other: &Self) -> Option<usize> {
if other.codes.len() > self.codes.len() {
return None;
}
let mut sum = 0;
for ((_, other_num_bits), (_, self_num_bits)) in other.codes.iter().zip(self.codes.iter()) {
if *other_num_bits != 0 && *self_num_bits == 0 {
return None;
}
sum += other_num_bits.abs_diff(*self_num_bits) as usize;
}
Some(sum)
}
pub(crate) fn num_bits_for_symbol(&self, symbol: u8) -> Option<u8> {
self.codes
.get(symbol as usize)
.and_then(|&(_, bits)| if bits > 0 { Some(bits) } else { None })
}
}
fn cheap_desc_size_proxy(weights: &[u8]) -> Option<usize> {
let n = weights.len();
if n == 0 {
return None;
}
let raw_ok = n <= 128;
let raw_size = n.div_ceil(2) + 1;
let mut hist = [0u32; 13];
for &w in weights {
debug_assert!(
(w as usize) < hist.len(),
"huffman weights are bounded to 0..12 by `build_limited_weights`"
);
hist[w as usize] += 1;
}
let total = n as u32;
let mut bits: u64 = 0;
for &c in &hist {
if c == 0 {
continue;
}
let ratio = total.div_ceil(c);
let bits_per_symbol = if ratio <= 1 {
1
} else {
32 - (ratio - 1).leading_zeros()
};
bits += (c as u64) * (bits_per_symbol as u64);
}
let fse_payload_bytes = bits.div_ceil(8) as usize;
const FSE_HEADER_OVERHEAD_BYTES: usize = 8;
let fse_size = fse_payload_bytes + FSE_HEADER_OVERHEAD_BYTES;
let fse_ok = fse_size <= 128;
match (fse_ok, raw_ok) {
(true, true) => Some(fse_size.min(raw_size)),
(true, false) => Some(fse_size),
(false, true) => Some(raw_size),
(false, false) => None,
}
}
fn huffman_weight_sum_is_power_of_two(weights: &[usize]) -> bool {
let sum = weights
.iter()
.copied()
.filter(|&weight| weight > 0)
.map(|weight| 1usize << (weight - 1))
.sum::<usize>();
sum.is_power_of_two()
}
#[derive(Clone)]
struct HuffNode {
count: usize,
symbol: usize,
parent: Option<usize>,
nb_bits: usize,
}
fn build_huffman_leaf_depths(counts: &[usize]) -> Vec<HuffNode> {
let leaf_count = counts.iter().filter(|&&count| count > 0).count();
let mut nodes: Vec<HuffNode> = Vec::with_capacity((2 * leaf_count).max(1));
if leaf_count == 0 {
return nodes;
}
if leaf_count == 1 {
let (symbol, &count) = counts.iter().enumerate().find(|&(_, &c)| c > 0).unwrap();
nodes.push(HuffNode {
count,
symbol,
parent: None,
nb_bits: 0,
});
return nodes;
}
const BUCKETS: usize = 192;
const LOG_BUCKETS_BEGIN: usize = 158; const DISTINCT_CUTOFF: usize = 165; let bucket_of = |count: usize| -> usize {
if count < DISTINCT_CUTOFF {
count
} else {
(highest_bit_set(count) - 1) + LOG_BUCKETS_BEGIN
}
};
let mut bucket_size = [0u32; BUCKETS];
for &count in counts {
if count > 0 {
bucket_size[bucket_of(count)] += 1;
}
}
let mut start = [0u32; BUCKETS];
let mut acc = 0u32;
for bucket in (0..BUCKETS).rev() {
start[bucket] = acc;
acc += bucket_size[bucket];
}
let mut cursor = start;
nodes.resize(
leaf_count,
HuffNode {
count: 0,
symbol: 0,
parent: None,
nb_bits: 0,
},
);
for (symbol, &count) in counts.iter().enumerate() {
if count == 0 {
continue;
}
let bucket = bucket_of(count);
let pos = cursor[bucket] as usize;
cursor[bucket] += 1;
nodes[pos] = HuffNode {
count,
symbol,
parent: None,
nb_bits: 0,
};
}
for bucket in DISTINCT_CUTOFF..BUCKETS {
let lo = start[bucket] as usize;
let hi = cursor[bucket] as usize;
if hi - lo > 1 {
nodes[lo..hi].sort_by(|left, right| match right.count.cmp(&left.count) {
Ordering::Equal => left.symbol.cmp(&right.symbol),
other => other,
});
}
}
nodes.resize(
2 * leaf_count - 1,
HuffNode {
count: usize::MAX,
symbol: usize::MAX,
parent: None,
nb_bits: 0,
},
);
let mut low_s = leaf_count as isize - 1;
let mut low_n = leaf_count;
let node_root = leaf_count + (leaf_count - 1) - 1;
let mut node_nb = leaf_count;
nodes[node_nb].count = nodes[low_s as usize].count + nodes[(low_s - 1) as usize].count;
nodes[node_nb].symbol = nodes[(low_s - 1) as usize]
.symbol
.min(nodes[low_s as usize].symbol);
nodes[low_s as usize].parent = Some(node_nb);
nodes[(low_s - 1) as usize].parent = Some(node_nb);
node_nb += 1;
low_s -= 2;
while node_nb <= node_root {
let first = {
let leaf_count = if low_s >= 0 {
nodes[low_s as usize].count
} else {
usize::MAX
};
let node_count = nodes[low_n].count;
if leaf_count < node_count {
let idx = low_s as usize;
low_s -= 1;
idx
} else {
let idx = low_n;
low_n += 1;
idx
}
};
let second = {
let leaf_count = if low_s >= 0 {
nodes[low_s as usize].count
} else {
usize::MAX
};
let node_count = nodes[low_n].count;
if leaf_count < node_count {
let idx = low_s as usize;
low_s -= 1;
idx
} else {
let idx = low_n;
low_n += 1;
idx
}
};
nodes[node_nb].count = nodes[first].count + nodes[second].count;
nodes[node_nb].symbol = nodes[first].symbol.min(nodes[second].symbol);
nodes[first].parent = Some(node_nb);
nodes[second].parent = Some(node_nb);
node_nb += 1;
}
for leaf_idx in 0..leaf_count {
let mut depth = 0usize;
let mut parent = nodes[leaf_idx].parent;
while let Some(parent_idx) = parent {
depth += 1;
parent = nodes[parent_idx].parent;
}
nodes[leaf_idx].nb_bits = depth;
}
nodes.truncate(leaf_count);
nodes
}
fn limited_weights_into(
leaves: &[HuffNode],
counts_len: usize,
max_nb_bits: usize,
work: &mut Vec<HuffNode>,
out: &mut Vec<usize>,
) -> bool {
out.clear();
out.resize(counts_len, 0);
if leaves.len() <= 1 {
if let Some(leaf) = leaves.first() {
out[leaf.symbol] = 1;
}
return true;
}
work.clear();
work.extend_from_slice(leaves);
enforce_max_height(work, max_nb_bits);
if work.iter().any(|leaf| leaf.nb_bits > max_nb_bits) {
return false;
}
let kraft_sum = work
.iter()
.map(|leaf| 1usize << (max_nb_bits - leaf.nb_bits))
.sum::<usize>();
if kraft_sum != 1usize << max_nb_bits {
return false;
}
for leaf in work.iter() {
out[leaf.symbol] = max_nb_bits - leaf.nb_bits + 1;
}
true
}
fn cheap_huf_table_log(counts: &[usize]) -> usize {
let total: usize = counts.iter().sum();
if total <= 1 {
return 11;
}
let max_symbol = counts.iter().rposition(|&c| c > 0).unwrap_or(0);
crate::fse::fse_encoder::optimal_table_log(11, total, max_symbol, 1) as usize
}
fn build_limited_weights(counts: &[usize], max_nb_bits: usize) -> Vec<usize> {
let leaves = build_huffman_leaf_depths(counts);
let mut work = Vec::new();
let mut out = Vec::new();
if limited_weights_into(&leaves, counts.len(), max_nb_bits, &mut work, &mut out) {
out
} else {
legacy_distributed_weights(counts)
}
}
fn legacy_distributed_weights(counts: &[usize]) -> Vec<usize> {
let zeros = counts.iter().filter(|x| **x == 0).count();
let mut weights = distribute_weights(counts.len() - zeros);
let limit = weights.len().ilog2() as usize + 2;
redistribute_weights(&mut weights, limit);
weights.reverse();
let mut counts_sorted = counts.iter().enumerate().collect::<Vec<_>>();
counts_sorted.sort_by_key(|(_, c1)| *c1);
let mut weights_distributed = alloc::vec![0; counts.len()];
for (idx, count) in counts_sorted {
if *count == 0 {
weights_distributed[idx] = 0;
} else {
weights_distributed[idx] = weights.pop().unwrap();
}
}
weights_distributed
}
fn enforce_max_height(nodes: &mut [HuffNode], target_nb_bits: usize) {
let Some(largest_bits) = nodes.iter().map(|node| node.nb_bits).max() else {
return;
};
if largest_bits <= target_nb_bits {
return;
}
let base_cost = 1usize << (largest_bits - target_nb_bits);
let mut total_cost = 0isize;
let mut n = nodes.len() - 1;
while nodes[n].nb_bits > target_nb_bits {
total_cost += (base_cost - (1usize << (largest_bits - nodes[n].nb_bits))) as isize;
nodes[n].nb_bits = target_nb_bits;
if n == 0 {
break;
}
n -= 1;
}
while n > 0 && nodes[n].nb_bits == target_nb_bits {
n -= 1;
}
total_cost >>= largest_bits - target_nb_bits;
const NO_SYMBOL: usize = usize::MAX;
debug_assert!(
target_nb_bits + 2 <= 14,
"target_nb_bits {target_nb_bits} exceeds HUF_TABLELOG_MAX scratch bound"
);
let mut rank_last = [NO_SYMBOL; 14];
let mut current_nb_bits = target_nb_bits;
for pos in (0..=n).rev() {
if nodes[pos].nb_bits >= current_nb_bits {
continue;
}
current_nb_bits = nodes[pos].nb_bits;
rank_last[target_nb_bits - current_nb_bits] = pos;
}
while total_cost > 0 {
let mut bits_to_decrease = (total_cost as usize).ilog2() as usize + 1;
while bits_to_decrease > 1 {
let high_pos = rank_last[bits_to_decrease];
let low_pos = rank_last[bits_to_decrease - 1];
if high_pos == NO_SYMBOL {
bits_to_decrease -= 1;
continue;
}
if low_pos == NO_SYMBOL {
break;
}
if nodes[high_pos].count <= 2 * nodes[low_pos].count {
break;
}
bits_to_decrease -= 1;
}
while bits_to_decrease <= target_nb_bits && rank_last[bits_to_decrease] == NO_SYMBOL {
bits_to_decrease += 1;
}
if bits_to_decrease > target_nb_bits {
return;
}
let pos = rank_last[bits_to_decrease];
total_cost -= 1isize << (bits_to_decrease - 1);
nodes[pos].nb_bits += 1;
if rank_last[bits_to_decrease - 1] == NO_SYMBOL {
rank_last[bits_to_decrease - 1] = pos;
}
if pos == 0 {
rank_last[bits_to_decrease] = NO_SYMBOL;
} else {
let next = pos - 1;
rank_last[bits_to_decrease] =
if nodes[next].nb_bits == target_nb_bits - bits_to_decrease {
next
} else {
NO_SYMBOL
};
}
}
while total_cost < 0 {
if rank_last[1] == NO_SYMBOL {
while n > 0 && nodes[n].nb_bits == target_nb_bits {
n -= 1;
}
if nodes[n].nb_bits == target_nb_bits || n + 1 >= nodes.len() {
break;
}
nodes[n + 1].nb_bits -= 1;
rank_last[1] = n + 1;
total_cost += 1;
continue;
}
if rank_last[1] + 1 >= nodes.len() {
break;
}
nodes[rank_last[1] + 1].nb_bits -= 1;
rank_last[1] += 1;
total_cost += 1;
}
}
fn highest_bit_set(x: usize) -> usize {
assert!(x > 0);
usize::BITS as usize - x.leading_zeros() as usize
}
fn distribute_weights(amount: usize) -> Vec<usize> {
assert!(amount >= 2);
assert!(amount <= 256);
let mut weights = Vec::new();
weights.push(1);
weights.push(1);
let mut target_weight = 1;
let mut weight_counter = 2;
while weights.len() < amount {
let mut add_new = 1 << (weight_counter - target_weight);
let available_space = amount - weights.len();
if add_new > available_space {
target_weight = weight_counter;
add_new = 1;
}
for _ in 0..add_new {
weights.push(target_weight);
}
weight_counter += 1;
}
assert_eq!(amount, weights.len());
weights
}
fn redistribute_weights(weights: &mut [usize], max_num_bits: usize) {
let weight_sum_log = weights
.iter()
.copied()
.map(|x| 1 << x)
.sum::<usize>()
.ilog2() as usize;
if weight_sum_log < max_num_bits {
return;
}
let decrease_weights_by = weight_sum_log - max_num_bits + 1;
let mut added_weights = 0;
for weight in weights.iter_mut() {
if *weight < decrease_weights_by {
for add in *weight..decrease_weights_by {
added_weights += 1 << add;
}
*weight = decrease_weights_by;
}
}
while added_weights > 0 {
let mut current_idx = 0;
let mut current_weight = 0;
for (idx, weight) in weights.iter().copied().enumerate() {
if 1 << (weight - 1) > added_weights {
break;
}
if weight > current_weight {
current_weight = weight;
current_idx = idx;
}
}
added_weights -= 1 << (current_weight - 1);
weights[current_idx] -= 1;
}
if weights[0] > 1 {
let offset = weights[0] - 1;
for weight in weights.iter_mut() {
*weight -= offset;
}
}
}
#[cfg(feature = "bench_internals")]
pub(crate) fn huf_weight_description_for_test(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
let table = HuffmanTable::build_from_data(data);
let mut weights = {
let mut out = Vec::new();
let mut writer = BitWriter::from(&mut out);
let encoder = HuffmanEncoder::new(&table, &mut writer);
encoder.weights()
};
weights.pop();
let encoded = HuffmanEncoder::<Vec<u8>>::encode_weight_description(&weights)
.expect("expected FSE weights");
let mut description = Vec::with_capacity(encoded.len() + 1);
description.push(encoded.len() as u8);
description.extend_from_slice(&encoded);
(description, weights)
}
#[cfg(feature = "bench_internals")]
pub(crate) fn huf_encode4x_for_test(data: &[u8]) -> Vec<u8> {
let table = HuffmanTable::build_from_data(data);
let mut encoded = Vec::new();
{
let mut writer = BitWriter::from(&mut encoded);
let mut encoder = HuffmanEncoder::new(&table, &mut writer);
encoder.encode4x(data, true);
writer.flush();
}
encoded
}
#[cfg(test)]
mod tests;