#[cfg(not(target_arch = "wasm32"))]
use core::arch::asm;
#[inline(always)]
#[allow(unsafe_code)]
pub fn asm_fence() {
#[cfg(not(target_arch = "wasm32"))]
{
unsafe {
asm!("", options(nostack, preserves_flags));
}
}
#[cfg(target_arch = "wasm32")]
{
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
#[inline(always)]
pub fn compiler_fence() {
asm_fence();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
#[allow(unsafe_code)]
pub fn tsc_start() -> u64 {
let lo: u32;
let hi: u32;
unsafe {
asm!(
"lfence",
"rdtsc",
out("eax") lo,
out("edx") hi,
options(nostack, preserves_flags),
);
}
((hi as u64) << 32) | (lo as u64)
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
#[allow(unsafe_code)]
pub fn tsc_end() -> u64 {
let lo: u32;
let hi: u32;
unsafe {
asm!(
"rdtscp",
"lfence",
out("eax") lo,
out("edx") hi,
out("ecx") _, options(nostack, preserves_flags),
);
}
((hi as u64) << 32) | (lo as u64)
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
#[allow(unsafe_code)]
pub fn tsc_start() -> u64 {
let val: u64;
unsafe {
asm!(
"isb",
"mrs {val}, cntvct_el0",
val = out(reg) val,
options(nostack, preserves_flags),
);
}
val
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
#[allow(unsafe_code)]
pub fn tsc_end() -> u64 {
let val: u64;
unsafe {
asm!(
"isb",
"mrs {val}, cntvct_el0",
"isb",
val = out(reg) val,
options(nostack, preserves_flags),
);
}
val
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub fn tsc_start() -> u64 {
0
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub fn tsc_end() -> u64 {
0
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub fn calibrate_tsc_frequency() -> f64 {
let mut prev_ratio = 0.0_f64;
for power in 0..9 {
let sleep_ms = 1u64 << power; let sleep_dur = std::time::Duration::from_millis(sleep_ms);
let tsc_before = tsc_start();
let instant_before = std::time::Instant::now();
std::thread::sleep(sleep_dur);
let tsc_after = tsc_end();
let instant_after = std::time::Instant::now();
let tsc_delta = tsc_after.wrapping_sub(tsc_before) as f64;
let ns_delta = instant_after.duration_since(instant_before).as_nanos() as f64;
if ns_delta < 1.0 {
continue;
}
let ratio = tsc_delta / ns_delta;
if prev_ratio > 0.0 {
let relative_diff = ((ratio - prev_ratio) / prev_ratio).abs();
if relative_diff < 0.001 {
return ratio;
}
}
prev_ratio = ratio;
}
prev_ratio
}
#[cfg(target_arch = "x86_64")]
#[allow(unsafe_code)]
pub fn tsc_is_invariant() -> bool {
let max_extended: u32;
unsafe {
asm!(
"push rbx",
"mov eax, 0x80000000",
"cpuid",
"pop rbx",
out("eax") max_extended,
out("ecx") _,
out("edx") _,
options(preserves_flags),
);
}
if max_extended < 0x80000007 {
return false;
}
let edx: u32;
unsafe {
asm!(
"push rbx",
"mov eax, 0x80000007",
"cpuid",
"pop rbx",
out("eax") _,
out("ecx") _,
out("edx") edx,
options(preserves_flags),
);
}
(edx & (1 << 8)) != 0
}
#[cfg(target_arch = "aarch64")]
pub fn tsc_is_invariant() -> bool {
true
}
#[inline(always)]
pub fn ticks_to_ns(ticks: u64, ticks_per_ns: f64) -> u64 {
if ticks_per_ns > 0.0 {
(ticks as f64 / ticks_per_ns) as u64
} else {
0
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub struct TscTimer {
ticks_per_ns: f64,
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl TscTimer {
pub fn new() -> Option<Self> {
if !tsc_is_invariant() {
return None;
}
let freq = calibrate_tsc_frequency();
if freq > 0.001 && freq < 10.0 {
Some(Self { ticks_per_ns: freq })
} else {
None
}
}
#[inline(always)]
#[allow(dead_code)] pub fn measure<R>(&self, f: impl FnOnce() -> R) -> (u64, R) {
compiler_fence();
let start = tsc_start();
let result = f();
let end = tsc_end();
compiler_fence();
let ticks = end.wrapping_sub(start);
(ticks_to_ns(ticks, self.ticks_per_ns), result)
}
pub fn ticks_per_ns(&self) -> f64 {
self.ticks_per_ns
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub struct TscTimer;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
impl TscTimer {
pub fn new() -> Option<Self> {
None
}
pub fn ticks_per_ns(&self) -> f64 {
0.0
}
}
#[inline(never)]
pub fn stack_jitter_call(
func: &mut crate::bench::BenchFn,
bencher: &mut crate::bench::Bencher,
depth: usize,
) {
let _pad: [u8; 64] = std::hint::black_box([0u8; 64]);
if depth == 0 {
func.call(bencher);
} else {
stack_jitter_call(func, bencher, depth - 1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn asm_fence_does_not_panic() {
asm_fence();
compiler_fence();
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn tsc_reads_increase() {
let a = tsc_start();
let mut x = 0u64;
for i in 0..1000 {
x = x.wrapping_add(std::hint::black_box(i));
}
std::hint::black_box(x);
let b = tsc_end();
assert!(b > a, "TSC should advance: {a} -> {b}");
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn tsc_calibration_is_sane() {
let freq = calibrate_tsc_frequency();
assert!(
freq > 0.001 && freq < 10.0,
"TSC frequency {freq} ticks/ns is outside sane range"
);
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn tsc_is_invariant_check() {
let invariant = tsc_is_invariant();
eprintln!("TSC invariant: {invariant}");
}
#[test]
fn stack_jitter_call_works() {
use std::sync::atomic::{AtomicBool, Ordering};
static CALLED: AtomicBool = AtomicBool::new(false);
let mut func = crate::bench::BenchFn::new(|b: &mut crate::bench::Bencher| {
CALLED.store(true, Ordering::Relaxed);
b.iter(|| std::hint::black_box(42u64));
});
let mut bencher = crate::bench::Bencher::new(10);
CALLED.store(false, Ordering::Relaxed);
stack_jitter_call(&mut func, &mut bencher, 0);
assert!(CALLED.load(Ordering::Relaxed), "depth=0 should call func");
CALLED.store(false, Ordering::Relaxed);
stack_jitter_call(&mut func, &mut bencher, 10);
assert!(CALLED.load(Ordering::Relaxed), "depth=10 should call func");
CALLED.store(false, Ordering::Relaxed);
stack_jitter_call(&mut func, &mut bencher, 50);
assert!(CALLED.load(Ordering::Relaxed), "depth=50 should call func");
}
#[test]
fn stack_jitter_different_depths_produce_results() {
for depth in [0, 5, 20, 50] {
let mut func = crate::bench::BenchFn::new(|b: &mut crate::bench::Bencher| {
b.iter(|| std::hint::black_box(42u64));
});
let mut bencher = crate::bench::Bencher::new(100);
stack_jitter_call(&mut func, &mut bencher, depth);
assert!(
bencher.elapsed_ns > 0,
"depth={depth} should produce positive elapsed_ns"
);
}
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn ticks_to_ns_basic() {
let ns = ticks_to_ns(3000, 3.0);
assert_eq!(ns, 1000);
}
}