1 | use std::{fmt, num::NonZeroU64}; |
2 | |
3 | use crate::time::FineDuration; |
4 | |
5 | #[cfg (target_arch = "aarch64" )] |
6 | #[path = "aarch64.rs" ] |
7 | mod arch; |
8 | |
9 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
10 | #[path = "x86.rs" ] |
11 | mod arch; |
12 | |
13 | /// [CPU timestamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter). |
14 | #[derive (Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
15 | #[repr (transparent)] |
16 | pub(crate) struct TscTimestamp { |
17 | pub value: u64, |
18 | } |
19 | |
20 | impl TscTimestamp { |
21 | /// Gets the timestamp frequency. |
22 | /// |
23 | /// On AArch64, this simply reads `cntfrq_el0`. On x86, this measures the |
24 | /// TSC frequency. |
25 | #[inline ] |
26 | #[allow (unreachable_code)] |
27 | pub fn frequency() -> Result<NonZeroU64, TscUnavailable> { |
28 | // Miri does not support inline assembly. |
29 | #[cfg (miri)] |
30 | return Err(TscUnavailable::Unimplemented); |
31 | |
32 | #[cfg (any(target_arch = "aarch64" , target_arch = "x86" , target_arch = "x86_64" ))] |
33 | return NonZeroU64::new(arch::frequency()?).ok_or(TscUnavailable::ZeroFrequency); |
34 | |
35 | Err(TscUnavailable::Unimplemented) |
36 | } |
37 | |
38 | /// Reads the timestamp counter. |
39 | #[inline (always)] |
40 | pub fn start() -> Self { |
41 | #[allow (unused)] |
42 | let value = 0; |
43 | |
44 | #[cfg (target_arch = "aarch64" )] |
45 | let value = arch::timestamp(); |
46 | |
47 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
48 | let value = arch::start_timestamp(); |
49 | |
50 | Self { value } |
51 | } |
52 | |
53 | /// Reads the timestamp counter. |
54 | #[inline (always)] |
55 | pub fn end() -> Self { |
56 | #[allow (unused)] |
57 | let value = 0; |
58 | |
59 | #[cfg (target_arch = "aarch64" )] |
60 | let value = arch::timestamp(); |
61 | |
62 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
63 | let value = arch::end_timestamp(); |
64 | |
65 | Self { value } |
66 | } |
67 | |
68 | pub fn duration_since(self, earlier: Self, frequency: NonZeroU64) -> FineDuration { |
69 | const PICOS: u128 = 1_000_000_000_000; |
70 | |
71 | let Some(diff) = self.value.checked_sub(earlier.value) else { |
72 | return Default::default(); |
73 | }; |
74 | |
75 | FineDuration { picos: (diff as u128 * PICOS) / frequency.get() as u128 } |
76 | } |
77 | } |
78 | |
79 | /// Reason for why the timestamp counter cannot be used. |
80 | #[derive (Clone, Copy)] |
81 | pub(crate) enum TscUnavailable { |
82 | /// Not yet implemented for this platform. |
83 | Unimplemented, |
84 | |
85 | /// Got a frequency of 0. |
86 | ZeroFrequency, |
87 | |
88 | /// Missing the appropriate instructions. |
89 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
90 | MissingInstructions, |
91 | |
92 | /// The timestamp counter is not guaranteed to be constant. |
93 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
94 | VariableFrequency, |
95 | } |
96 | |
97 | impl fmt::Display for TscUnavailable { |
98 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
99 | let reason: &'static str = match self { |
100 | Self::Unimplemented => "unimplemented" , |
101 | Self::ZeroFrequency => "zero TSC frequency" , |
102 | |
103 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
104 | Self::MissingInstructions => "missing instructions" , |
105 | |
106 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
107 | Self::VariableFrequency => "variable TSC frequency" , |
108 | }; |
109 | |
110 | f.write_str(data:reason) |
111 | } |
112 | } |
113 | |