feat(rust): port program timing helpers
Move the implementation of programs/timefn.c into rust/src/timefn.rs. The file provides the monotonic nanosecond clock (UTIL_getTime, span helpers, UTIL_waitForNextTick, UTIL_support_MT_measurements) used by the CLI and by several C test tools. timefn.c remains as a declaration-only shim so the original source lists and header configuration keep working, and it pins the ABI with static asserts: UTIL_time_t is returned by value and must stay a plain 64-bit counter, which the Rust #[repr(C)] mirror also asserts. Platform selection mirrors the C preprocessor structure: Windows uses QueryPerformanceCounter, Apple targets use mach_absolute_time, and other POSIX systems use libc clock_gettime(CLOCK_MONOTONIC). Only the unix path is exercised by this environment; the Windows and Apple paths are written from the C source and compile-checked logically but are untested here. The C90 clock() fallback is unreachable on Rust-supported targets, so multi-threaded measurement support is always reported. The symbols live in the program-only zstd-cli-rs package, keeping them out of library builds. Linking that archive into C test binaries surfaced a structural problem: rustc's local ThinLTO promotes internal symbols across codegen units, so extracting the timefn object could drag in the zstd_cli parser object, whose FIO_* externs test binaries cannot satisfy. The parser is therefore gated behind a new additive `cli` cargo feature (default on). Program archives build with cli,compression,decompression as before, while tests/Makefile links a helpers-only archive (rust/target/cli-helpers) built with --no-default-features, which contains no fileio references at all. tests/Makefile gains build rules for the helpers archive and adds it as a prerequisite of every binary that compiles the timefn shim: fullbench(32), fullbench-lib, fullbench-dll, fuzzer(32), zstreamtest(32/asan/tsan/ubsan), paramgrill, decodecorpus, and poolTests. Prerequisite order places the archive after all C objects in `$^` link lines; the known-broken -dll recipes filter to %.c, so they name the archive explicitly. Original C test sources are untouched; only link inputs changed. zstd-cli-rs now depends on libc (already used by the core crate) for clock_gettime and the Mach timebase bindings. Test Plan: - cd rust && cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test --all-targets && cargo build --release - cd rust/cli && cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test --all-targets; repeat tests with --no-default-features plus features compression / decompression / (none) - make -C programs zstd; roundtrip echo hello | zstd | zstd -d - make -C tests fullbench fuzzer zstreamtest paramgrill decodecorpus poolTests; ./tests/fullbench -i0; ./tests/fuzzer -i1 --no-big-tests; ./tests/poolTests; make -C tests test-rust-lib-smoke - verified with nm that the helpers archive member defining UTIL_getTime has no FIO_*/ZSTD_* undefined references Refs: rust/README.md
This commit is contained in:
+5
-4
@@ -32,7 +32,8 @@ RUST_CLI_MANIFEST := $(RUST_CLI_DIR)/Cargo.toml
|
||||
RUST_SOURCES := $(RUST_MANIFEST) $(RUST_DIR)/Cargo.lock \
|
||||
$(shell find $(RUST_DIR)/src -type f -name '*.rs' -print)
|
||||
RUST_CLI_SOURCES := $(RUST_CLI_MANIFEST) $(RUST_CLI_DIR)/Cargo.lock \
|
||||
$(RUST_CLI_DIR)/src/lib.rs $(RUST_DIR)/src/zstd_cli.rs
|
||||
$(RUST_CLI_DIR)/src/lib.rs $(RUST_DIR)/src/zstd_cli.rs \
|
||||
$(RUST_DIR)/src/timefn.rs
|
||||
|
||||
# Keep Rust's HUF implementation in lockstep with libzstd.mk's C selection.
|
||||
# Forced modes may arrive as libzstd.mk variables or as direct -D flags in
|
||||
@@ -88,7 +89,7 @@ RUST_CLI_STATICLIB := $(RUST_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
|
||||
RUST_CLI_STATICLIB_32 := $(RUST_CLI_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
|
||||
RUST_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_CLI_TARGET_DIR) \
|
||||
--no-default-features --features compression,decompression
|
||||
--no-default-features --features cli,compression,decompression
|
||||
|
||||
$(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_CLI_CARGO_FLAGS)
|
||||
@@ -114,7 +115,7 @@ RUST_DECOMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_CLI_BUILD
|
||||
RUST_DECOMPRESS_CLI_STATICLIB := $(RUST_DECOMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
|
||||
RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_DECOMPRESS_CLI_TARGET_DIR) \
|
||||
--no-default-features --features decompression
|
||||
--no-default-features --features cli,decompression
|
||||
|
||||
$(RUST_DECOMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_DECOMPRESS_CLI_CARGO_FLAGS)
|
||||
@@ -134,7 +135,7 @@ RUST_COMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_CLI_BUILD_CON
|
||||
RUST_COMPRESS_CLI_STATICLIB := $(RUST_COMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
|
||||
RUST_COMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_COMPRESS_CLI_TARGET_DIR) \
|
||||
--no-default-features --features compression
|
||||
--no-default-features --features cli,compression
|
||||
|
||||
$(RUST_COMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_COMPRESS_CLI_CARGO_FLAGS)
|
||||
|
||||
+8
-155
@@ -8,161 +8,14 @@
|
||||
* You may select, at your option, one of the above-listed licenses.
|
||||
*/
|
||||
|
||||
|
||||
/* === Dependencies === */
|
||||
/* The implementation lives in rust/src/timefn.rs, built into the Rust CLI
|
||||
* static archive. This translation unit stays in the original source lists so
|
||||
* build configuration keeps working while the implementation is in Rust. */
|
||||
|
||||
#include "timefn.h"
|
||||
#include "platform.h" /* set _POSIX_C_SOURCE */
|
||||
#include <time.h> /* CLOCK_MONOTONIC, TIME_UTC */
|
||||
|
||||
/*-****************************************
|
||||
* Time functions
|
||||
******************************************/
|
||||
|
||||
#if defined(_WIN32) /* Windows */
|
||||
|
||||
#include <windows.h> /* LARGE_INTEGER */
|
||||
#include <stdlib.h> /* abort */
|
||||
#include <stdio.h> /* perror */
|
||||
|
||||
UTIL_time_t UTIL_getTime(void)
|
||||
{
|
||||
static LARGE_INTEGER ticksPerSecond;
|
||||
static int init = 0;
|
||||
if (!init) {
|
||||
if (!QueryPerformanceFrequency(&ticksPerSecond)) {
|
||||
perror("timefn::QueryPerformanceFrequency");
|
||||
abort();
|
||||
}
|
||||
init = 1;
|
||||
}
|
||||
{ UTIL_time_t r;
|
||||
LARGE_INTEGER x;
|
||||
QueryPerformanceCounter(&x);
|
||||
r.t = (PTime)(x.QuadPart * 1000000000ULL / ticksPerSecond.QuadPart);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
|
||||
#include <mach/mach_time.h> /* mach_timebase_info_data_t, mach_timebase_info, mach_absolute_time */
|
||||
|
||||
UTIL_time_t UTIL_getTime(void)
|
||||
{
|
||||
static mach_timebase_info_data_t rate;
|
||||
static int init = 0;
|
||||
if (!init) {
|
||||
mach_timebase_info(&rate);
|
||||
init = 1;
|
||||
}
|
||||
{ UTIL_time_t r;
|
||||
r.t = mach_absolute_time() * (PTime)rate.numer / (PTime)rate.denom;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
/* POSIX.1-2001 (optional) */
|
||||
#elif defined(CLOCK_MONOTONIC)
|
||||
|
||||
#include <stdlib.h> /* abort */
|
||||
#include <stdio.h> /* perror */
|
||||
|
||||
UTIL_time_t UTIL_getTime(void)
|
||||
{
|
||||
/* time must be initialized, othersize it may fail msan test.
|
||||
* No good reason, likely a limitation of timespec_get() for some target */
|
||||
struct timespec time = { 0, 0 };
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) {
|
||||
perror("timefn::clock_gettime(CLOCK_MONOTONIC)");
|
||||
abort();
|
||||
}
|
||||
{ UTIL_time_t r;
|
||||
r.t = (PTime)time.tv_sec * 1000000000ULL + (PTime)time.tv_nsec;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* C11 requires support of timespec_get().
|
||||
* However, FreeBSD 11 claims C11 compliance while lacking timespec_get().
|
||||
* Double confirm timespec_get() support by checking the definition of TIME_UTC.
|
||||
* However, some versions of Android manage to simultaneously define TIME_UTC
|
||||
* and lack timespec_get() support... */
|
||||
#elif (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */) \
|
||||
&& defined(TIME_UTC) && !defined(__ANDROID__)
|
||||
|
||||
#include <stdlib.h> /* abort */
|
||||
#include <stdio.h> /* perror */
|
||||
|
||||
UTIL_time_t UTIL_getTime(void)
|
||||
{
|
||||
/* time must be initialized, othersize it may fail msan test.
|
||||
* No good reason, likely a limitation of timespec_get() for some target */
|
||||
struct timespec time = { 0, 0 };
|
||||
if (timespec_get(&time, TIME_UTC) != TIME_UTC) {
|
||||
perror("timefn::timespec_get(TIME_UTC)");
|
||||
abort();
|
||||
}
|
||||
{ UTIL_time_t r;
|
||||
r.t = (PTime)time.tv_sec * 1000000000ULL + (PTime)time.tv_nsec;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else /* relies on standard C90 (note : clock_t produces wrong measurements for multi-threaded workloads) */
|
||||
|
||||
UTIL_time_t UTIL_getTime(void)
|
||||
{
|
||||
UTIL_time_t r;
|
||||
r.t = (PTime)clock() * 1000000000ULL / CLOCKS_PER_SEC;
|
||||
return r;
|
||||
}
|
||||
|
||||
#define TIME_MT_MEASUREMENTS_NOT_SUPPORTED
|
||||
|
||||
#endif
|
||||
|
||||
/* ==== Common functions, valid for all time API ==== */
|
||||
|
||||
PTime UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd)
|
||||
{
|
||||
return clockEnd.t - clockStart.t;
|
||||
}
|
||||
|
||||
PTime UTIL_getSpanTimeMicro(UTIL_time_t begin, UTIL_time_t end)
|
||||
{
|
||||
return UTIL_getSpanTimeNano(begin, end) / 1000ULL;
|
||||
}
|
||||
|
||||
PTime UTIL_clockSpanMicro(UTIL_time_t clockStart )
|
||||
{
|
||||
UTIL_time_t const clockEnd = UTIL_getTime();
|
||||
return UTIL_getSpanTimeMicro(clockStart, clockEnd);
|
||||
}
|
||||
|
||||
PTime UTIL_clockSpanNano(UTIL_time_t clockStart )
|
||||
{
|
||||
UTIL_time_t const clockEnd = UTIL_getTime();
|
||||
return UTIL_getSpanTimeNano(clockStart, clockEnd);
|
||||
}
|
||||
|
||||
void UTIL_waitForNextTick(void)
|
||||
{
|
||||
UTIL_time_t const clockStart = UTIL_getTime();
|
||||
UTIL_time_t clockEnd;
|
||||
do {
|
||||
clockEnd = UTIL_getTime();
|
||||
} while (UTIL_getSpanTimeNano(clockStart, clockEnd) == 0);
|
||||
}
|
||||
|
||||
int UTIL_support_MT_measurements(void)
|
||||
{
|
||||
# if defined(TIME_MT_MEASUREMENTS_NOT_SUPPORTED)
|
||||
return 0;
|
||||
# else
|
||||
return 1;
|
||||
# endif
|
||||
}
|
||||
/* The Rust port mirrors this exact ABI: UTIL_time_t is returned by value and
|
||||
* must remain a plain 64-bit nanosecond counter. */
|
||||
typedef char UTIL_staticAssert_ptimeIs64Bit[(sizeof(PTime) == 8) ? 1 : -1];
|
||||
typedef char UTIL_staticAssert_timeIsPlainCounter[
|
||||
(sizeof(UTIL_time_t) == sizeof(PTime)) ? 1 : -1];
|
||||
|
||||
Reference in New Issue
Block a user