From 5362112c9c604355448ad55ac6ac0021fd7836f6 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 04:31:43 +0200 Subject: [PATCH] feat(cli): move file info aggregation to Rust Mirror fileio.c's private fileInfo_t with a checked repr(C) ABI and aggregate totals, flags, and file counts through an output-pointer shim. Preserve zeroed non-aggregate fields and use wrapping arithmetic for the original integer operations. Test Plan: cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features cli,compression,decompression,benchmark (106 passed); cargo clippy --all-targets with the same features; make -B -C programs -j2 zstd zstd-small zstd-frugal; make -C tests -j2 test-cli-tests (41 passed). --- programs/fileio.c | 36 ++++++----- rust/src/fileio_prefs.rs | 132 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 14 deletions(-) diff --git a/programs/fileio.c b/programs/fileio.c index cf46b02ab..eb012a29d 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -2780,6 +2780,28 @@ typedef struct { unsigned dictID; } fileInfo_t; +typedef char FIO_rust_file_info_compressed_size_offset[ + (offsetof(fileInfo_t, compressedSize) == sizeof(U64)) ? 1 : -1]; +typedef char FIO_rust_file_info_window_size_offset[ + (offsetof(fileInfo_t, windowSize) == 2 * sizeof(U64)) ? 1 : -1]; +typedef char FIO_rust_file_info_frame_counts_offset[ + (offsetof(fileInfo_t, numActualFrames) == 3 * sizeof(U64)) ? 1 : -1]; +typedef char FIO_rust_file_info_checksum_offset[ + (offsetof(fileInfo_t, checksum) == 3 * sizeof(U64) + 4 * sizeof(int)) ? 1 : -1]; +typedef char FIO_rust_file_info_size[ + (sizeof(fileInfo_t) == (sizeof(void*) == 8 + ? 7 * sizeof(U64) + : 13 * sizeof(U32))) ? 1 : -1]; + +void FIO_rust_addFInfo(fileInfo_t* output, fileInfo_t const* fi1, fileInfo_t const* fi2); + +static fileInfo_t FIO_addFInfo(fileInfo_t fi1, fileInfo_t fi2) +{ + fileInfo_t total; + FIO_rust_addFInfo(&total, &fi1, &fi2); + return total; +} + typedef enum { info_success=0, info_frame_error=1, @@ -2978,20 +3000,6 @@ displayInfo(const char* inFileName, const fileInfo_t* info, int displayLevel) } } -static fileInfo_t FIO_addFInfo(fileInfo_t fi1, fileInfo_t fi2) -{ - fileInfo_t total; - memset(&total, 0, sizeof(total)); - total.numActualFrames = fi1.numActualFrames + fi2.numActualFrames; - total.numSkippableFrames = fi1.numSkippableFrames + fi2.numSkippableFrames; - total.compressedSize = fi1.compressedSize + fi2.compressedSize; - total.decompressedSize = fi1.decompressedSize + fi2.decompressedSize; - total.decompUnavailable = fi1.decompUnavailable | fi2.decompUnavailable; - total.usesCheck = fi1.usesCheck & fi2.usesCheck; - total.nbFiles = fi1.nbFiles + fi2.nbFiles; - return total; -} - static int FIO_listFile(fileInfo_t* total, const char* inFileName, int displayLevel) { diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index 7fc22c975..898b06466 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -40,6 +40,21 @@ pub struct FIO_outBuffer { pos: usize, } +/// C's private `fileInfo_t` from `programs/fileio.c`. +#[repr(C)] +pub struct FIO_fileInfo_t { + decompressedSize: u64, + compressedSize: u64, + windowSize: u64, + numActualFrames: c_int, + numSkippableFrames: c_int, + decompUnavailable: c_int, + usesCheck: c_int, + checksum: [u8; 4], + nbFiles: u32, + dictID: c_uint, +} + #[cfg(target_vendor = "apple")] const ZSTD_SPARSE_DEFAULT: c_int = 0; #[cfg(not(target_vendor = "apple"))] @@ -574,6 +589,40 @@ pub unsafe extern "C" fn FIO_rust_setOutBuffer( } } +/// Aggregate two file-info records through an output pointer to avoid a +/// cross-language struct return ABI. +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_addFInfo( + output: *mut FIO_fileInfo_t, + fi1: *const FIO_fileInfo_t, + fi2: *const FIO_fileInfo_t, +) { + let fi1 = unsafe { &*fi1 }; + let fi2 = unsafe { &*fi2 }; + let mut total = FIO_fileInfo_t { + decompressedSize: 0, + compressedSize: 0, + windowSize: 0, + numActualFrames: 0, + numSkippableFrames: 0, + decompUnavailable: 0, + usesCheck: 0, + checksum: [0; 4], + nbFiles: 0, + dictID: 0, + }; + + total.numActualFrames = fi1.numActualFrames.wrapping_add(fi2.numActualFrames); + total.numSkippableFrames = fi1.numSkippableFrames.wrapping_add(fi2.numSkippableFrames); + total.compressedSize = fi1.compressedSize.wrapping_add(fi2.compressedSize); + total.decompressedSize = fi1.decompressedSize.wrapping_add(fi2.decompressedSize); + total.decompUnavailable = fi1.decompUnavailable | fi2.decompUnavailable; + total.usesCheck = fi1.usesCheck & fi2.usesCheck; + total.nbFiles = fi1.nbFiles.wrapping_add(fi2.nbFiles); + + unsafe { output.write(total) }; +} + #[inline] fn lz4_block_size_from_block_id(id: c_int) -> c_int { 1 << (8 + 2 * id) @@ -971,6 +1020,89 @@ mod tests { assert_eq!(output.pos, 2); } + #[test] + fn file_info_aggregation_preserves_totals_flags_and_zero_fields() { + let first = FIO_fileInfo_t { + decompressedSize: 100, + compressedSize: 40, + windowSize: 1, + numActualFrames: 2, + numSkippableFrames: 3, + decompUnavailable: 0, + usesCheck: 1, + checksum: [1, 2, 3, 4], + nbFiles: 5, + dictID: 6, + }; + let second = FIO_fileInfo_t { + decompressedSize: 200, + compressedSize: 60, + windowSize: 2, + numActualFrames: 7, + numSkippableFrames: 11, + decompUnavailable: 1, + usesCheck: 0, + checksum: [5, 6, 7, 8], + nbFiles: 13, + dictID: 14, + }; + let mut total = FIO_fileInfo_t { + decompressedSize: u64::MAX, + compressedSize: u64::MAX, + windowSize: u64::MAX, + numActualFrames: -1, + numSkippableFrames: -1, + decompUnavailable: -1, + usesCheck: -1, + checksum: [u8::MAX; 4], + nbFiles: u32::MAX, + dictID: c_uint::MAX, + }; + + unsafe { FIO_rust_addFInfo(&mut total, &first, &second) }; + + assert_eq!(total.decompressedSize, 300); + assert_eq!(total.compressedSize, 100); + assert_eq!(total.windowSize, 0); + assert_eq!(total.numActualFrames, 9); + assert_eq!(total.numSkippableFrames, 14); + assert_eq!(total.decompUnavailable, 1); + assert_eq!(total.usesCheck, 0); + assert_eq!(total.checksum, [0; 4]); + assert_eq!(total.nbFiles, 18); + assert_eq!(total.dictID, 0); + } + + #[test] + fn file_info_layout_matches_private_c_record() { + let word = size_of::(); + let int = size_of::(); + + assert_eq!(align_of::(), align_of::()); + assert_eq!(offset_of!(FIO_fileInfo_t, decompressedSize), 0); + assert_eq!(offset_of!(FIO_fileInfo_t, compressedSize), word); + assert_eq!(offset_of!(FIO_fileInfo_t, windowSize), 2 * word); + assert_eq!(offset_of!(FIO_fileInfo_t, numActualFrames), 3 * word); + assert_eq!( + offset_of!(FIO_fileInfo_t, numSkippableFrames), + 3 * word + int + ); + assert_eq!( + offset_of!(FIO_fileInfo_t, decompUnavailable), + 3 * word + 2 * int + ); + assert_eq!(offset_of!(FIO_fileInfo_t, usesCheck), 3 * word + 3 * int); + assert_eq!(offset_of!(FIO_fileInfo_t, checksum), 3 * word + 4 * int); + assert_eq!(offset_of!(FIO_fileInfo_t, nbFiles), 3 * word + 4 * int + 4); + assert_eq!(offset_of!(FIO_fileInfo_t, dictID), 3 * word + 4 * int + 8); + let expected_size = if size_of::() == 8 { + 7 * word + } else { + 13 * size_of::() + }; + assert_eq!(size_of::(), expected_size); + } + #[test] fn lz4_block_size_shim_preserves_the_block_id_formula() { assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(0), 1 << 8);