use alloc::vec::Vec;
use super::FnEncoder;
use crate::compressor::core::data::Stream;
use crate::compressor::core::error::NodeError;
use crate::compressor::core::exec::NodeCx;
pub(crate) static MUX_LENGTHS: FnEncoder = FnEncoder(encode);
const SPLIT_POINT: i32 = crate::graph::params::mux_lengths::SPLIT_POINT;
const MATCH_LENGTH_BIAS: i32 = crate::graph::params::mux_lengths::MATCH_LENGTH_BIAS;
const MIN_MATCH_LENGTH_METADATA_ID: i32 = crate::graph::params::lz::MIN_MATCH_LENGTH_METADATA_ID;
#[inline(always)]
fn read<const W: usize>(bytes: &[u8], index: usize) -> u64 {
let mut word = [0u8; 8];
word[..W].copy_from_slice(&bytes[index * W..(index + 1) * W]);
u64::from_le_bytes(word)
}
#[inline(always)]
fn lengths<const W: usize>(ll: &[u8], ml: &[u8], index: usize, bias: u32) -> (u64, u64) {
(
read::<W>(ll, index),
read::<W>(ml, index).wrapping_sub(u64::from(bias)),
)
}
const fn masks(split: u32) -> (u64, u64) {
(
((1u32 << split) - 1) as u64,
((1u32 << (8 - split)) - 1) as u64,
)
}
fn compute_split(ll: &[u8], ml: &[u8], count: usize, width: usize, bias: u32) -> (u32, usize) {
match width {
1 => compute_split_w::<1>(ll, ml, count, bias),
2 => compute_split_w::<2>(ll, ml, count, bias),
4 => compute_split_w::<4>(ll, ml, count, bias),
_ => compute_split_w::<8>(ll, ml, count, bias),
}
}
fn compute_split_w<const W: usize>(ll: &[u8], ml: &[u8], count: usize, bias: u32) -> (u32, usize) {
let mut ll_stats = [0usize; 256];
let mut ml_stats = [0usize; 256];
for i in 0..count {
let (l, m) = lengths::<W>(ll, ml, i, bias);
ll_stats[l.min(255) as usize] += 1;
ml_stats[m.min(255) as usize] += 1;
}
let mut best_bits = 0;
let mut best = 2 * count + 1;
for bits in 1..8u32 {
let (ll_mask, ml_mask) = masks(bits);
let extra: usize = ll_stats[ll_mask as usize..].iter().sum::<usize>()
+ ml_stats[ml_mask as usize..].iter().sum::<usize>();
if extra < best {
best = extra;
best_bits = bits;
}
}
(best_bits, best)
}
fn mux(
ll: &[u8],
ml: &[u8],
count: usize,
width: usize,
split: u32,
bias: u32,
) -> (Vec<u8>, Vec<u8>) {
match width {
1 => mux_w::<1>(ll, ml, count, split, bias),
2 => mux_w::<2>(ll, ml, count, split, bias),
4 => mux_w::<4>(ll, ml, count, split, bias),
_ => mux_w::<8>(ll, ml, count, split, bias),
}
}
fn mux_w<const W: usize>(
ll: &[u8],
ml: &[u8],
count: usize,
split: u32,
bias: u32,
) -> (Vec<u8>, Vec<u8>) {
let (ll_mask, ml_mask) = masks(split);
let mut muxed = Vec::with_capacity(count);
let mut long = Vec::new();
for i in 0..count {
let (l, m) = lengths::<W>(ll, ml, i, bias);
let mut byte = 0u8;
if l >= ll_mask {
byte |= ll_mask as u8;
long.extend_from_slice(&(l - ll_mask).to_le_bytes()[..W]);
} else {
byte |= l as u8;
}
if m >= ml_mask {
byte |= (ml_mask << split) as u8;
long.extend_from_slice(&(m - ml_mask).to_le_bytes()[..W]);
} else {
byte |= (m << split) as u8;
}
muxed.push(byte);
}
(muxed, long)
}
fn encode<'a>(cx: &mut NodeCx<'_, 'a>, inputs: &[Stream<'a>]) -> Result<(), NodeError> {
let (ll_in, ml_in) = (&inputs[0], &inputs[1]);
let width = ll_in.width();
let count = ll_in.n_elts();
if width != ml_in.width() {
return Err(NodeError::InvalidInput(
"literal and match lengths must have same element width",
));
}
if count != ml_in.n_elts() {
return Err(NodeError::InvalidInput(
"literal and match lengths must have same element count",
));
}
let bias = match cx.int(MATCH_LENGTH_BIAS) {
Some(value) => value as u32,
None => ml_in
.metadata()
.iter()
.find(|&&(id, _)| id == MIN_MATCH_LENGTH_METADATA_ID)
.map_or(0, |&(_, value)| value as u32),
};
if bias > 15 {
return Err(NodeError::InvalidParam(
"match_length_bias must be in [0, 15]",
));
}
let (ll, ml) = (ll_in.bytes(), ml_in.bytes());
let split = match cx.int(SPLIT_POINT) {
Some(value) => {
let split = value as u32;
if split > 8 {
return Err(NodeError::InvalidParam("split_point must be in [0, 8]"));
}
split
}
None => compute_split(ll, ml, count, width, bias).0,
};
let (muxed, long) = mux(ll, ml, count, width, split, bias);
cx.send_header(&[(split | (bias << 4)) as u8])?;
cx.emit(0, 1, muxed)?;
cx.emit(1, width, long)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compressor::core::data::{Bytes, Type};
use alloc::vec::Vec;
fn le(values: &[u64], width: usize) -> Vec<u8> {
values
.iter()
.flat_map(|v| v.to_le_bytes()[..width].to_vec())
.collect()
}
#[test]
fn small_lengths_pack_into_one_byte_and_large_ones_overflow() {
let ll = le(&[1, 7, 3, 300], 2);
let ml = le(&[4, 4, 100, 5], 2);
let (muxed, long) = mux(&ll, &ml, 4, 2, 3, 0);
assert_eq!(
muxed,
[1 | (4 << 3), 7 | (4 << 3), 3 | (31 << 3), 7 | (5 << 3)]
);
assert_eq!(long, le(&[0, 69, 293], 2));
}
#[test]
fn the_bias_is_subtracted_from_match_lengths_with_wraparound() {
let ll = le(&[0, 0], 1);
let ml = le(&[2, 9], 1);
let (muxed, long) = mux(&ll, &ml, 2, 1, 4, 4);
assert_eq!(muxed, [0xF0, 5 << 4]);
assert_eq!(long, [(2u64.wrapping_sub(4).wrapping_sub(15)) as u8]);
}
#[test]
fn the_degenerate_splits_send_one_length_entirely_to_the_overflow() {
let ll = le(&[5], 1);
let ml = le(&[9], 1);
let (muxed, long) = mux(&ll, &ml, 1, 1, 0, 0);
assert_eq!(muxed, [9]);
assert_eq!(long, [5]);
let (muxed, long) = mux(&ll, &ml, 1, 1, 8, 0);
assert_eq!(muxed, [5]);
assert_eq!(long, [9]);
}
#[test]
fn the_split_point_minimises_overflow_and_takes_the_lowest_on_ties() {
let ll = le(&[2; 10], 1);
let ml = le(&[2; 10], 1);
let (split, long) = compute_split(&ll, &ml, 10, 1, 0);
assert_eq!((split, long), (2, 0));
let ll = le(&[40; 10], 1);
let ml = le(&[1; 10], 1);
assert_eq!(compute_split(&ll, &ml, 10, 1, 0), (6, 0));
assert_eq!(compute_split(&[], &[], 0, 1, 0), (1, 0));
}
#[cfg(feature = "lz")]
#[test]
fn the_node_reads_the_bias_from_the_lz_metadata_and_a_parameter_overrides_it() {
use crate::compressor::core::codecs::test_util::{run_node, text};
let src = text(30_000, 3);
let lz = run_node(52, &[], 3, &[Stream::serial(&src)]).unwrap();
let [_, _, ll, ml] = &lz.singles[..] else {
panic!("four outputs");
};
let inputs = [ll.clone(), ml.clone()];
let out = run_node(53, &[], 3, &inputs).unwrap();
let header = out.header.unwrap();
assert_eq!(header[0] >> 4, 4, "ZL_LZ_MIN_MATCH from the metadata");
let out = run_node(53, &[(1, 2), (0, 5)], 3, &inputs).unwrap();
assert_eq!(out.header.unwrap(), [5 | (2 << 4)]);
let plain = [
Stream::new(Type::Numeric, 2, Bytes::from(le(&[3, 4], 2))),
Stream::new(Type::Numeric, 2, Bytes::from(le(&[5, 6], 2))),
];
let out = run_node(53, &[], 3, &plain).unwrap();
assert_eq!(out.header.unwrap()[0] >> 4, 0);
}
#[test]
fn invalid_inputs_and_parameters_are_rejected() {
use crate::compressor::core::codecs::test_util::run_node;
let make = |width, values: &[u64]| {
Stream::new(Type::Numeric, width, Bytes::from(le(values, width)))
};
let a = [make(2, &[1, 2]), make(4, &[1, 2])];
assert!(matches!(
run_node(53, &[], 3, &a),
Err(NodeError::InvalidInput(_))
));
let b = [make(2, &[1, 2]), make(2, &[1])];
assert!(matches!(
run_node(53, &[], 3, &b),
Err(NodeError::InvalidInput(_))
));
let ok = [make(2, &[1, 2]), make(2, &[3, 4])];
for ints in [[(1, 16)], [(1, -1)], [(0, 9)], [(0, -1)]] {
assert!(matches!(
run_node(53, &ints, 3, &ok),
Err(NodeError::InvalidParam(_))
));
}
}
#[test]
fn every_width_reads_and_writes_whole_elements() {
for width in [1usize, 2, 4, 8] {
let ll = le(&[1, 200], width);
let ml = le(&[3, 250], width);
let (muxed, long) = mux(&ll, &ml, 2, width, 4, 1);
assert_eq!(muxed.len(), 2);
assert_eq!(long.len() % width, 0);
let value = match width {
1 => read::<1>(&ll, 1),
2 => read::<2>(&ll, 1),
4 => read::<4>(&ll, 1),
_ => read::<8>(&ll, 1),
};
assert_eq!(value, 200);
}
}
}