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).
This commit is contained in:
2026-07-18 04:31:43 +02:00
parent d5ae2a450b
commit 5362112c9c
2 changed files with 154 additions and 14 deletions
+22 -14
View File
@@ -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)
{
+132
View File
@@ -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::<u64>();
let int = size_of::<c_int>();
assert_eq!(align_of::<FIO_fileInfo_t>(), align_of::<u64>());
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::<usize>() == 8 {
7 * word
} else {
13 * size_of::<u32>()
};
assert_eq!(size_of::<FIO_fileInfo_t>(), expected_size);
}
#[test]
fn lz4_block_size_shim_preserves_the_block_id_formula() {
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(0), 1 << 8);