fix(compress): reserve the block split terminal boundary

The optimal block splitter stores up to 196 split points and then needs one
additional partition entry for the terminal sequence boundary consumed by the
C block-emission loop. The previous array declaration provided only the split
slots, so a maximally partitioned block could write the terminal boundary past
the projected Rust/C state. Reserve the extra entry and document the layout;
retain focused tests for equal-cost and estimation-error cases so the splitter
continues to avoid unnecessary or invalid partitions.

Test Plan:
- Focused block-split Rust tests -- 7 passed
- `make -B -C lib -j2 lib` -- passed
- `cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check` -- passed
- Scoped `git diff --check` -- passed
This commit is contained in:
2026-07-18 17:59:49 +02:00
parent e02cf8b052
commit 4aafcde301
3 changed files with 44 additions and 2 deletions
+1 -1
View File
@@ -2841,7 +2841,7 @@ ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc,
BYTE* op = (BYTE*)dst;
size_t i = 0;
size_t srcBytesTotal = 0;
U32* const partitions = zc->blockSplitCtx.partitions; /* size == ZSTD_MAX_NB_BLOCK_SPLITS */
U32* const partitions = zc->blockSplitCtx.partitions; /* splits plus the terminal boundary */
SeqStore_t* const nextSeqStore = &zc->blockSplitCtx.nextSeqStore;
SeqStore_t* const currSeqStore = &zc->blockSplitCtx.currSeqStore;
size_t const numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq);
+2 -1
View File
@@ -465,7 +465,8 @@ typedef struct {
SeqStore_t currSeqStore;
SeqStore_t nextSeqStore;
U32 partitions[ZSTD_MAX_NB_BLOCK_SPLITS];
/* One terminal boundary follows the at most ZSTD_MAX_NB_BLOCK_SPLITS splits. */
U32 partitions[ZSTD_MAX_NB_BLOCK_SPLITS + 1];
ZSTD_entropyCTablesMetadata_t entropyMetadata;
} ZSTD_blockSplitCtx;
+41
View File
@@ -421,6 +421,47 @@ mod tests {
assert_eq!(partitions[0], 600);
}
#[test]
fn an_estimation_error_in_any_candidate_prevents_the_split() {
for error_index in 0..3 {
let mut sequences = vec![SeqDef::default(); 600];
let mut partitions = [u32::MAX; 2];
let mut calls = 0;
let splits = unsafe {
run_search(&mut sequences, 0, 0, &mut partitions, |_| {
let current_call = calls;
calls += 1;
if current_call == error_index {
ERROR(ZstdErrorCode::Generic)
} else {
0
}
})
};
assert_eq!(splits, 0, "error in candidate {error_index}");
assert_eq!(calls, 3, "error in candidate {error_index}");
assert_eq!(partitions[0], 600, "error in candidate {error_index}");
}
}
#[test]
fn equal_estimated_cost_does_not_split() {
let mut sequences = vec![SeqDef::default(); 600];
let mut partitions = [u32::MAX; 2];
let mut calls = 0;
let splits = unsafe {
run_search(&mut sequences, 0, 0, &mut partitions, |_| {
calls += 1;
0
})
};
assert_eq!(splits, 0);
assert_eq!(calls, 3);
assert_eq!(partitions[0], 600);
}
#[test]
fn derived_views_keep_long_length_metadata_in_their_ranges() {
let mut sequences = vec![SeqDef::default(); 600];