shadow_rs/host/
cpu.rs

1use std::time::Duration;
2
3use shadow_shim_helper_rs::{emulated_time::EmulatedTime, simulation_time::SimulationTime};
4
5/// Accounts for time executing code on the native CPU, calculating a
6/// corresponding delay for when the simulated CPU should be allowed to run
7/// next.
8pub struct Cpu {
9    simulated_frequency: u64,
10    native_frequency: u64,
11    threshold: Option<SimulationTime>,
12    precision: Option<SimulationTime>,
13    now: EmulatedTime,
14    time_cpu_available: EmulatedTime,
15}
16
17impl Cpu {
18    /// `threshold`: if None, never report a delay. Otherwise only report a
19    /// delay after it is more than this threshold.
20    ///
21    /// `precision`: if provided, round individual native delays to this
22    /// granularity (rounding up at midpoint). Panics if this is `Some(0)`.
23    pub fn new(
24        simulated_frequency: u64,
25        native_frequency: u64,
26        threshold: Option<SimulationTime>,
27        precision: Option<SimulationTime>,
28    ) -> Self {
29        if let Some(precision) = precision {
30            assert!(precision > SimulationTime::ZERO)
31        }
32
33        Self {
34            simulated_frequency,
35            native_frequency,
36            threshold,
37            precision,
38            now: EmulatedTime::MIN,
39            time_cpu_available: EmulatedTime::MIN,
40        }
41    }
42
43    /// Configure the current time.
44    pub fn update_time(&mut self, now: EmulatedTime) {
45        self.now = now;
46    }
47
48    /// Account for `native_delay` spent natively executing code.
49    pub fn add_delay(&mut self, native_delay: Duration) {
50        // first normalize the physical CPU to the virtual CPU. We use u128 here
51        // to guarantee no overflow when multiplying two u64's.
52        let cycles = native_delay
53            .as_nanos()
54            .checked_mul(self.native_frequency as u128)
55            .unwrap();
56        let simulated_delay_nanos = cycles / (self.simulated_frequency as u128);
57        // Theoretically possible to overflow (and then panic) here, but only
58        // for a delay of > ~500 years.
59        let mut adjusted_delay =
60            SimulationTime::from_nanos(simulated_delay_nanos.try_into().unwrap());
61
62        // round the adjusted delay to the nearest precision if needed
63        if let Some(precision) = self.precision {
64            let remainder = adjusted_delay % precision;
65
66            // first round down (this is also the first step to rounding up)
67            adjusted_delay -= remainder;
68
69            // now check if we should round up
70            let half_precision = precision / 2;
71            if remainder >= half_precision {
72                // we should have rounded up, so adjust up by one interval
73                adjusted_delay += precision;
74            }
75        }
76
77        self.time_cpu_available += adjusted_delay;
78    }
79
80    /// Calculate the simulated delay until this CPU is ready to run again.
81    pub fn delay(&self) -> SimulationTime {
82        let Some(threshold) = self.threshold else {
83            return SimulationTime::ZERO;
84        };
85        let Some(built_up_delay) = self.time_cpu_available.checked_duration_since(&self.now) else {
86            return SimulationTime::ZERO;
87        };
88        if built_up_delay > threshold {
89            built_up_delay
90        } else {
91            SimulationTime::ZERO
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    const MHZ: u64 = 1_000_000;
101
102    #[test]
103    fn no_threshold_never_delays() {
104        let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, None, None);
105        assert_eq!(cpu.delay(), SimulationTime::ZERO);
106
107        cpu.add_delay(Duration::from_secs(1));
108        assert_eq!(cpu.delay(), SimulationTime::ZERO);
109    }
110
111    #[test]
112    fn basic_delay() {
113        let mut cpu = Cpu::new(
114            1000 * MHZ,
115            1000 * MHZ,
116            Some(SimulationTime::NANOSECOND),
117            None,
118        );
119        assert_eq!(cpu.delay(), SimulationTime::ZERO);
120
121        // Set our start time.
122        cpu.update_time(EmulatedTime::UNIX_EPOCH);
123
124        // Simulate having spent 1 native second.
125        cpu.add_delay(Duration::from_secs(1));
126
127        // With this configuration, simulated delay should be 1:1 with native time spent.
128        assert_eq!(cpu.delay(), SimulationTime::SECOND);
129
130        // Moving time forward should reduce the delay by that amount.
131        cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_millis(100));
132        assert_eq!(cpu.delay(), SimulationTime::from_millis(900));
133
134        // Moving time forward to exactly the end of the delay should result in zero delay
135        cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(1));
136        assert_eq!(cpu.delay(), SimulationTime::ZERO);
137
138        // Moving time past the end of the delay should still result in zero delay
139        cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(2));
140        assert_eq!(cpu.delay(), SimulationTime::ZERO);
141    }
142
143    #[test]
144    fn no_overflow() {
145        // Use 1 THz processor
146        let mut cpu = Cpu::new(
147            1_000_000 * MHZ,
148            1_000_000 * MHZ,
149            Some(SimulationTime::NANOSECOND),
150            None,
151        );
152
153        // Simulate having spent a native hour
154        cpu.add_delay(Duration::from_secs(3600));
155
156        assert_eq!(cpu.delay(), SimulationTime::from_secs(3600));
157    }
158
159    #[test]
160    fn faster_native() {
161        let mut cpu = Cpu::new(
162            1000 * MHZ,
163            1100 * MHZ,
164            Some(SimulationTime::NANOSECOND),
165            None,
166        );
167        assert_eq!(cpu.delay(), SimulationTime::ZERO);
168
169        // Since the simulated CPU is slower, it takes longer to execute.
170        cpu.add_delay(Duration::from_millis(1000));
171        assert_eq!(cpu.delay(), SimulationTime::from_millis(1100));
172    }
173
174    #[test]
175    fn faster_simulated() {
176        let mut cpu = Cpu::new(
177            1100 * MHZ,
178            1000 * MHZ,
179            Some(SimulationTime::NANOSECOND),
180            None,
181        );
182        assert_eq!(cpu.delay(), SimulationTime::ZERO);
183
184        // Since the simulated CPU is faster, it takes less time to execute.
185        cpu.add_delay(Duration::from_millis(1100));
186        assert_eq!(cpu.delay(), SimulationTime::from_millis(1000));
187    }
188
189    #[test]
190    fn thresholded() {
191        let threshold = SimulationTime::from_millis(100);
192        let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, Some(threshold), None);
193        assert_eq!(cpu.delay(), SimulationTime::ZERO);
194
195        // Simulate having spent 1 ms.
196        cpu.add_delay(Duration::from_millis(1));
197
198        // Since this is below the threshold, delay should still be 0.
199        assert_eq!(cpu.delay(), SimulationTime::ZERO);
200
201        // Spend another 100 ms.
202        cpu.add_delay(Duration::from_millis(100));
203
204        // Now that we're past the threshold, should see the full 101 ms we've spent.
205        assert_eq!(cpu.delay(), SimulationTime::from_millis(101));
206    }
207
208    #[test]
209    fn round_lt_half_precision() {
210        let precision = SimulationTime::from_millis(100);
211        let mut cpu = Cpu::new(
212            1000 * MHZ,
213            1000 * MHZ,
214            Some(SimulationTime::NANOSECOND),
215            Some(precision),
216        );
217        cpu.add_delay(Duration::from_millis(149));
218        assert_eq!(cpu.delay(), SimulationTime::from_millis(100));
219    }
220
221    #[test]
222    fn round_half_precision() {
223        let precision = SimulationTime::from_millis(100);
224        let mut cpu = Cpu::new(
225            1000 * MHZ,
226            1000 * MHZ,
227            Some(SimulationTime::NANOSECOND),
228            Some(precision),
229        );
230        cpu.add_delay(Duration::from_millis(150));
231        assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
232    }
233
234    #[test]
235    fn round_gt_half_precision() {
236        let precision = SimulationTime::from_millis(100);
237        let mut cpu = Cpu::new(
238            1000 * MHZ,
239            1000 * MHZ,
240            Some(SimulationTime::NANOSECOND),
241            Some(precision),
242        );
243        cpu.add_delay(Duration::from_millis(151));
244        assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
245    }
246}