swage_core/memory/
timer.rs

1#[cfg(target_arch = "x86_64")]
2use {core::arch::x86_64, core::ptr};
3
4#[cfg(target_arch = "aarch64")]
5use anyhow::bail;
6use log::trace;
7
8/// Measures memory access timing between address pairs.
9///
10/// Used to detect DRAM bank conflicts and verify memory organization
11/// during allocator setup.
12pub trait MemoryTupleTimer {
13    /// time_subsequent_access_from_ram measures the access time when accessing both memory locations back to back from ram.
14    ///
15    /// # Safety
16    /// * `a` and `b` must be valid pointers to memory locations
17    unsafe fn time_subsequent_access_from_ram(
18        &self,
19        a: *const u8,
20        b: *const u8,
21        rounds: usize,
22    ) -> u64;
23}
24
25/// Errors that can occur when creating memory timers.
26#[derive(Debug, thiserror::Error)]
27pub enum TimerError {
28    /// Current CPU architecture is not supported for timing
29    #[error("Architecture not supported")]
30    ArchitectureNotSupported,
31}
32
33/// Creates a memory timer for the current architecture.
34///
35/// # Errors
36///
37/// Returns error if current architecture doesn't support timing
38pub fn construct_memory_tuple_timer() -> Result<Box<dyn MemoryTupleTimer>, TimerError> {
39    #[cfg(target_arch = "x86_64")]
40    return Ok(Box::new(DefaultMemoryTupleTimer {}));
41    #[cfg(target_arch = "aarch64")]
42    return Err(ArchitectureNotSupported);
43}
44
45/// x86_64 implementation using RDTSCP instruction.
46#[cfg(target_arch = "x86_64")]
47pub struct DefaultMemoryTupleTimer {}
48
49#[cfg(target_arch = "x86_64")]
50impl MemoryTupleTimer for DefaultMemoryTupleTimer {
51    ///time_subsequent_access_from_ram measures the access time when accessing both memory locations back to back from ram.
52    /// #Arguments
53    /// * `a` pointer to first memory location
54    /// * `b` pointer to second memory location
55    /// * `rounds` average the access time over this many accesses
56    unsafe fn time_subsequent_access_from_ram(
57        &self,
58        a: *const u8,
59        b: *const u8,
60        rounds: usize,
61    ) -> u64 {
62        unsafe {
63            let mut measurements = Vec::with_capacity(rounds);
64            //flush data from cache
65            x86_64::_mm_clflush(a);
66            x86_64::_mm_clflush(b);
67            let mut aux = 0;
68            let mut run_idx = 0;
69            let mut sum = 0;
70            while run_idx < rounds {
71                x86_64::_mm_mfence(); //ensures clean slate memory access time wise
72                let before = x86_64::__rdtscp(&mut aux); // read timestamp counter
73                x86_64::_mm_mfence(); //ensures clean slate memory access time wise
74                ptr::read_volatile(a);
75                ptr::read_volatile(b);
76                let after = x86_64::__rdtscp(&mut aux); //read second timestamp
77                x86_64::_mm_mfence(); //ensure rdtsc is done
78                let time = after - before;
79                measurements.push(time);
80                sum += time;
81                run_idx += 1;
82                //flush data from cache
83                x86_64::_mm_clflush(a);
84                x86_64::_mm_clflush(b);
85            }
86            trace!("Measurements: {:?}", measurements);
87            let _mean = sum / rounds as u64;
88            median(measurements)
89        }
90    }
91}
92
93fn median(mut list: Vec<u64>) -> u64 {
94    list.sort();
95    let mid = list.len() / 2;
96    (list[mid] + list[mid + 1]) / 2
97}