1use std::time::Duration;
23use shadow_shim_helper_rs::{emulated_time::EmulatedTime, simulation_time::SimulationTime};
45/// 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}
1617impl 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)`.
23pub fn new(
24 simulated_frequency: u64,
25 native_frequency: u64,
26 threshold: Option<SimulationTime>,
27 precision: Option<SimulationTime>,
28 ) -> Self {
29if let Some(precision) = precision {
30assert!(precision > SimulationTime::ZERO)
31 }
3233Self {
34 simulated_frequency,
35 native_frequency,
36 threshold,
37 precision,
38 now: EmulatedTime::MIN,
39 time_cpu_available: EmulatedTime::MIN,
40 }
41 }
4243/// Configure the current time.
44pub fn update_time(&mut self, now: EmulatedTime) {
45self.now = now;
46 }
4748/// Account for `native_delay` spent natively executing code.
49pub 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.
52let cycles = native_delay
53 .as_nanos()
54 .checked_mul(self.native_frequency as u128)
55 .unwrap();
56let 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.
59let mut adjusted_delay =
60 SimulationTime::from_nanos(simulated_delay_nanos.try_into().unwrap());
6162// round the adjusted delay to the nearest precision if needed
63if let Some(precision) = self.precision {
64let remainder = adjusted_delay % precision;
6566// first round down (this is also the first step to rounding up)
67adjusted_delay -= remainder;
6869// now check if we should round up
70let half_precision = precision / 2;
71if remainder >= half_precision {
72// we should have rounded up, so adjust up by one interval
73adjusted_delay += precision;
74 }
75 }
7677self.time_cpu_available += adjusted_delay;
78 }
7980/// Calculate the simulated delay until this CPU is ready to run again.
81pub fn delay(&self) -> SimulationTime {
82let Some(threshold) = self.threshold else {
83return SimulationTime::ZERO;
84 };
85let Some(built_up_delay) = self.time_cpu_available.checked_duration_since(&self.now) else {
86return SimulationTime::ZERO;
87 };
88if built_up_delay > threshold {
89 built_up_delay
90 } else {
91 SimulationTime::ZERO
92 }
93 }
94}
9596#[cfg(test)]
97mod tests {
98use super::*;
99100const MHZ: u64 = 1_000_000;
101102#[test]
103fn no_threshold_never_delays() {
104let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, None, None);
105assert_eq!(cpu.delay(), SimulationTime::ZERO);
106107 cpu.add_delay(Duration::from_secs(1));
108assert_eq!(cpu.delay(), SimulationTime::ZERO);
109 }
110111#[test]
112fn basic_delay() {
113let mut cpu = Cpu::new(
1141000 * MHZ,
1151000 * MHZ,
116Some(SimulationTime::NANOSECOND),
117None,
118 );
119assert_eq!(cpu.delay(), SimulationTime::ZERO);
120121// Set our start time.
122cpu.update_time(EmulatedTime::UNIX_EPOCH);
123124// Simulate having spent 1 native second.
125cpu.add_delay(Duration::from_secs(1));
126127// With this configuration, simulated delay should be 1:1 with native time spent.
128assert_eq!(cpu.delay(), SimulationTime::SECOND);
129130// Moving time forward should reduce the delay by that amount.
131cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_millis(100));
132assert_eq!(cpu.delay(), SimulationTime::from_millis(900));
133134// Moving time forward to exactly the end of the delay should result in zero delay
135cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(1));
136assert_eq!(cpu.delay(), SimulationTime::ZERO);
137138// Moving time past the end of the delay should still result in zero delay
139cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(2));
140assert_eq!(cpu.delay(), SimulationTime::ZERO);
141 }
142143#[test]
144fn no_overflow() {
145// Use 1 THz processor
146let mut cpu = Cpu::new(
1471_000_000 * MHZ,
1481_000_000 * MHZ,
149Some(SimulationTime::NANOSECOND),
150None,
151 );
152153// Simulate having spent a native hour
154cpu.add_delay(Duration::from_secs(3600));
155156assert_eq!(cpu.delay(), SimulationTime::from_secs(3600));
157 }
158159#[test]
160fn faster_native() {
161let mut cpu = Cpu::new(
1621000 * MHZ,
1631100 * MHZ,
164Some(SimulationTime::NANOSECOND),
165None,
166 );
167assert_eq!(cpu.delay(), SimulationTime::ZERO);
168169// Since the simulated CPU is slower, it takes longer to execute.
170cpu.add_delay(Duration::from_millis(1000));
171assert_eq!(cpu.delay(), SimulationTime::from_millis(1100));
172 }
173174#[test]
175fn faster_simulated() {
176let mut cpu = Cpu::new(
1771100 * MHZ,
1781000 * MHZ,
179Some(SimulationTime::NANOSECOND),
180None,
181 );
182assert_eq!(cpu.delay(), SimulationTime::ZERO);
183184// Since the simulated CPU is faster, it takes less time to execute.
185cpu.add_delay(Duration::from_millis(1100));
186assert_eq!(cpu.delay(), SimulationTime::from_millis(1000));
187 }
188189#[test]
190fn thresholded() {
191let threshold = SimulationTime::from_millis(100);
192let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, Some(threshold), None);
193assert_eq!(cpu.delay(), SimulationTime::ZERO);
194195// Simulate having spent 1 ms.
196cpu.add_delay(Duration::from_millis(1));
197198// Since this is below the threshold, delay should still be 0.
199assert_eq!(cpu.delay(), SimulationTime::ZERO);
200201// Spend another 100 ms.
202cpu.add_delay(Duration::from_millis(100));
203204// Now that we're past the threshold, should see the full 101 ms we've spent.
205assert_eq!(cpu.delay(), SimulationTime::from_millis(101));
206 }
207208#[test]
209fn round_lt_half_precision() {
210let precision = SimulationTime::from_millis(100);
211let mut cpu = Cpu::new(
2121000 * MHZ,
2131000 * MHZ,
214Some(SimulationTime::NANOSECOND),
215Some(precision),
216 );
217 cpu.add_delay(Duration::from_millis(149));
218assert_eq!(cpu.delay(), SimulationTime::from_millis(100));
219 }
220221#[test]
222fn round_half_precision() {
223let precision = SimulationTime::from_millis(100);
224let mut cpu = Cpu::new(
2251000 * MHZ,
2261000 * MHZ,
227Some(SimulationTime::NANOSECOND),
228Some(precision),
229 );
230 cpu.add_delay(Duration::from_millis(150));
231assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
232 }
233234#[test]
235fn round_gt_half_precision() {
236let precision = SimulationTime::from_millis(100);
237let mut cpu = Cpu::new(
2381000 * MHZ,
2391000 * MHZ,
240Some(SimulationTime::NANOSECOND),
241Some(precision),
242 );
243 cpu.add_delay(Duration::from_millis(151));
244assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
245 }
246}