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:
+22
-14
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user