Files
zstd-rs/rust/src/mem.rs
T
ddidderr fc4da0531a feat(rust): move wildcopy behind a narrow ABI leaf
Keep the C header's ZSTD_wildcopy signature as the enum-facing wrapper, but
move its over-copying implementation into rust/src/mem.rs. The wrapper passes
the enum's int representation to ZSTD_rust_wildcopy, where only the two valid
values are converted to ZstdOverlap. This leaves C's ZSTD_copy16/COPY16
helpers available to their direct compression callers and removes the now
unused COPY8 helper. ZSTD_copy16 is marked unused-safe for C translation units
that include the shared header without calling that direct helper.

The Rust leaf preserves the original first-copy behavior for zero and short
lengths, the source-before-destination 8-byte do-while path, the no-overlap
distance assertion, and the first-then-two-COPY16 loop. Its ABI contract does
not take ownership of caller buffers. ABI tests use 32-byte padded buffers and
exercise no-overlap plus offsets 8 and 15 across the boundary lengths, checking
both copied bytes and guard regions.

Test Plan:
- `cargo test mem::tests` -- passed (4 tests)
- `make lib-nomt` and `make lib-mt` -- passed
- `make -C tests test-zstream` -- passed
- `make -C tests test-fullbench` -- completed; its `-P0` run printed the
  existing Scenario 17 diagnostic, but the target returned normally
- `cargo clippy`, `cargo clippy --benches`, `cargo clippy --tests`,
  `cargo +nightly fmt`, then the same three clippy commands -- passed on the
  final repeat
- `git diff --cached --check` -- passed
2026-07-18 16:14:20 +02:00

414 lines
11 KiB
Rust

#![allow(non_snake_case)]
use std::mem;
use std::os::raw::c_int;
pub type U8 = u8;
pub type S8 = i8;
pub type U16 = u16;
pub type S16 = i16;
pub type U32 = u32;
pub type S32 = i32;
pub type U64 = u64;
pub type S64 = i64;
pub type BYTE = u8;
#[inline]
pub fn MEM_32bits() -> bool {
mem::size_of::<usize>() == 4
}
#[inline]
pub fn MEM_64bits() -> bool {
mem::size_of::<usize>() == 8
}
#[inline]
pub fn MEM_isLittleEndian() -> bool {
cfg!(target_endian = "little")
}
#[inline]
pub unsafe fn MEM_read16(mem_ptr: *const std::ffi::c_void) -> U16 {
(mem_ptr as *const U16).read_unaligned()
}
#[inline]
pub unsafe fn MEM_read32(mem_ptr: *const std::ffi::c_void) -> U32 {
(mem_ptr as *const U32).read_unaligned()
}
#[inline]
pub unsafe fn MEM_read64(mem_ptr: *const std::ffi::c_void) -> U64 {
(mem_ptr as *const U64).read_unaligned()
}
#[inline]
pub unsafe fn MEM_readST(mem_ptr: *const std::ffi::c_void) -> usize {
(mem_ptr as *const usize).read_unaligned()
}
#[inline]
pub unsafe fn MEM_write16(mem_ptr: *mut std::ffi::c_void, value: U16) {
(mem_ptr as *mut U16).write_unaligned(value);
}
#[inline]
pub unsafe fn MEM_write32(mem_ptr: *mut std::ffi::c_void, value: U32) {
(mem_ptr as *mut U32).write_unaligned(value);
}
#[inline]
pub unsafe fn MEM_write64(mem_ptr: *mut std::ffi::c_void, value: U64) {
(mem_ptr as *mut U64).write_unaligned(value);
}
#[inline]
pub unsafe fn MEM_readLE16(mem_ptr: *const std::ffi::c_void) -> U16 {
U16::from_le(MEM_read16(mem_ptr))
}
#[inline]
pub unsafe fn MEM_readLE24(mem_ptr: *const std::ffi::c_void) -> U32 {
let low = MEM_readLE16(mem_ptr);
let high = *mem_ptr.cast::<U8>().add(2) as U32;
(low as U32) | (high << 16)
}
#[inline]
pub unsafe fn MEM_readLE32(mem_ptr: *const std::ffi::c_void) -> U32 {
U32::from_le(MEM_read32(mem_ptr))
}
#[inline]
pub unsafe fn MEM_readLE64(mem_ptr: *const std::ffi::c_void) -> U64 {
U64::from_le(MEM_read64(mem_ptr))
}
#[inline]
pub unsafe fn MEM_readLEST(mem_ptr: *const std::ffi::c_void) -> usize {
if MEM_32bits() {
MEM_readLE32(mem_ptr) as usize
} else {
MEM_readLE64(mem_ptr) as usize
}
}
#[inline]
pub unsafe fn MEM_writeLE16(mem_ptr: *mut std::ffi::c_void, val: U16) {
MEM_write16(mem_ptr, val.to_le());
}
#[inline]
pub unsafe fn MEM_writeLE24(mem_ptr: *mut std::ffi::c_void, val: U32) {
MEM_writeLE16(mem_ptr, val as U16);
*mem_ptr.cast::<U8>().add(2) = (val >> 16) as U8;
}
#[inline]
pub unsafe fn MEM_writeLE32(mem_ptr: *mut std::ffi::c_void, val: U32) {
MEM_write32(mem_ptr, val.to_le());
}
#[inline]
pub unsafe fn MEM_writeLE64(mem_ptr: *mut std::ffi::c_void, val: U64) {
MEM_write64(mem_ptr, val.to_le());
}
#[inline]
pub unsafe fn MEM_writeLEST(mem_ptr: *mut std::ffi::c_void, val: usize) {
if MEM_32bits() {
MEM_writeLE32(mem_ptr, val as U32);
} else {
MEM_writeLE64(mem_ptr, val as U64);
}
}
#[inline]
pub unsafe fn MEM_readBE32(mem_ptr: *const std::ffi::c_void) -> U32 {
U32::from_be(MEM_read32(mem_ptr))
}
#[inline]
pub unsafe fn MEM_writeBE32(mem_ptr: *mut std::ffi::c_void, val: U32) {
MEM_write32(mem_ptr, val.to_be());
}
#[inline]
pub unsafe fn MEM_readBE64(mem_ptr: *const std::ffi::c_void) -> U64 {
U64::from_be(MEM_read64(mem_ptr))
}
#[inline]
pub unsafe fn MEM_writeBE64(mem_ptr: *mut std::ffi::c_void, val: U64) {
MEM_write64(mem_ptr, val.to_be());
}
#[inline]
pub unsafe fn MEM_readBEST(mem_ptr: *const std::ffi::c_void) -> usize {
if MEM_32bits() {
MEM_readBE32(mem_ptr) as usize
} else {
MEM_readBE64(mem_ptr) as usize
}
}
#[inline]
pub unsafe fn MEM_writeBEST(mem_ptr: *mut std::ffi::c_void, val: usize) {
if MEM_32bits() {
MEM_writeBE32(mem_ptr, val as U32);
} else {
MEM_writeBE64(mem_ptr, val as U64);
}
}
#[inline]
pub fn MEM_swap32(in_val: U32) -> U32 {
in_val.swap_bytes()
}
#[inline]
pub fn MEM_swap64(in_val: U64) -> U64 {
in_val.swap_bytes()
}
#[inline]
pub fn MEM_swapST(in_val: usize) -> usize {
if MEM_32bits() {
MEM_swap32(in_val as U32) as usize
} else {
MEM_swap64(in_val as U64) as usize
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ZstdOverlap {
NoOverlap,
OverlapSrcBeforeDst,
}
/// # Safety
/// `dst`/`src` must be valid for the wildcopy contract (may over-read/write up to 32 bytes).
#[inline]
pub unsafe fn ZSTD_wildcopy(
dst: *mut std::ffi::c_void,
src: *const std::ffi::c_void,
length: isize,
ovtype: ZstdOverlap,
) {
const WILDCOPY_VECLEN: isize = 16;
let mut op = dst as *mut U8;
let mut ip = src as *const U8;
let oend = op.wrapping_offset(length);
let diff = (op as isize).wrapping_sub(ip as isize);
if ovtype == ZstdOverlap::OverlapSrcBeforeDst && diff < WILDCOPY_VECLEN {
// Handle short offset copies.
loop {
std::ptr::copy_nonoverlapping(ip, op, 8);
op = op.add(8);
ip = ip.add(8);
if op >= oend {
break;
}
}
} else {
assert!(diff >= WILDCOPY_VECLEN || diff <= -WILDCOPY_VECLEN);
// Keep the first COPY16 separate because short copies are common.
std::ptr::copy_nonoverlapping(ip, op, 16);
if WILDCOPY_VECLEN >= length {
return;
}
op = op.add(16);
ip = ip.add(16);
loop {
std::ptr::copy_nonoverlapping(ip, op, 16);
op = op.add(16);
ip = ip.add(16);
std::ptr::copy_nonoverlapping(ip, op, 16);
op = op.add(16);
ip = ip.add(16);
if op >= oend {
break;
}
}
}
}
/// Rust implementation of the C `ZSTD_wildcopy()` ABI wrapper.
///
/// # Safety
/// `dst`/`src` must be valid for the wildcopy contract (may over-read/write up to 32 bytes),
/// and `ovtype` must be one of the two `ZSTD_overlap_e` values.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_wildcopy(
dst: *mut std::ffi::c_void,
src: *const std::ffi::c_void,
length: isize,
ovtype: c_int,
) {
let ovtype = match ovtype {
0 => ZstdOverlap::NoOverlap,
1 => ZstdOverlap::OverlapSrcBeforeDst,
_ => unreachable!("invalid ZSTD_overlap_e value"),
};
ZSTD_wildcopy(dst, src, length, ovtype);
}
/// # Safety
/// `dst` must be valid for `dst_capacity` bytes; `src` for `src_size` bytes.
#[inline]
pub unsafe fn ZSTD_limitCopy(
dst: *mut std::ffi::c_void,
dst_capacity: usize,
src: *const std::ffi::c_void,
src_size: usize,
) -> usize {
let length = std::cmp::min(dst_capacity, src_size);
if length > 0 {
std::ptr::copy_nonoverlapping(src as *const U8, dst as *mut U8, length);
}
length
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::c_void;
#[test]
fn unaligned_endian_io_round_trips() {
let mut bytes = [0u8; 24];
unsafe {
let p = bytes.as_mut_ptr().add(1).cast::<c_void>();
MEM_writeLE16(p, 0x1234);
assert_eq!(&bytes[1..3], &[0x34, 0x12]);
assert_eq!(MEM_readLE16(p), 0x1234);
MEM_writeLE24(p, 0x00ab_cdef);
assert_eq!(&bytes[1..4], &[0xef, 0xcd, 0xab]);
assert_eq!(MEM_readLE24(p), 0x00ab_cdef);
MEM_writeLE64(p, 0x0123_4567_89ab_cdef);
assert_eq!(MEM_readLE64(p), 0x0123_4567_89ab_cdef);
MEM_writeBE64(p, 0x0123_4567_89ab_cdef);
assert_eq!(&bytes[1..9], &[1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
assert_eq!(MEM_readBE64(p), 0x0123_4567_89ab_cdef);
}
}
const WILDCOPY_REDZONE: usize = 32;
const WILDCOPY_LENGTHS: &[usize] = &[0, 1, 16, 17, 31, 32, 33, 63, 64];
fn no_overlap_write_len(length: usize) -> usize {
if length <= 16 {
16
} else {
16 + (length - 16).div_ceil(32) * 32
}
}
fn short_overlap_write_len(length: usize) -> usize {
length.max(8).div_ceil(8) * 8
}
fn assert_filled(bytes: &[u8], value: u8) {
assert!(bytes.iter().all(|byte| *byte == value));
}
#[test]
fn wildcopy_abi_no_overlap_respects_contract_boundaries() {
for &length in WILDCOPY_LENGTHS {
let written = no_overlap_write_len(length);
let source_start = WILDCOPY_REDZONE;
let destination_start = source_start + written + 2 * WILDCOPY_REDZONE;
let total_size = destination_start + written + WILDCOPY_REDZONE;
let mut bytes = vec![0xa5; total_size];
for (index, byte) in bytes[source_start..source_start + written]
.iter_mut()
.enumerate()
{
*byte = (index as u8).wrapping_mul(17).wrapping_add(3);
}
bytes[destination_start..destination_start + written].fill(0xcc);
let source = bytes[source_start..source_start + written].to_vec();
unsafe {
ZSTD_rust_wildcopy(
bytes.as_mut_ptr().add(destination_start).cast(),
bytes.as_ptr().add(source_start).cast(),
length as isize,
0,
);
}
assert_filled(&bytes[..source_start], 0xa5);
assert_eq!(&bytes[source_start..source_start + written], &source);
assert_filled(&bytes[source_start + written..destination_start], 0xa5);
assert_eq!(
&bytes[destination_start..destination_start + written],
&source,
"length={length}"
);
assert_filled(&bytes[destination_start + written..], 0xa5);
}
}
#[test]
fn wildcopy_abi_short_offsets_respect_contract_boundaries() {
for &offset in &[8usize, 15] {
for &length in WILDCOPY_LENGTHS {
let written = short_overlap_write_len(length);
let source_start = WILDCOPY_REDZONE;
let destination_start = source_start + offset;
let data_end = destination_start + written;
let total_size = data_end + WILDCOPY_REDZONE;
let mut bytes = vec![0xa5; total_size];
for (index, byte) in bytes[source_start..data_end].iter_mut().enumerate() {
*byte = (index % offset) as u8 + 1;
}
unsafe {
ZSTD_rust_wildcopy(
bytes.as_mut_ptr().add(destination_start).cast(),
bytes.as_ptr().add(source_start).cast(),
length as isize,
1,
);
}
assert_filled(&bytes[..source_start], 0xa5);
assert_eq!(
&bytes[destination_start..data_end],
&(1..=offset)
.cycle()
.take(written)
.map(|value| value as u8)
.collect::<Vec<_>>(),
"offset={offset}, length={length}"
);
assert_filled(&bytes[data_end..], 0xa5);
}
}
}
#[test]
fn limit_copy_reports_the_bytes_written() {
let src = *b"abcdef";
let mut dst = [0u8; 4];
let copied = unsafe {
ZSTD_limitCopy(
dst.as_mut_ptr().cast(),
dst.len(),
src.as_ptr().cast(),
src.len(),
)
};
assert_eq!(copied, 4);
assert_eq!(&dst, b"abcd");
}
}