Introduce the Rust static library and move the shared byte, bitstream, CPU, error, xxHash, debug, and public-common implementations into it. Thin C shims preserve the existing header-driven C build while original tests link the Rust archive. This establishes the ABI-safe foundation for later codec and CLI ports; entropy coding, runtime support, codecs, dictionaries, and the CLI remain C. The top-down migration map documents that boundary and its validation path. Test Plan: - cargo fmt --check - cargo test --all-targets - cargo clippy --all-targets -- -D warnings - cargo build --release Refs: rust/README.md
326 lines
8.0 KiB
Rust
326 lines
8.0 KiB
Rust
#![allow(non_snake_case)]
|
|
use std::mem;
|
|
|
|
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 {
|
|
loop {
|
|
std::ptr::copy_nonoverlapping(ip, op, 8);
|
|
op = op.add(8);
|
|
ip = ip.add(8);
|
|
if op >= oend {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
std::ptr::copy_nonoverlapping(ip, op, 16);
|
|
if length <= 16 {
|
|
return;
|
|
}
|
|
op = op.add(16);
|
|
ip = ip.add(16);
|
|
while op < oend {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # 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);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn wildcopy_always_performs_its_first_copy() {
|
|
let mut bytes = [0u8; 96];
|
|
for (index, byte) in bytes[..32].iter_mut().enumerate() {
|
|
*byte = index as u8;
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_wildcopy(
|
|
bytes.as_mut_ptr().add(64).cast(),
|
|
bytes.as_ptr().cast(),
|
|
0,
|
|
ZstdOverlap::NoOverlap,
|
|
);
|
|
}
|
|
assert_eq!(&bytes[64..80], &bytes[..16]);
|
|
}
|
|
|
|
#[test]
|
|
fn short_offset_wildcopy_is_do_while_like() {
|
|
let mut bytes = [0u8; 32];
|
|
for (index, byte) in bytes[..8].iter_mut().enumerate() {
|
|
*byte = (index + 1) as u8;
|
|
}
|
|
|
|
unsafe {
|
|
ZSTD_wildcopy(
|
|
bytes.as_mut_ptr().add(8).cast(),
|
|
bytes.as_ptr().cast(),
|
|
0,
|
|
ZstdOverlap::OverlapSrcBeforeDst,
|
|
);
|
|
}
|
|
assert_eq!(&bytes[8..16], &[1, 2, 3, 4, 5, 6, 7, 8]);
|
|
}
|
|
|
|
#[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");
|
|
}
|
|
}
|