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.
This commit is contained in:
2026-07-21 09:12:42 +02:00
parent b0b7441e94
commit cb770812fd
2 changed files with 61 additions and 2 deletions
+13 -2
View File
@@ -196,6 +196,14 @@ typedef char ZSTD_rust_no_forward_progress_projection_layout[
? 1 : -1]; ? 1 : -1];
int ZSTD_rust_no_forward_progress_policy( int ZSTD_rust_no_forward_progress_policy(
const ZSTD_rustNoForwardProgressProjection* projection); 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 { typedef struct {
unsigned configured_level; unsigned configured_level;
} ZSTD_rustLegacySupportProjection; } ZSTD_rustLegacySupportProjection;
@@ -333,7 +341,10 @@ int ZSTD_rust_no_forward_progress_max(void)
int ZSTD_rust_heapmode(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, 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_DCtx dctx;
ZSTD_rustDecompressStackProjection const projection = { ZSTD_rustDecompressStackProjection const projection = {
ZSTD_HEAPMODE, ZSTD_rust_heapmode(),
&dctx, &dctx,
sizeof(dctx), sizeof(dctx),
dst, dst,
+48
View File
@@ -321,6 +321,17 @@ const _: () = {
assert!(size_of::<ZSTD_rustNoForwardProgressProjection>() == size_of::<c_int>()); assert!(size_of::<ZSTD_rustNoForwardProgressProjection>() == size_of::<c_int>());
}; };
#[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::<ZSTD_rustHeapModeProjection>() == size_of::<c_int>());
};
const _: () = { const _: () = {
assert!(offset_of!(ZSTD_rustDctxView, dctx) == 0); assert!(offset_of!(ZSTD_rustDctxView, dctx) == 0);
assert!(offset_of!(ZSTD_rustDctxView, static_size) == 29 * size_of::<usize>()); assert!(offset_of!(ZSTD_rustDctxView, static_size) == 29 * size_of::<usize>());
@@ -682,6 +693,21 @@ pub unsafe extern "C" fn ZSTD_rust_no_forward_progress_policy(
projection.configured_max 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)] #[cfg(test)]
mod no_forward_progress_policy_tests { mod no_forward_progress_policy_tests {
use super::*; 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. /// Normalize the C build's legacy-support threshold before dispatch.
/// ///
/// C supplies the active compile-time value because test builds intentionally /// C supplies the active compile-time value because test builds intentionally