pub(crate) const NO_POSITION: usize = usize::MAX;
const NO_LINK: u32 = u32::MAX;
fn resolve(newest: usize, link: u32) -> usize {
if link == NO_LINK {
return NO_POSITION;
}
newest - (newest as u32).wrapping_sub(link) as usize
}
fn mix(value: u32, bits: u32) -> usize {
(value.wrapping_mul(0x9E37_79B1) >> (32 - bits)) as usize
}
#[derive(Debug, Clone)]
pub(crate) struct MatchFinder<const MIN_MATCH: usize> {
head: Vec<u32>,
prev: Vec<u32>,
mask: usize,
newest: usize,
}
impl<const MIN_MATCH: usize> MatchFinder<MIN_MATCH> {
const HASH_BITS: u32 = match MIN_MATCH {
3 => 16,
4 => 17,
_ => panic!("match finder supports MIN_MATCH of 3 or 4"),
};
pub(crate) fn new(window: usize) -> Self {
let window = window.max(1).next_power_of_two();
Self {
head: vec![NO_LINK; 1 << Self::HASH_BITS],
prev: vec![0; window],
mask: window - 1,
newest: 0,
}
}
fn hash(input: &[u8], pos: usize) -> usize {
let value = if MIN_MATCH == 3 {
u32::from(input[pos])
| (u32::from(input[pos + 1]) << 8)
| (u32::from(input[pos + 2]) << 16)
} else {
u32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]])
};
mix(value, Self::HASH_BITS)
}
pub(crate) fn insert(&mut self, input: &[u8], pos: usize) {
if pos + MIN_MATCH <= input.len() {
let hash = Self::hash(input, pos);
self.prev[pos & self.mask] = self.head[hash];
self.head[hash] = pos as u32;
self.newest = self.newest.max(pos);
}
}
pub(crate) fn first(&self, input: &[u8], pos: usize) -> usize {
resolve(self.newest, self.head[Self::hash(input, pos)])
}
pub(crate) fn previous(&self, candidate: usize) -> usize {
let older = resolve(self.newest, self.prev[candidate & self.mask]);
if older >= candidate {
return NO_POSITION;
}
older
}
}
#[derive(Debug)]
pub(crate) struct TreeMatchFinder {
head: Vec<u32>,
son: Vec<u32>,
mask: usize,
}
impl TreeMatchFinder {
const HASH_BITS: u32 = 17;
const MIN_MATCH: usize = 4;
pub(crate) fn new(window: usize) -> Self {
let window = window.max(1).next_power_of_two();
Self {
head: vec![NO_LINK; 1 << Self::HASH_BITS],
son: vec![0; window * 2],
mask: window - 1,
}
}
pub(crate) fn matches(
&mut self,
input: &[u8],
pos: usize,
len_limit: usize,
max_distance: usize,
cut: usize,
out: &mut Vec<(u32, u32)>,
) {
if pos + Self::MIN_MATCH > input.len() {
return;
}
debug_assert!(len_limit >= Self::MIN_MATCH && pos + len_limit <= input.len());
let hash = mix(
u32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]]),
Self::HASH_BITS,
);
let mut current = resolve(pos, self.head[hash]);
self.head[hash] = pos as u32;
let mut ptr0 = ((pos & self.mask) << 1) + 1;
let mut ptr1 = (pos & self.mask) << 1;
let mut len0 = 0usize;
let mut len1 = 0usize;
let mut longest = Self::MIN_MATCH - 1;
let mut budget = cut;
let mut floor = pos;
loop {
if current >= floor || pos - current > self.mask || budget == 0 {
self.son[ptr0] = NO_LINK;
self.son[ptr1] = NO_LINK;
return;
}
budget -= 1;
floor = current;
let pair = (current & self.mask) << 1;
let mut len = len0.min(len1);
if input[current + len] == input[pos + len] {
len += 1;
while len < len_limit && input[current + len] == input[pos + len] {
len += 1;
}
if len > longest {
if pos - current <= max_distance {
out.push((len as u32, (pos - current) as u32));
}
longest = len;
if len == len_limit {
self.son[ptr1] = self.son[pair];
self.son[ptr0] = self.son[pair + 1];
return;
}
}
}
if input[current + len] < input[pos + len] {
self.son[ptr1] = current as u32;
ptr1 = pair + 1;
len1 = len;
current = resolve(pos, self.son[ptr1]);
} else {
self.son[ptr0] = current as u32;
ptr0 = pair;
len0 = len;
current = resolve(pos, self.son[ptr0]);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{resolve, MatchFinder, TreeMatchFinder, NO_LINK, NO_POSITION};
const REPEATED: &[u8] = b"abcdabcdabcdabcd";
#[test]
fn a_chain_walks_from_the_nearest_candidate_to_the_furthest() {
let mut finder = MatchFinder::<4>::new(REPEATED.len());
for pos in [0, 4, 8] {
finder.insert(REPEATED, pos);
}
let mut walked = Vec::new();
let mut candidate = finder.first(REPEATED, 12);
while candidate != NO_POSITION {
walked.push(candidate);
candidate = finder.previous(candidate);
}
assert_eq!(walked, [8, 4, 0]);
}
#[test]
fn a_position_that_has_fallen_out_of_the_window_still_names_itself() {
let data = vec![b'x'; 4096];
let mut finder = MatchFinder::<4>::new(64);
finder.insert(&data, 0);
finder.insert(&data, 2048);
assert_eq!(finder.first(&data, 3000), 2048);
assert_eq!(finder.previous(2048), 0);
}
#[test]
fn resolving_recovers_a_position_stored_below_four_gigabytes() {
assert_eq!(resolve(1_000, 0), 0);
assert_eq!(resolve(1_000, 999), 999);
assert_eq!(resolve(1_000, 1_000), 1_000);
assert_eq!(resolve(1_000, NO_LINK), NO_POSITION);
}
fn tree_matches_at_every_position(input: &[u8], cut: usize) -> Vec<Vec<(u32, u32)>> {
let mut finder = TreeMatchFinder::new(input.len());
let mut all = Vec::new();
for pos in 0..input.len() {
let mut out = Vec::new();
if pos + 4 <= input.len() {
let len_limit = input.len() - pos;
finder.matches(input, pos, len_limit, pos, cut, &mut out);
}
all.push(out);
}
all
}
#[test]
fn the_tree_reports_the_nearest_distance_for_each_length() {
let all = tree_matches_at_every_position(REPEATED, usize::MAX);
assert_eq!(all[12], [(4, 4)]);
assert_eq!(all[8], [(8, 4)]);
}
#[test]
fn the_tree_reports_runs_of_increasing_length_and_distance() {
let input = b"abcdeXXXXX_abcdfYYYY__abcdeZZ";
let all = tree_matches_at_every_position(input, usize::MAX);
assert_eq!(all[22], [(4, 11), (5, 22)]);
}
#[test]
fn a_spent_budget_ends_the_descent_but_keeps_what_it_found() {
let input = b"abcdeXXXXX_abcdfYYYY__abcdeZZ";
let all = tree_matches_at_every_position(input, 1);
assert_eq!(all[22], [(4, 11)]);
}
#[test]
fn a_node_matching_the_whole_limit_hands_its_children_to_the_new_position() {
let input = vec![b'z'; 512];
let all = tree_matches_at_every_position(&input, usize::MAX);
for (pos, matches) in all.iter().enumerate().skip(1).take(507) {
assert_eq!(
matches.as_slice(),
[((input.len() - pos) as u32, 1)],
"position {pos} did not stop at its nearest candidate",
);
}
}
#[test]
#[cfg(target_pointer_width = "64")]
fn resolving_recovers_a_position_stored_past_four_gigabytes() {
const FOUR_GIB: usize = 1 << 32;
let newest = FOUR_GIB + 16;
assert_eq!(resolve(newest, 16), newest);
assert_eq!(resolve(newest, 1), FOUR_GIB + 1);
assert_eq!(resolve(newest, 0), FOUR_GIB);
assert_eq!(resolve(newest, u32::MAX - 1), FOUR_GIB - 2);
}
}