From d9fe5d25ab497bca3a7c4758ca0ab83b97a78099 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 04:43:41 +0200 Subject: [PATCH] feat(compress): move MT overlap window policy to Rust Move construction of the external-dictionary and active-prefix ranges into a narrow Rust ABI while retaining the C window and logging surface. Preserve byte-range half-open overlap semantics and leave the input-range overlap wrapper available to its other C caller. Test Plan: cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression (180 passed); root and CLI clippy; make -B -C lib -j2 lib; make -C tests -j2 test-cli-tests (41 passed); make -B -C tests -j2 test-zstream (84 named tests plus 6,845 and 9,628 fuzz cases passed). --- lib/compress/zstdmt_compress.c | 25 +++++------ rust/src/zstdmt_compress.rs | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index d33320e1e..0b7674778 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -141,6 +141,9 @@ size_t ZSTDMT_rust_computeOverlapSize(unsigned windowLog, unsigned chainLog, int strategy, int overlapLog, int enableLdm); int ZSTDMT_rust_isOverlapped(const void* bufferStart, size_t bufferCapacity, const void* rangeStart, size_t rangeSize); +int ZSTDMT_rust_doesOverlapWindow(const void* bufferStart, size_t bufferCapacity, + const void* nextSrc, const void* base, + const void* dictBase, U32 dictLimit, U32 lowLimit); typedef struct ZSTDMT_bufferPool_s { ZSTDMT_RustBufferPool* rustPool; @@ -1539,24 +1542,18 @@ static int ZSTDMT_isOverlapped(Buffer buffer, Range range) static int ZSTDMT_doesOverlapWindow(Buffer buffer, ZSTD_window_t window) { - Range extDict; - Range prefix; - DEBUGLOG(5, "ZSTDMT_doesOverlapWindow"); - extDict.start = window.dictBase + window.lowLimit; - extDict.size = window.dictLimit - window.lowLimit; - - prefix.start = window.base + window.dictLimit; - prefix.size = window.nextSrc - (window.base + window.dictLimit); DEBUGLOG(5, "extDict [0x%zx, 0x%zx)", - (size_t)extDict.start, - (size_t)extDict.start + extDict.size); + (size_t)window.dictBase + window.lowLimit, + (size_t)window.dictBase + window.dictLimit); DEBUGLOG(5, "prefix [0x%zx, 0x%zx)", - (size_t)prefix.start, - (size_t)prefix.start + prefix.size); + (size_t)window.base + window.dictLimit, + (size_t)window.nextSrc); - return ZSTDMT_isOverlapped(buffer, extDict) - || ZSTDMT_isOverlapped(buffer, prefix); + return ZSTDMT_rust_doesOverlapWindow(buffer.start, buffer.capacity, + window.nextSrc, window.base, + window.dictBase, window.dictLimit, + window.lowLimit); } static void ZSTDMT_waitForLdmComplete(ZSTDMT_CCtx* mtctx, Buffer buffer) diff --git a/rust/src/zstdmt_compress.rs b/rust/src/zstdmt_compress.rs index 1afad3d8f..cbc9eea51 100644 --- a/rust/src/zstdmt_compress.rs +++ b/rust/src/zstdmt_compress.rs @@ -250,6 +250,43 @@ pub extern "C" fn ZSTDMT_rust_isOverlapped( is_overlapped(bufferStart, bufferCapacity, rangeStart, rangeSize) } +/// Return non-zero when a buffer overlaps either the external dictionary or +/// the active prefix represented by a C `ZSTD_window_t`. +/// +/// The C window stores the two ranges as pointer/index pairs. Passing those +/// scalar fields separately keeps the private C struct out of the Rust ABI; +/// the byte-distance arithmetic mirrors the original pointer subtraction. +#[no_mangle] +pub extern "C" fn ZSTDMT_rust_doesOverlapWindow( + bufferStart: *const c_void, + bufferCapacity: usize, + nextSrc: *const c_void, + base: *const c_void, + dictBase: *const c_void, + dictLimit: u32, + lowLimit: u32, +) -> c_int { + let ext_dict_start = dictBase.cast::().wrapping_add(lowLimit as usize); + let ext_dict_size = dictLimit.wrapping_sub(lowLimit) as usize; + let prefix_start = base.cast::().wrapping_add(dictLimit as usize); + let prefix_size = (nextSrc as usize) + .wrapping_sub(base as usize) + .wrapping_sub(dictLimit as usize); + + (is_overlapped( + bufferStart, + bufferCapacity, + ext_dict_start.cast(), + ext_dict_size, + ) != 0 + || is_overlapped( + bufferStart, + bufferCapacity, + prefix_start.cast(), + prefix_size, + ) != 0) as c_int +} + #[derive(Default)] struct BufferPoolState { buffer_size: usize, @@ -926,6 +963,51 @@ mod tests { ); } + #[test] + fn overlap_window_checks_external_dictionary_and_prefix() { + let bytes = [0u8; 32]; + let base = bytes.as_ptr(); + let next_src = base.wrapping_add(16); + let dict_base = base.wrapping_add(16); + + assert_eq!( + ZSTDMT_rust_doesOverlapWindow( + base.wrapping_add(20).cast(), + 4, + next_src.cast(), + base.cast(), + dict_base.cast(), + 8, + 4, + ), + 1 + ); + assert_eq!( + ZSTDMT_rust_doesOverlapWindow( + base.wrapping_add(12).cast(), + 4, + next_src.cast(), + base.cast(), + dict_base.cast(), + 8, + 4, + ), + 1 + ); + assert_eq!( + ZSTDMT_rust_doesOverlapWindow( + base.cast(), + 4, + next_src.cast(), + base.cast(), + dict_base.cast(), + 8, + 4, + ), + 0 + ); + } + #[test] fn buffer_pool_reuses_and_resizes_buffers() { let pool = unsafe { ZSTDMT_rust_buffer_pool_create(2, DEFAULT_MEM) };