#[inline(always)]
fn first_different_byte(diff: usize) -> usize {
if cfg!(target_endian = "little") {
diff.trailing_zeros() as usize / 8
} else {
diff.leading_zeros() as usize / 8
}
}
#[inline(always)]
pub(crate) fn extend_match(
src_data: &[u8],
target: &[u8],
ref_start: usize,
data_pos: usize,
max_match: usize,
) -> usize {
let Some(src_tail) = src_data.get(ref_start..) else {
return 0;
};
let Some(tgt_tail) = target.get(data_pos..) else {
return 0;
};
let max = max_match.min(src_tail.len()).min(tgt_tail.len());
let mut i = 0;
const WORD_SIZE: usize = core::mem::size_of::<usize>();
while i + WORD_SIZE <= max {
let src_word = usize::from_ne_bytes(src_tail[i..i + WORD_SIZE].try_into().unwrap());
let tgt_word = usize::from_ne_bytes(tgt_tail[i..i + WORD_SIZE].try_into().unwrap());
let diff = src_word ^ tgt_word;
if diff != 0 {
return i + first_different_byte(diff);
}
i += WORD_SIZE;
}
while i < max {
if src_tail[i] != tgt_tail[i] {
return i;
}
i += 1;
}
max
}
#[cfg(test)]
mod tests {
use super::extend_match;
fn extend_match_reference(
src_data: &[u8],
target: &[u8],
ref_start: usize,
data_pos: usize,
max_match: usize,
) -> usize {
let mut i = 0;
while i < max_match {
let Some(&a) = src_data.get(ref_start + i) else {
break;
};
let Some(&b) = target.get(data_pos + i) else {
break;
};
if a != b {
break;
}
i += 1;
}
i
}
fn assert_match(src: &[u8], tgt: &[u8], ref_start: usize, data_pos: usize, max_match: usize) {
debug_assert!(ref_start + max_match <= src.len());
debug_assert!(data_pos + max_match <= tgt.len());
let expected = extend_match_reference(src, tgt, ref_start, data_pos, max_match);
let actual = extend_match(src, tgt, ref_start, data_pos, max_match);
assert_eq!(actual, expected, "mismatch: {actual} != {expected}");
}
#[test]
fn empty_inputs() {
assert_match(b"", b"", 0, 0, 0);
}
#[test]
fn full_equal() {
let a = b"abcdef";
let b = b"abcdef";
assert_match(a, b, 0, 0, 6);
}
#[test]
fn first_byte_differs() {
assert_match(b"xbcdef", b"abcdef", 0, 0, 6);
}
#[test]
fn middle_differs() {
assert_match(b"abcxef", b"abcdef", 0, 0, 6);
}
#[test]
fn last_byte_differs() {
assert_match(b"abcdex", b"abcdef", 0, 0, 6);
}
#[test]
fn with_offsets() {
assert_match(b"zzzzabcdef", b"yyyyabcxef", 4, 4, 6);
}
#[test]
fn max_shorter_than_equal_prefix() {
assert_match(b"abcdef", b"abcdef", 0, 0, 3);
}
#[test]
fn near_end_of_src() {
assert_match(b"abcdef", b"xxcdef", 2, 2, 4);
}
#[test]
fn near_end_of_tgt() {
assert_match(b"xxcdef", b"abcdef", 2, 2, 4);
}
#[test]
fn max_zero() {
assert_match(b"abcdef", b"abcdef", 0, 0, 0);
}
#[test]
fn single_byte_match() {
assert_match(b"a", b"a", 0, 0, 1);
}
#[test]
fn single_byte_no_match() {
assert_match(b"a", b"b", 0, 0, 1);
}
#[test]
fn long_match_64_bytes() {
let a = vec![b'A'; 128];
let mut b = vec![b'A'; 128];
b[64] = b'B';
assert_match(&a, &b, 0, 0, 128);
}
#[test]
fn long_match_4096_bytes() {
let a = vec![b'A'; 8192];
let mut b = vec![b'A'; 8192];
b[4096] = b'B';
assert_match(&a, &b, 0, 0, 8192);
}
#[test]
fn word_boundary_7_bytes() {
assert_match(b"1234567", b"1234567", 0, 0, 7);
}
#[test]
fn word_boundary_8_bytes() {
assert_match(b"12345678", b"12345678", 0, 0, 8);
}
#[test]
fn word_boundary_9_bytes() {
assert_match(b"123456789", b"123456789", 0, 0, 9);
}
#[test]
fn mismatch_in_first_word() {
assert_match(b"12345678", b"1234x678", 0, 0, 8);
}
#[test]
fn mismatch_in_second_word() {
assert_match(b"12345678abcdefgh", b"12345678abcxefgh", 0, 0, 16);
}
#[test]
fn ref_start_at_last_byte() {
assert_match(b"hello", b"hello", 4, 4, 1);
}
#[test]
fn data_pos_at_last_byte() {
assert_match(b"hello", b"hello", 4, 4, 1);
}
#[test]
fn ref_start_near_end() {
assert_match(b"abc", b"abcdef", 2, 0, 1);
}
#[test]
fn data_pos_near_end() {
assert_match(b"abcdef", b"abc", 0, 2, 1);
}
}