From cb770812fd51f877da7a7eff7f26d9507d3787f0 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 09:12:42 +0200 Subject: [PATCH] refactor(decompress): move heap mode policy to Rust `ZSTD_HEAPMODE` was still returned directly from C, and the stack entry projection also copied the raw macro. That left the one-shot stack-versus-heap classification in the C configuration shim even though Rust owns the public decompression branch and stack action. Keep the build-time override in C, but pass it through a one-field repr(C) projection to Rust. Rust normalizes values below one to the stack mode and values at or above one to the heap mode, preserving the existing behavior while making the branch policy explicit. The C stack object, private DCtx layout, platform initialization, and trace boundary remain C-owned; moving those would either duplicate build configuration or cross the requested ABI boundary. The stack projection now uses the same normalized result as the one-shot branch. ABI layout assertions cover the scalar projection, and focused Rust tests cover negative, zero, positive, maximum, and missing-configuration inputs. Test Plan: - `git diff --cached --check` -- passed - `rustfmt --edition 2021 --check rust/src/zstd_decompress.rs` -- passed - Cargo, Make, native tests, and heavy commands intentionally not run per the task restriction; integration verification remains pending. --- lib/decompress/zstd_decompress.c | 15 ++++++++-- rust/src/zstd_decompress.rs | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index b9602d51e..546c27b7e 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -196,6 +196,14 @@ typedef char ZSTD_rust_no_forward_progress_projection_layout[ ? 1 : -1]; int ZSTD_rust_no_forward_progress_policy( const ZSTD_rustNoForwardProgressProjection* projection); +typedef struct { + int configured_mode; +} ZSTD_rustHeapModeProjection; +typedef char ZSTD_rust_heapmode_projection_layout[ + (offsetof(ZSTD_rustHeapModeProjection, configured_mode) == 0 + && sizeof(ZSTD_rustHeapModeProjection) == sizeof(int)) + ? 1 : -1]; +int ZSTD_rust_heapmode_policy(const ZSTD_rustHeapModeProjection* projection); typedef struct { unsigned configured_level; } ZSTD_rustLegacySupportProjection; @@ -333,7 +341,10 @@ int ZSTD_rust_no_forward_progress_max(void) int ZSTD_rust_heapmode(void) { - return ZSTD_HEAPMODE; + ZSTD_rustHeapModeProjection const projection = { + ZSTD_HEAPMODE + }; + return ZSTD_rust_heapmode_policy(&projection); } size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity, @@ -341,7 +352,7 @@ size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity, { ZSTD_DCtx dctx; ZSTD_rustDecompressStackProjection const projection = { - ZSTD_HEAPMODE, + ZSTD_rust_heapmode(), &dctx, sizeof(dctx), dst, diff --git a/rust/src/zstd_decompress.rs b/rust/src/zstd_decompress.rs index 477918d7a..a87ac47d8 100644 --- a/rust/src/zstd_decompress.rs +++ b/rust/src/zstd_decompress.rs @@ -321,6 +321,17 @@ const _: () = { assert!(size_of::() == size_of::()); }; +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rustHeapModeProjection { + pub configured_mode: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rustHeapModeProjection, configured_mode) == 0); + assert!(size_of::() == size_of::()); +}; + const _: () = { assert!(offset_of!(ZSTD_rustDctxView, dctx) == 0); assert!(offset_of!(ZSTD_rustDctxView, static_size) == 29 * size_of::()); @@ -682,6 +693,21 @@ pub unsafe extern "C" fn ZSTD_rust_no_forward_progress_policy( projection.configured_max } +/// Normalize the C build's heap-mode setting to the one-shot branch policy. +/// +/// C supplies the compile-time value because it may be overridden by the +/// build. Rust owns the decision that values below one use the stack entry +/// path and values at or above one use an allocated DCtx. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_heapmode_policy( + projection: *const ZSTD_rustHeapModeProjection, +) -> c_int { + let Some(projection) = (unsafe { projection.as_ref() }) else { + return 0; + }; + c_int::from(projection.configured_mode >= 1) +} + #[cfg(test)] mod no_forward_progress_policy_tests { use super::*; @@ -706,6 +732,28 @@ mod no_forward_progress_policy_tests { } } +#[cfg(test)] +mod heapmode_policy_tests { + use super::*; + + #[test] + fn normalizes_stack_and_heap_modes() { + for configured_mode in [c_int::MIN, -1, 0] { + let projection = ZSTD_rustHeapModeProjection { configured_mode }; + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(&projection) }, 0); + } + for configured_mode in [1, c_int::MAX] { + let projection = ZSTD_rustHeapModeProjection { configured_mode }; + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(&projection) }, 1); + } + } + + #[test] + fn rejects_missing_heapmode_configuration() { + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(ptr::null()) }, 0); + } +} + /// Normalize the C build's legacy-support threshold before dispatch. /// /// C supplies the active compile-time value because test builds intentionally