feat(compress): move MT synchronization policy to Rust
Port the rsyncable synchronization-point scan and rolling hash policy behind a scalar ABI. Keep the MT scheduler, input buffers, and context ownership in C while adding boundary-focused Rust coverage for disabled, short, buffered, spanning, and hit/no-hit paths. Test Plan: - cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression - cargo clippy --manifest-path rust/Cargo.toml - cargo clippy --manifest-path rust/Cargo.toml --benches - cargo clippy --manifest-path rust/Cargo.toml --tests - cargo +nightly fmt --manifest-path rust/Cargo.toml --all - make -B -C lib -j2 lib - make -B -C tests -j2 test-zstream (pending post-commit native gate)
This commit is contained in:
@@ -144,6 +144,11 @@ int ZSTDMT_rust_isOverlapped(const void* bufferStart, size_t bufferCapacity,
|
||||
int ZSTDMT_rust_doesOverlapWindow(const void* bufferStart, size_t bufferCapacity,
|
||||
const void* nextSrc, const void* base,
|
||||
const void* dictBase, U32 dictLimit, U32 lowLimit);
|
||||
void ZSTDMT_rust_findSynchronizationPoint(const void* inputSrc, size_t inputSize,
|
||||
size_t inputPos, size_t targetSectionSize,
|
||||
const void* inBuffStart, size_t inBuffFilled,
|
||||
int rsyncable, U64 primePower, U64 hitMask,
|
||||
size_t* toLoad, int* flush);
|
||||
|
||||
typedef struct ZSTDMT_bufferPool_s {
|
||||
ZSTDMT_RustBufferPool* rustPool;
|
||||
@@ -1647,97 +1652,14 @@ typedef struct {
|
||||
static SyncPoint
|
||||
findSynchronizationPoint(ZSTDMT_CCtx const* mtctx, ZSTD_inBuffer const input)
|
||||
{
|
||||
BYTE const* const istart = (BYTE const*)input.src + input.pos;
|
||||
U64 const primePower = mtctx->rsync.primePower;
|
||||
U64 const hitMask = mtctx->rsync.hitMask;
|
||||
|
||||
SyncPoint syncPoint;
|
||||
U64 hash;
|
||||
BYTE const* prev;
|
||||
size_t pos;
|
||||
|
||||
syncPoint.toLoad = MIN(input.size - input.pos, mtctx->targetSectionSize - mtctx->inBuff.filled);
|
||||
syncPoint.flush = 0;
|
||||
if (!mtctx->params.rsyncable)
|
||||
/* Rsync is disabled. */
|
||||
return syncPoint;
|
||||
if (mtctx->inBuff.filled + input.size - input.pos < RSYNC_MIN_BLOCK_SIZE)
|
||||
/* We don't emit synchronization points if it would produce too small blocks.
|
||||
* We don't have enough input to find a synchronization point, so don't look.
|
||||
*/
|
||||
return syncPoint;
|
||||
if (mtctx->inBuff.filled + syncPoint.toLoad < RSYNC_LENGTH)
|
||||
/* Not enough to compute the hash.
|
||||
* We will miss any synchronization points in this RSYNC_LENGTH byte
|
||||
* window. However, since it depends only in the internal buffers, if the
|
||||
* state is already synchronized, we will remain synchronized.
|
||||
* Additionally, the probability that we miss a synchronization point is
|
||||
* low: RSYNC_LENGTH / targetSectionSize.
|
||||
*/
|
||||
return syncPoint;
|
||||
/* Initialize the loop variables. */
|
||||
if (mtctx->inBuff.filled < RSYNC_MIN_BLOCK_SIZE) {
|
||||
/* We don't need to scan the first RSYNC_MIN_BLOCK_SIZE positions
|
||||
* because they can't possibly be a sync point. So we can start
|
||||
* part way through the input buffer.
|
||||
*/
|
||||
pos = RSYNC_MIN_BLOCK_SIZE - mtctx->inBuff.filled;
|
||||
if (pos >= RSYNC_LENGTH) {
|
||||
prev = istart + pos - RSYNC_LENGTH;
|
||||
hash = ZSTD_rollingHash_compute(prev, RSYNC_LENGTH);
|
||||
} else {
|
||||
assert(mtctx->inBuff.filled >= RSYNC_LENGTH);
|
||||
prev = (BYTE const*)mtctx->inBuff.buffer.start + mtctx->inBuff.filled - RSYNC_LENGTH;
|
||||
hash = ZSTD_rollingHash_compute(prev + pos, (RSYNC_LENGTH - pos));
|
||||
hash = ZSTD_rollingHash_append(hash, istart, pos);
|
||||
}
|
||||
} else {
|
||||
/* We have enough bytes buffered to initialize the hash,
|
||||
* and have processed enough bytes to find a sync point.
|
||||
* Start scanning at the beginning of the input.
|
||||
*/
|
||||
assert(mtctx->inBuff.filled >= RSYNC_MIN_BLOCK_SIZE);
|
||||
assert(RSYNC_MIN_BLOCK_SIZE >= RSYNC_LENGTH);
|
||||
pos = 0;
|
||||
prev = (BYTE const*)mtctx->inBuff.buffer.start + mtctx->inBuff.filled - RSYNC_LENGTH;
|
||||
hash = ZSTD_rollingHash_compute(prev, RSYNC_LENGTH);
|
||||
if ((hash & hitMask) == hitMask) {
|
||||
/* We're already at a sync point so don't load any more until
|
||||
* we're able to flush this sync point.
|
||||
* This likely happened because the job table was full so we
|
||||
* couldn't add our job.
|
||||
*/
|
||||
syncPoint.toLoad = 0;
|
||||
syncPoint.flush = 1;
|
||||
return syncPoint;
|
||||
}
|
||||
}
|
||||
/* Starting with the hash of the previous RSYNC_LENGTH bytes, roll
|
||||
* through the input. If we hit a synchronization point, then cut the
|
||||
* job off, and tell the compressor to flush the job. Otherwise, load
|
||||
* all the bytes and continue as normal.
|
||||
* If we go too long without a synchronization point (targetSectionSize)
|
||||
* then a block will be emitted anyways, but this is okay, since if we
|
||||
* are already synchronized we will remain synchronized.
|
||||
*/
|
||||
assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash);
|
||||
for (; pos < syncPoint.toLoad; ++pos) {
|
||||
BYTE const toRemove = pos < RSYNC_LENGTH ? prev[pos] : istart[pos - RSYNC_LENGTH];
|
||||
/* This assert is very expensive, and Debian compiles with asserts enabled.
|
||||
* So disable it for now. We can get similar coverage by checking it at the
|
||||
* beginning & end of the loop.
|
||||
* assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash);
|
||||
*/
|
||||
hash = ZSTD_rollingHash_rotate(hash, toRemove, istart[pos], primePower);
|
||||
assert(mtctx->inBuff.filled + pos >= RSYNC_MIN_BLOCK_SIZE);
|
||||
if ((hash & hitMask) == hitMask) {
|
||||
syncPoint.toLoad = pos + 1;
|
||||
syncPoint.flush = 1;
|
||||
++pos; /* for assert */
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash);
|
||||
ZSTDMT_rust_findSynchronizationPoint(
|
||||
input.src, input.size, input.pos,
|
||||
mtctx->targetSectionSize,
|
||||
mtctx->inBuff.buffer.start, mtctx->inBuff.filled,
|
||||
mtctx->params.rsyncable,
|
||||
mtctx->rsync.primePower, mtctx->rsync.hitMask,
|
||||
&syncPoint.toLoad, &syncPoint.flush);
|
||||
return syncPoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,13 @@ const ZSTD_PS_ENABLE: c_int = 1;
|
||||
#[cfg(test)]
|
||||
const ZSTD_PS_DISABLE: c_int = 2;
|
||||
|
||||
const RSYNC_LENGTH: usize = 32;
|
||||
const RSYNC_MIN_BLOCK_LOG: usize = 17;
|
||||
const RSYNC_MIN_BLOCK_SIZE: usize = 1 << RSYNC_MIN_BLOCK_LOG;
|
||||
const PRIME8_BYTES: u64 = 0xCF1B_BCDC_B7A5_6463;
|
||||
const ROLL_HASH_CHAR_OFFSET: u64 = 10;
|
||||
const _: () = assert!(RSYNC_MIN_BLOCK_SIZE >= RSYNC_LENGTH);
|
||||
|
||||
#[inline]
|
||||
fn cycle_log(chain_log: c_uint, strategy: c_int) -> c_uint {
|
||||
chain_log.wrapping_sub((strategy >= ZSTD_BTLAZY2) as c_uint)
|
||||
@@ -139,6 +146,165 @@ pub extern "C" fn ZSTDMT_rust_computeOverlapSize(
|
||||
compute_overlap_size(windowLog, chainLog, strategy, overlapLog, enableLdm)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn rolling_hash_append(mut hash: u64, input: *const u8, size: usize) -> u64 {
|
||||
for pos in 0..size {
|
||||
let byte = u64::from(unsafe { *input.wrapping_add(pos) });
|
||||
hash = hash
|
||||
.wrapping_mul(PRIME8_BYTES)
|
||||
.wrapping_add(byte.wrapping_add(ROLL_HASH_CHAR_OFFSET));
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn rolling_hash_compute(input: *const u8, size: usize) -> u64 {
|
||||
unsafe { rolling_hash_append(0, input, size) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rolling_hash_rotate(hash: u64, to_remove: u8, to_add: u8, prime_power: u64) -> u64 {
|
||||
hash.wrapping_sub(
|
||||
u64::from(to_remove)
|
||||
.wrapping_add(ROLL_HASH_CHAR_OFFSET)
|
||||
.wrapping_mul(prime_power),
|
||||
)
|
||||
.wrapping_mul(PRIME8_BYTES)
|
||||
.wrapping_add(u64::from(to_add).wrapping_add(ROLL_HASH_CHAR_OFFSET))
|
||||
}
|
||||
|
||||
/// Search for a synchronization point without exposing the C MT context or
|
||||
/// its private buffer types across the FFI boundary.
|
||||
#[inline]
|
||||
unsafe fn find_synchronization_point(
|
||||
input_src: *const c_void,
|
||||
input_size: usize,
|
||||
input_pos: usize,
|
||||
target_section_size: usize,
|
||||
in_buff_start: *const c_void,
|
||||
in_buff_filled: usize,
|
||||
rsyncable: c_int,
|
||||
prime_power: u64,
|
||||
hit_mask: u64,
|
||||
) -> (usize, c_int) {
|
||||
let istart = input_src.cast::<u8>().wrapping_add(input_pos);
|
||||
let available = input_size.wrapping_sub(input_pos);
|
||||
let mut to_load = available.min(target_section_size.wrapping_sub(in_buff_filled));
|
||||
let mut flush = 0;
|
||||
|
||||
if rsyncable == 0 {
|
||||
return (to_load, flush);
|
||||
}
|
||||
if in_buff_filled.wrapping_add(available) < RSYNC_MIN_BLOCK_SIZE {
|
||||
return (to_load, flush);
|
||||
}
|
||||
if in_buff_filled.wrapping_add(to_load) < RSYNC_LENGTH {
|
||||
return (to_load, flush);
|
||||
}
|
||||
|
||||
let (mut pos, prev, mut hash) = if in_buff_filled < RSYNC_MIN_BLOCK_SIZE {
|
||||
let pos = RSYNC_MIN_BLOCK_SIZE.wrapping_sub(in_buff_filled);
|
||||
if pos >= RSYNC_LENGTH {
|
||||
let prev = istart.wrapping_add(pos.wrapping_sub(RSYNC_LENGTH));
|
||||
let hash = unsafe { rolling_hash_compute(prev, RSYNC_LENGTH) };
|
||||
(pos, prev, hash)
|
||||
} else {
|
||||
debug_assert!(in_buff_filled >= RSYNC_LENGTH);
|
||||
let prev = in_buff_start
|
||||
.cast::<u8>()
|
||||
.wrapping_add(in_buff_filled.wrapping_sub(RSYNC_LENGTH));
|
||||
let hash = unsafe { rolling_hash_compute(prev.wrapping_add(pos), RSYNC_LENGTH - pos) };
|
||||
let hash = unsafe { rolling_hash_append(hash, istart, pos) };
|
||||
(pos, prev, hash)
|
||||
}
|
||||
} else {
|
||||
let pos = 0;
|
||||
let prev = in_buff_start
|
||||
.cast::<u8>()
|
||||
.wrapping_add(in_buff_filled.wrapping_sub(RSYNC_LENGTH));
|
||||
let hash = unsafe { rolling_hash_compute(prev, RSYNC_LENGTH) };
|
||||
if (hash & hit_mask) == hit_mask {
|
||||
return (0, 1);
|
||||
}
|
||||
(pos, prev, hash)
|
||||
};
|
||||
|
||||
debug_assert!(
|
||||
pos < RSYNC_LENGTH
|
||||
|| unsafe {
|
||||
rolling_hash_compute(
|
||||
istart.wrapping_add(pos.wrapping_sub(RSYNC_LENGTH)),
|
||||
RSYNC_LENGTH,
|
||||
) == hash
|
||||
}
|
||||
);
|
||||
while pos < to_load {
|
||||
let to_remove = if pos < RSYNC_LENGTH {
|
||||
unsafe { *prev.wrapping_add(pos) }
|
||||
} else {
|
||||
unsafe { *istart.wrapping_add(pos.wrapping_sub(RSYNC_LENGTH)) }
|
||||
};
|
||||
let to_add = unsafe { *istart.wrapping_add(pos) };
|
||||
hash = rolling_hash_rotate(hash, to_remove, to_add, prime_power);
|
||||
debug_assert!(in_buff_filled.wrapping_add(pos) >= RSYNC_MIN_BLOCK_SIZE);
|
||||
if (hash & hit_mask) == hit_mask {
|
||||
to_load = pos + 1;
|
||||
flush = 1;
|
||||
pos += 1;
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
debug_assert!(
|
||||
pos < RSYNC_LENGTH
|
||||
|| unsafe {
|
||||
rolling_hash_compute(
|
||||
istart.wrapping_add(pos.wrapping_sub(RSYNC_LENGTH)),
|
||||
RSYNC_LENGTH,
|
||||
) == hash
|
||||
}
|
||||
);
|
||||
(to_load, flush)
|
||||
}
|
||||
|
||||
/// C ABI for the pure MT synchronization-point policy.
|
||||
///
|
||||
/// The caller retains ownership of the MT context, input position, and
|
||||
/// buffer updates. Rust only scans the two byte ranges and writes the two
|
||||
/// scalar results.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTDMT_rust_findSynchronizationPoint(
|
||||
inputSrc: *const c_void,
|
||||
inputSize: usize,
|
||||
inputPos: usize,
|
||||
targetSectionSize: usize,
|
||||
inBuffStart: *const c_void,
|
||||
inBuffFilled: usize,
|
||||
rsyncable: c_int,
|
||||
primePower: u64,
|
||||
hitMask: u64,
|
||||
toLoad: *mut usize,
|
||||
flush: *mut c_int,
|
||||
) {
|
||||
let (computed_to_load, computed_flush) = unsafe {
|
||||
find_synchronization_point(
|
||||
inputSrc,
|
||||
inputSize,
|
||||
inputPos,
|
||||
targetSectionSize,
|
||||
inBuffStart,
|
||||
inBuffFilled,
|
||||
rsyncable,
|
||||
primePower,
|
||||
hitMask,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
*toLoad = computed_to_load;
|
||||
*flush = computed_flush;
|
||||
}
|
||||
}
|
||||
|
||||
type ZstdAllocFunction = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
||||
type ZstdFreeFunction = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
||||
|
||||
@@ -849,6 +1015,264 @@ mod tests {
|
||||
opaque: ptr::null_mut(),
|
||||
};
|
||||
|
||||
fn rsync_prime_power() -> u64 {
|
||||
(0..RSYNC_LENGTH - 1).fold(1, |power, _| power.wrapping_mul(PRIME8_BYTES))
|
||||
}
|
||||
|
||||
fn patterned_bytes(length: usize, seed: u8) -> Vec<u8> {
|
||||
let mut value = seed;
|
||||
let mut bytes = vec![0; length];
|
||||
for byte in &mut bytes {
|
||||
value = value.wrapping_mul(17).wrapping_add(29);
|
||||
*byte = value;
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn rolling_hash_bytes(bytes: &[u8]) -> u64 {
|
||||
bytes.iter().fold(0, |hash, &byte| {
|
||||
hash.wrapping_mul(PRIME8_BYTES)
|
||||
.wrapping_add(u64::from(byte).wrapping_add(ROLL_HASH_CHAR_OFFSET))
|
||||
})
|
||||
}
|
||||
|
||||
fn call_synchronization_point(
|
||||
input: &[u8],
|
||||
input_pos: usize,
|
||||
in_buff: &[u8],
|
||||
in_buff_filled: usize,
|
||||
target_section_size: usize,
|
||||
rsyncable: c_int,
|
||||
hit_mask: u64,
|
||||
) -> (usize, c_int) {
|
||||
let mut to_load = usize::MAX;
|
||||
let mut flush = -1;
|
||||
unsafe {
|
||||
ZSTDMT_rust_findSynchronizationPoint(
|
||||
input.as_ptr().cast(),
|
||||
input.len(),
|
||||
input_pos,
|
||||
target_section_size,
|
||||
in_buff.as_ptr().cast(),
|
||||
in_buff_filled,
|
||||
rsyncable,
|
||||
rsync_prime_power(),
|
||||
hit_mask,
|
||||
&mut to_load,
|
||||
&mut flush,
|
||||
);
|
||||
}
|
||||
(to_load, flush)
|
||||
}
|
||||
|
||||
fn input_hashes_after_each_byte(
|
||||
in_buff: &[u8],
|
||||
in_buff_filled: usize,
|
||||
input: &[u8],
|
||||
input_pos: usize,
|
||||
) -> (u64, Vec<u64>) {
|
||||
let mut history = in_buff[in_buff_filled - RSYNC_LENGTH..in_buff_filled].to_vec();
|
||||
history.extend_from_slice(&input[input_pos..]);
|
||||
let initial = rolling_hash_bytes(&history[..RSYNC_LENGTH]);
|
||||
let hashes = (0..input.len() - input_pos)
|
||||
.map(|pos| rolling_hash_bytes(&history[pos + 1..pos + 1 + RSYNC_LENGTH]))
|
||||
.collect();
|
||||
(initial, hashes)
|
||||
}
|
||||
|
||||
fn hit_mask_for_index(initial: u64, hashes: &[u64], target: usize) -> u64 {
|
||||
let target_hash = hashes[target];
|
||||
let prior_matches = |mask: u64| {
|
||||
(initial & mask) == mask || hashes[..target].iter().any(|hash| (hash & mask) == mask)
|
||||
};
|
||||
if target_hash != 0 && !prior_matches(target_hash) {
|
||||
return target_hash;
|
||||
}
|
||||
for mask in 1..=u64::from(u16::MAX) {
|
||||
if target_hash & mask == mask && !prior_matches(mask) {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
panic!("could not isolate synchronization hash at position {target}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_keeps_rsync_disabled() {
|
||||
let input = patterned_bytes(20, 3);
|
||||
let in_buff = patterned_bytes(64, 5);
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(&input, 3, &in_buff, 40, 100, 0, 0),
|
||||
(17, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_does_not_scan_a_short_hash_window() {
|
||||
let input = patterned_bytes(RSYNC_LENGTH - 1, 7);
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 11);
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
0,
|
||||
RSYNC_MIN_BLOCK_SIZE + input.len(),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
(input.len(), 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_respects_min_block_boundaries() {
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 13);
|
||||
for &in_buff_filled in &[
|
||||
0,
|
||||
RSYNC_LENGTH - 1,
|
||||
RSYNC_LENGTH,
|
||||
RSYNC_MIN_BLOCK_SIZE - 1,
|
||||
RSYNC_MIN_BLOCK_SIZE,
|
||||
] {
|
||||
let input_len = if in_buff_filled < RSYNC_MIN_BLOCK_SIZE {
|
||||
RSYNC_MIN_BLOCK_SIZE - in_buff_filled + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let input = patterned_bytes(input_len, in_buff_filled as u8);
|
||||
let expected = if in_buff_filled == RSYNC_MIN_BLOCK_SIZE {
|
||||
(0, 1)
|
||||
} else {
|
||||
(input_len, 1)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
in_buff_filled,
|
||||
in_buff_filled + input_len,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
expected,
|
||||
"inBuff.filled={in_buff_filled}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_reports_initial_first_middle_and_final_hits() {
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 17);
|
||||
let input = patterned_bytes(96, 19);
|
||||
let (initial, hashes) =
|
||||
input_hashes_after_each_byte(&in_buff, RSYNC_MIN_BLOCK_SIZE, &input, 0);
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
RSYNC_MIN_BLOCK_SIZE,
|
||||
RSYNC_MIN_BLOCK_SIZE + input.len(),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
(0, 1)
|
||||
);
|
||||
|
||||
for &(name, index) in &[("first", 0), ("middle", 37), ("final", 95)] {
|
||||
let hit_mask = hit_mask_for_index(initial, &hashes, index);
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
RSYNC_MIN_BLOCK_SIZE,
|
||||
RSYNC_MIN_BLOCK_SIZE + input.len(),
|
||||
1,
|
||||
hit_mask,
|
||||
),
|
||||
(index + 1, 1),
|
||||
"{name} hit"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_loads_all_input_when_no_hash_hits() {
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 23);
|
||||
let input = patterned_bytes(96, 29);
|
||||
let (initial, hashes) =
|
||||
input_hashes_after_each_byte(&in_buff, RSYNC_MIN_BLOCK_SIZE, &input, 0);
|
||||
assert_ne!(initial, u64::MAX);
|
||||
assert!(hashes.iter().all(|&hash| hash != u64::MAX));
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
RSYNC_MIN_BLOCK_SIZE,
|
||||
RSYNC_MIN_BLOCK_SIZE + input.len(),
|
||||
1,
|
||||
u64::MAX,
|
||||
),
|
||||
(input.len(), 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_preserves_nonzero_input_position() {
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 31);
|
||||
let prefix = patterned_bytes(7, 37);
|
||||
let payload = patterned_bytes(64, 41);
|
||||
let mut input = prefix;
|
||||
input.extend_from_slice(&payload);
|
||||
let (initial, hashes) =
|
||||
input_hashes_after_each_byte(&in_buff, RSYNC_MIN_BLOCK_SIZE, &input, 7);
|
||||
let hit_mask = hit_mask_for_index(initial, &hashes, 23);
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
7,
|
||||
&in_buff,
|
||||
RSYNC_MIN_BLOCK_SIZE,
|
||||
RSYNC_MIN_BLOCK_SIZE + payload.len(),
|
||||
1,
|
||||
hit_mask,
|
||||
),
|
||||
(24, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_point_handles_a_window_spanning_the_buffer_boundary() {
|
||||
let in_buff = patterned_bytes(RSYNC_MIN_BLOCK_SIZE, 43);
|
||||
let input = [0xe1, 0x7a];
|
||||
let in_buff_filled = RSYNC_MIN_BLOCK_SIZE - 1;
|
||||
let mut history = in_buff[in_buff_filled - RSYNC_LENGTH..in_buff_filled].to_vec();
|
||||
history.extend_from_slice(&input);
|
||||
let hit_mask = rolling_hash_bytes(&history[2..2 + RSYNC_LENGTH]);
|
||||
|
||||
assert_eq!(
|
||||
call_synchronization_point(
|
||||
&input,
|
||||
0,
|
||||
&in_buff,
|
||||
in_buff_filled,
|
||||
in_buff_filled + input.len(),
|
||||
1,
|
||||
hit_mask,
|
||||
),
|
||||
(input.len(), 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_seq_buffer_conversion_uses_whole_element_capacity() {
|
||||
let mut sequences = [ZstdMtRawSeq::default(); 3];
|
||||
|
||||
Reference in New Issue
Block a user