From f44d9726c3adb3f862ef2f3836dc6089c12bbb5f Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 08:13:08 +0200 Subject: [PATCH] refactor(decompress): move sequence mode policy to Rust Keep the C build-time mutual-exclusion check and private decoder-context assembly in the C shim, but pass the selected force-short/force-long policy through Rust. Rust now owns normalization to the runtime, short, or long sequence decoder mode, with ABI assertions and focused null/build-policy tests. Test Plan: Not run in this atomic commit; the capped serial Rust, native, smoke, and original test-suite verification follows. --- lib/decompress/zstd_decompress_block.c | 31 ++++++++--- rust/src/zstd_decompress_block.rs | 71 +++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lib/decompress/zstd_decompress_block.c b/lib/decompress/zstd_decompress_block.c index 1fd9f89bc..dc8ab4258 100644 --- a/lib/decompress/zstd_decompress_block.c +++ b/lib/decompress/zstd_decompress_block.c @@ -25,13 +25,19 @@ #error "Cannot force the use of the short and the long ZSTD_decompressSequences variants!" #endif -#if defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) -#define ZSTD_RUST_SEQUENCE_DECODER_MODE 1 -#elif defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) -#define ZSTD_RUST_SEQUENCE_DECODER_MODE 2 -#else -#define ZSTD_RUST_SEQUENCE_DECODER_MODE 0 -#endif +typedef struct { + int forceShort; + int forceLong; +} ZSTD_rustSequenceDecoderPolicy; + +typedef char ZSTD_rust_sequence_decoder_policy_layout[ + (offsetof(ZSTD_rustSequenceDecoderPolicy, forceShort) == 0 + && offsetof(ZSTD_rustSequenceDecoderPolicy, forceLong) == sizeof(int) + && sizeof(ZSTD_rustSequenceDecoderPolicy) == 2 * sizeof(int)) + ? 1 : -1]; + +int ZSTD_rust_sequence_decoder_mode( + const ZSTD_rustSequenceDecoderPolicy* policy); typedef char ZSTD_rust_block_seq_symbol_layout[(sizeof(ZSTD_seqSymbol) == 8) ? 1 : -1]; typedef char ZSTD_rust_block_entropy_rep_offset[ @@ -72,6 +78,15 @@ typedef struct { static ZSTD_rustBlockCtx ZSTD_rust_block_context(ZSTD_DCtx* dctx) { ZSTD_rustBlockCtx ctx; + ZSTD_rustSequenceDecoderPolicy const sequencePolicy = { +#if defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) + 1, 0 +#elif defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) + 0, 1 +#else + 0, 0 +#endif + }; ctx.lltPtr = &dctx->LLTptr; ctx.mltPtr = &dctx->MLTptr; ctx.oftPtr = &dctx->OFTptr; @@ -98,7 +113,7 @@ static ZSTD_rustBlockCtx ZSTD_rust_block_context(ZSTD_DCtx* dctx) ctx.litBufferLocation = &dctx->litBufferLocation; ctx.litExtraBuffer = dctx->litExtraBuffer; ctx.litExtraBufferSize = ZSTD_LITBUFFEREXTRASIZE; - ctx.sequenceDecoderMode = ZSTD_RUST_SEQUENCE_DECODER_MODE; + ctx.sequenceDecoderMode = ZSTD_rust_sequence_decoder_mode(&sequencePolicy); return ctx; } diff --git a/rust/src/zstd_decompress_block.rs b/rust/src/zstd_decompress_block.rs index fe15aa060..d27c8f54b 100644 --- a/rust/src/zstd_decompress_block.rs +++ b/rust/src/zstd_decompress_block.rs @@ -33,7 +33,7 @@ use crate::mem::{MEM_32bits, MEM_64bits, MEM_readLE16, MEM_readLE24, U32}; use crate::zstd_decompress::ZSTD_DCtx; use std::cmp::min; use std::ffi::c_void; -use std::mem::MaybeUninit; +use std::mem::{offset_of, size_of, MaybeUninit}; use std::os::raw::{c_int, c_short, c_uint}; use std::ptr; use std::sync::OnceLock; @@ -68,6 +68,47 @@ const LONG_OFFSET_HISTORY_THRESHOLD: usize = 1 << 24; const LONG_OFFSET_MIN_SHARE_32: u32 = 20; const LONG_OFFSET_MIN_SHARE_64: u32 = 7; +/// Build-system sequence-decoder choices passed without exposing `ZSTD_DCtx`. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rustSequenceDecoderPolicy { + force_short: c_int, + force_long: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rustSequenceDecoderPolicy, force_short) == 0); + assert!( + offset_of!(ZSTD_rustSequenceDecoderPolicy, force_long) == size_of::() + ); + assert!(size_of::() == 2 * size_of::()); +}; + +#[inline] +fn sequence_decoder_mode(policy: &ZSTD_rustSequenceDecoderPolicy) -> c_int { + if policy.force_short != 0 { + SEQUENCE_DECODER_FORCE_SHORT + } else if policy.force_long != 0 { + SEQUENCE_DECODER_FORCE_LONG + } else { + SEQUENCE_DECODER_RUNTIME + } +} + +/// Normalize the build's sequence-decoder choice before block dispatch. +/// +/// C retains the compile-time mutual-exclusion check and private context +/// projection; Rust owns the scalar mode selected by that public build policy. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_sequence_decoder_mode( + policy: *const ZSTD_rustSequenceDecoderPolicy, +) -> c_int { + let Some(policy) = (unsafe { policy.as_ref() }) else { + return SEQUENCE_DECODER_RUNTIME; + }; + sequence_decoder_mode(policy) +} + const LL_BASE: [u32; MAX_LL + 1] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, 48, 64, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000, @@ -1892,6 +1933,34 @@ pub unsafe extern "C" fn ZSTD_decompressBlock( mod tests { use super::*; + #[test] + fn sequence_decoder_mode_preserves_build_forcing_policy() { + let runtime = ZSTD_rustSequenceDecoderPolicy { + force_short: 0, + force_long: 0, + }; + let short = ZSTD_rustSequenceDecoderPolicy { + force_short: 1, + force_long: 0, + }; + let long = ZSTD_rustSequenceDecoderPolicy { + force_short: 0, + force_long: 1, + }; + + assert_eq!(sequence_decoder_mode(&runtime), SEQUENCE_DECODER_RUNTIME); + assert_eq!(sequence_decoder_mode(&short), SEQUENCE_DECODER_FORCE_SHORT); + assert_eq!(sequence_decoder_mode(&long), SEQUENCE_DECODER_FORCE_LONG); + } + + #[test] + fn sequence_decoder_mode_null_policy_defaults_to_runtime() { + assert_eq!( + unsafe { ZSTD_rust_sequence_decoder_mode(ptr::null()) }, + SEQUENCE_DECODER_RUNTIME + ); + } + #[test] fn offset_code_22_and_23_keep_their_22_and_23_bit_bases() { assert_eq!(OF_BASE[22], 0x3F_FFFD);