feat(compress): move overflow correction policy into Rust

Move the overflow-correction branch and ordering into the Rust rewrite while
keeping the C-owned window correction, workspace table markers, index reducer,
and dictionary fields behind callbacks. Rust now preserves the original
need-correction fast path, dirty/reduce/clean ordering, saturating
nextToUpdate adjustment, and dictionary invalidation. Add focused callback
ordering and safe-window tests.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo fmt --manifest-path rust/Cargo.toml -- --check
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml overflow_correction -- --nocapture
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040 make -B -C programs -j1 zstd
- ulimit -v 41943040 make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s
This commit is contained in:
2026-07-19 19:30:20 +02:00
parent 9c298999ac
commit 1cb3efd0af
3 changed files with 312 additions and 18 deletions
+200
View File
@@ -5238,6 +5238,96 @@ pub extern "C" fn ZSTD_rust_windowNeedOverflowCorrection(
) as u32
}
type OverflowNeedCorrectionFn =
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void) -> c_int;
type OverflowCorrectFn = unsafe extern "C" fn(*mut c_void, *const c_void) -> c_uint;
type OverflowCallbackFn = unsafe extern "C" fn(*mut c_void);
type OverflowReduceIndexFn = unsafe extern "C" fn(*mut c_void, c_uint);
/// Projection for the overflow-correction ordering around C-owned match state.
///
/// Rust owns the correction branch and callback order. C retains the window,
/// workspace, match tables, and dictionary pointers behind these callbacks.
#[repr(C)]
pub struct ZSTD_rust_overflowCorrectState {
callback_context: *mut c_void,
next_to_update: *mut c_uint,
need_correction: Option<OverflowNeedCorrectionFn>,
correct_overflow: Option<OverflowCorrectFn>,
mark_tables_dirty: Option<OverflowCallbackFn>,
reduce_index: Option<OverflowReduceIndexFn>,
mark_tables_clean: Option<OverflowCallbackFn>,
invalidate_dictionary: Option<OverflowCallbackFn>,
}
const _: () = {
assert!(size_of::<OverflowNeedCorrectionFn>() == size_of::<usize>());
assert!(size_of::<OverflowCorrectFn>() == size_of::<usize>());
assert!(size_of::<OverflowCallbackFn>() == size_of::<usize>());
assert!(size_of::<OverflowReduceIndexFn>() == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_overflowCorrectState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_overflowCorrectState, next_to_update) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_overflowCorrectState, need_correction) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_overflowCorrectState, correct_overflow) == 3 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_overflowCorrectState, mark_tables_dirty) == 4 * size_of::<usize>()
);
assert!(offset_of!(ZSTD_rust_overflowCorrectState, reduce_index) == 5 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_overflowCorrectState, mark_tables_clean) == 6 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_overflowCorrectState, invalidate_dictionary) == 7 * size_of::<usize>()
);
assert!(size_of::<ZSTD_rust_overflowCorrectState>() == size_of::<[usize; 8]>());
};
/// Apply one overflow correction while keeping all private codec state in C.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_overflowCorrectIfNeeded(
state: *const ZSTD_rust_overflowCorrectState,
src: *const c_void,
src_end: *const c_void,
) {
if state.is_null() {
return;
}
let state = unsafe { &*state };
if state.callback_context.is_null() || state.next_to_update.is_null() {
return;
}
let (
Some(need_correction),
Some(correct_overflow),
Some(mark_tables_dirty),
Some(reduce_index),
Some(mark_tables_clean),
Some(invalidate_dictionary),
) = (
state.need_correction,
state.correct_overflow,
state.mark_tables_dirty,
state.reduce_index,
state.mark_tables_clean,
state.invalidate_dictionary,
)
else {
return;
};
unsafe {
if need_correction(state.callback_context, src, src_end) == 0 {
return;
}
let correction = correct_overflow(state.callback_context, src);
mark_tables_dirty(state.callback_context);
reduce_index(state.callback_context, correction);
mark_tables_clean(state.callback_context);
*state.next_to_update = (*state.next_to_update).saturating_sub(correction);
invalidate_dictionary(state.callback_context);
}
}
#[inline]
fn limit_next_to_update(curr: u32, next_to_update: u32) -> u32 {
let const_gap = 384u32;
@@ -10760,6 +10850,116 @@ mod tests {
assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold + 1), 1);
}
#[derive(Default)]
struct OverflowCorrectionTestContext {
events: Vec<&'static str>,
should_correct: c_int,
correction: c_uint,
}
unsafe fn overflow_correction_test_context(
context: *mut c_void,
) -> &'static mut OverflowCorrectionTestContext {
unsafe { &mut *context.cast::<OverflowCorrectionTestContext>() }
}
unsafe extern "C" fn overflow_correction_test_need(
context: *mut c_void,
_src: *const c_void,
_src_end: *const c_void,
) -> c_int {
let context = unsafe { overflow_correction_test_context(context) };
context.events.push("need");
context.should_correct
}
unsafe extern "C" fn overflow_correction_test_correct(
context: *mut c_void,
_src: *const c_void,
) -> c_uint {
let context = unsafe { overflow_correction_test_context(context) };
context.events.push("correct");
context.correction
}
unsafe extern "C" fn overflow_correction_test_mark_dirty(context: *mut c_void) {
unsafe { overflow_correction_test_context(context) }
.events
.push("dirty");
}
unsafe extern "C" fn overflow_correction_test_reduce(
context: *mut c_void,
_correction: c_uint,
) {
unsafe { overflow_correction_test_context(context) }
.events
.push("reduce");
}
unsafe extern "C" fn overflow_correction_test_mark_clean(context: *mut c_void) {
unsafe { overflow_correction_test_context(context) }
.events
.push("clean");
}
unsafe extern "C" fn overflow_correction_test_invalidate(context: *mut c_void) {
unsafe { overflow_correction_test_context(context) }
.events
.push("invalidate");
}
fn overflow_correction_test_state(
context: &mut OverflowCorrectionTestContext,
next_to_update: &mut c_uint,
) -> ZSTD_rust_overflowCorrectState {
ZSTD_rust_overflowCorrectState {
callback_context: (context as *mut OverflowCorrectionTestContext).cast(),
next_to_update,
need_correction: Some(overflow_correction_test_need),
correct_overflow: Some(overflow_correction_test_correct),
mark_tables_dirty: Some(overflow_correction_test_mark_dirty),
reduce_index: Some(overflow_correction_test_reduce),
mark_tables_clean: Some(overflow_correction_test_mark_clean),
invalidate_dictionary: Some(overflow_correction_test_invalidate),
}
}
#[test]
fn overflow_correction_preserves_order_and_saturates_next_to_update() {
let mut context = OverflowCorrectionTestContext {
should_correct: 1,
correction: 10,
..Default::default()
};
let mut next_to_update = 7;
let state = overflow_correction_test_state(&mut context, &mut next_to_update);
unsafe {
ZSTD_rust_overflowCorrectIfNeeded(&state, ptr::null(), ptr::null());
}
assert_eq!(next_to_update, 0);
assert_eq!(
context.events,
["need", "correct", "dirty", "reduce", "clean", "invalidate"]
);
}
#[test]
fn overflow_correction_skips_callbacks_when_window_is_safe() {
let mut context = OverflowCorrectionTestContext::default();
let mut next_to_update = 123;
let state = overflow_correction_test_state(&mut context, &mut next_to_update);
unsafe {
ZSTD_rust_overflowCorrectIfNeeded(&state, ptr::null(), ptr::null());
}
assert_eq!(next_to_update, 123);
assert_eq!(context.events, ["need"]);
}
#[test]
fn window_correction_handles_current_cycle_start_boundary() {
assert_eq!(window_correct_overflow(0x100, 3, 8), 0xf0);