swage_core/memory/
pfn_offset_resolver.rs

1use super::keyed_cache::KeyedCache;
2use std::cmp::min;
3
4use super::DRAMAddr;
5use super::{BytePointer, MemoryTupleTimer, pfn_offset::CachedPfnOffset};
6use crate::memory::mem_configuration::MemConfiguration;
7use crate::util::NamedProgress;
8use crate::util::ROW_SIZE;
9use indicatif::MultiProgress;
10use indicatif::ProgressBar;
11use indicatif::ProgressStyle;
12use itertools::Itertools;
13use log::{debug, info};
14
15/// Resolves the PFN-to-virtual-address offset in rows.
16///
17/// Used by allocators to determine DRAM row alignment for allocated memory.
18pub trait PfnOffsetResolver {
19    /// Determines the PFN offset by testing memory access timing.
20    ///
21    /// # Arguments
22    ///
23    /// * `mem_config` - DRAM configuration
24    /// * `conflict_threshold` - Timing threshold for bank conflicts (CPU cycles)
25    /// * `timer` - Memory timing measurement interface
26    /// * `progress` - Optional progress reporting
27    ///
28    /// # Returns
29    ///
30    /// The PFN offset in rows, or None if detection fails
31    fn pfn_offset(
32        &self,
33        mem_config: &MemConfiguration,
34        conflict_threshold: u64,
35        timer: &dyn MemoryTupleTimer,
36        progress: Option<&MultiProgress>,
37    ) -> Option<usize>;
38}
39
40impl<T> PfnOffsetResolver for T
41where
42    T: BytePointer + CachedPfnOffset,
43{
44    /// Find the PFN-VA offset in rows.
45    ///
46    /// This is brute force, simply trying all row offsets from 0...row_offsets (determined using mem_config)
47    /// There probably is a more sophisticated way to implement this, e.g., by examing the bank orders and
48    /// filtering for possible "bank periods" after each iteration, but this here should be fast enough for now.
49    /// WARNING: This function initializes pfn_offset with the provided mem_config. Calling #pfn_offset(...) with
50    ///          different arguments afterward WILL NOT reset the OnceCell, potentially causing unintended behavior.
51    fn pfn_offset(
52        &self,
53        mem_config: &MemConfiguration,
54        conflict_threshold: u64,
55        timer: &dyn MemoryTupleTimer,
56        progress: Option<&MultiProgress>,
57    ) -> Option<usize> {
58        // reuse cached value if possible
59        if let Some(offset) = self.get_cached((*mem_config, conflict_threshold)) {
60            return Some(offset);
61        }
62        // find PFN offset
63        let num_rows = self.len() / ROW_SIZE;
64        let max_rows = mem_config.bank_function_period() as usize / 2; // TODO: check if it is valid for all bank functions to divide by two here (I think it is)
65        let num_rows = min(num_rows, max_rows);
66        let offset = progress.map(|progress| {
67            progress.add(
68                ProgressBar::new(num_rows as u64).with_style(ProgressStyle::named_bar("Offset")),
69            )
70        });
71        let pairs = progress.map(|progress| {
72            progress.add(
73                ProgressBar::new((num_rows * (num_rows - 1) / 2) as u64)
74                    .with_style(ProgressStyle::named_bar("Pairs")),
75            )
76        });
77
78        // do a quick pre-check. Toggling the uppermost bit in the bank function should result in a fast timing.
79        if self.len() >= num_rows * ROW_SIZE {
80            let addr1 = self.ptr();
81            let addr2 = self.addr(num_rows * ROW_SIZE);
82            let time = unsafe { timer.time_subsequent_access_from_ram(addr1, addr2, 1000) };
83            if time > conflict_threshold {
84                info!("Pre-check failed. Block is not consecutive");
85                return self.put(None, (*mem_config, conflict_threshold));
86            }
87        } else {
88            debug!("Skip pre-check, block is too small");
89        }
90
91        'next_offset: for row_offset in 0..num_rows {
92            let addr_offset = (row_offset * ROW_SIZE) as isize;
93            debug!(
94                "Checking row offset {} (effective offset: 0x{:x})",
95                row_offset, addr_offset
96            );
97            // update progress
98            if let (Some(offset), Some(pairs)) = (&offset, &pairs) {
99                offset.inc(1);
100                pairs.reset();
101            }
102            // iterate over row pairs
103            for row_pair in (0..num_rows).combinations(2) {
104                if let Some(pairs) = &pairs {
105                    pairs.inc(1);
106                }
107                let offset1 = row_pair[0] * ROW_SIZE;
108                let offset2 = row_pair[1] * ROW_SIZE;
109                let addr1 = self.addr(offset1);
110                let addr2 = self.addr(offset2);
111                let dram1 = unsafe { DRAMAddr::from_virt_offset(addr1, addr_offset, mem_config) };
112                let dram2 = unsafe { DRAMAddr::from_virt_offset(addr2, addr_offset, mem_config) };
113                let same_bank = dram1.bank == dram2.bank;
114                let time = unsafe { timer.time_subsequent_access_from_ram(addr1, addr2, 1000) };
115                if (same_bank && time < conflict_threshold)
116                    || (!same_bank && time > conflict_threshold)
117                {
118                    debug!(
119                        "Expected {} banks for ({:?}, {:?}), but timed {} {} {}",
120                        if same_bank { "same" } else { "differing" },
121                        offset1 / ROW_SIZE,
122                        offset2 / ROW_SIZE,
123                        time,
124                        if same_bank { "<" } else { ">" },
125                        conflict_threshold
126                    );
127                    debug!(
128                        "rows: ({}, {}); addrs: (0x{:x}, 0x{:x}); DRAM: {:?}, {:?}",
129                        offset1 / ROW_SIZE,
130                        offset2 / ROW_SIZE,
131                        addr1 as u64,
132                        addr2 as u64,
133                        dram1,
134                        dram2
135                    );
136                    continue 'next_offset;
137                }
138            }
139            return self.put(Some(row_offset), (*mem_config, conflict_threshold));
140        }
141        self.put(None, (*mem_config, conflict_threshold))
142    }
143}