1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#[cfg(target_arch = "x86")]
use std::arch::x86;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64 as x86;
use std::time::{Duration, Instant};
use crate::time::{fence, TscUnavailable};
#[inline(always)]
pub(crate) fn start_timestamp() -> u64 {
// Serialize previous operations before `rdtsc` to ensure they are not
// inside the timed section.
util::lfence();
let tsc = util::rdtsc();
// Serialize `rdtsc` before any measured code.
util::lfence();
tsc
}
#[inline(always)]
pub(crate) fn end_timestamp() -> u64 {
// `rdtscp` is serialized after all previous operations.
let tsc = util::rdtscp();
// Serialize `rdtscp` before any subsequent code.
util::lfence();
tsc
}
pub(crate) fn frequency() -> Result<u64, TscUnavailable> {
let mut nominal = None;
// If we don't have CPUID, avoid it and assume invariant TSC.
if util::has_cpuid() {
if !util::tsc_is_available() {
return Err(TscUnavailable::MissingInstructions);
}
if !util::tsc_is_invariant() {
return Err(TscUnavailable::VariableFrequency);
}
nominal = nominal_frequency();
}
let measured = measure::measure_frequency();
// Use the nominal frequency if within 0.1% of the measured frequency.
//
// The nominal frequency is used for getting an exact value if the measured
// frequency is slightly off. It is not blindly trusted because it may not
// match the TSC frequency.
if let Some(nominal) = nominal {
if measured * 0.999 < nominal && nominal < measured * 1.001 {
return Ok(nominal.round() as u64);
}
}
Ok(measured.round() as u64)
}
/// Parses the CPU frequency in the brand name, e.g. "2.50GHz".
fn nominal_frequency() -> Option<f64> {
let name = util::cpu_name()?;
let name = {
let len = name.iter().position(|&ch| ch == 0).unwrap_or(name.len());
std::str::from_utf8(&name[..len]).ok()?
};
#[rustfmt::skip]
let frequencies = [
("MHz", 1e6),
("GHz", 1e9),
("THz", 1e12),
];
for (unit, scale) in frequencies {
let Some(unit_start) = name.find(unit) else {
continue;
};
let pre_unit = &name[..unit_start];
let num = match pre_unit.rsplit_once(' ') {
Some((_, num)) => num,
None => pre_unit,
};
if let Ok(num) = num.parse::<f64>() {
return Some(num * scale);
};
}
None
}
mod util {
use super::*;
#[inline(always)]
pub fn rdtsc() -> u64 {
fence::compiler_fence();
// SAFETY: Reading the TSC is memory safe.
let tsc = unsafe { x86::_rdtsc() };
fence::compiler_fence();
tsc
}
#[inline(always)]
pub fn rdtscp() -> u64 {
fence::compiler_fence();
// SAFETY: Reading the TSC is memory safe.
let tsc = unsafe { x86::__rdtscp(&mut 0) };
fence::compiler_fence();
tsc
}
#[inline(always)]
pub fn lfence() {
// SAFETY: A load fence is memory safe.
unsafe { x86::_mm_lfence() }
}
/// Does the host support the `cpuid` instruction?
///
/// This is a stable polyfill of [`x86::has_cpuid`].
#[inline]
pub fn has_cpuid() -> bool {
// https://doc.rust-lang.org/1.72.0/src/core/stdarch/crates/core_arch/src/x86/cpuid.rs.html#101
#[cfg(target_env = "sgx")]
{
false
}
#[cfg(all(not(target_env = "sgx"), target_arch = "x86_64"))]
{
true
}
#[cfg(all(not(target_env = "sgx"), target_arch = "x86"))]
{
// Optimization for i586 and i686 Rust targets which SSE enabled
// and support cpuid:
#[cfg(target_feature = "sse")]
{
true
}
// If SSE is not enabled, detect whether cpuid is available:
#[cfg(not(target_feature = "sse"))]
unsafe {
// On `x86` the `cpuid` instruction is not always available.
// This follows the approach indicated in:
// http://wiki.osdev.org/CPUID#Checking_CPUID_availability
// https://software.intel.com/en-us/articles/using-cpuid-to-detect-the-presence-of-sse-41-and-sse-42-instruction-sets/
// which detects whether `cpuid` is available by checking whether
// the 21st bit of the EFLAGS register is modifiable or not.
// If it is, then `cpuid` is available.
let result: u32;
std::arch::asm!(
// Read eflags and save a copy of it
"pushfd",
"pop {result}",
"mov {result}, {saved_flags}",
// Flip 21st bit of the flags
"xor $0x200000, {result}",
// Load the modified flags and read them back.
// Bit 21 can only be modified if cpuid is available.
"push {result}",
"popfd",
"pushfd",
"pop {result}",
// Use xor to find out whether bit 21 has changed
"xor {saved_flags}, {result}",
result = out(reg) result,
saved_flags = out(reg) _,
options(nomem, att_syntax),
);
// There is a race between popfd (A) and pushfd (B)
// where other bits beyond 21st may have been modified due to
// interrupts, a debugger stepping through the asm, etc.
//
// Therefore, explicitly check whether the 21st bit
// was modified or not.
//
// If the result is zero, the cpuid bit was not modified.
// If the result is `0x200000` (non-zero), then the cpuid
// was correctly modified and the CPU supports the cpuid
// instruction:
(result & 0x200000) != 0
}
}
}
#[inline]
fn cpuid(leaf: u32) -> x86::CpuidResult {
// SAFETY: `cpuid` is never unsafe to call.
unsafe { x86::__cpuid(leaf) }
}
/// Invokes CPUID and converts its output registers to an ordered array.
#[inline]
fn cpuid_array(leaf: u32) -> [u32; 4] {
let cpuid = cpuid(leaf);
[cpuid.eax, cpuid.ebx, cpuid.ecx, cpuid.edx]
}
/// Returns `true` if the given CPUID leaf is available.
#[inline]
fn cpuid_has_leaf(leaf: u32) -> bool {
cpuid(0x8000_0000).eax >= leaf
}
/// Returns `true` if CPUID indicates that the `rdtsc` and `rdtscp`
/// instructions are available.
#[inline]
pub fn tsc_is_available() -> bool {
let bits = cpuid(0x8000_0001).edx;
let rdtsc = 1 << 4;
let rdtscp = 1 << 27;
bits & (rdtsc | rdtscp) != 0
}
/// Returns `true` if CPUID indicates that the timestamp counter has a
/// constant frequency.
#[inline]
pub fn tsc_is_invariant() -> bool {
let leaf = 0x8000_0007;
if !cpuid_has_leaf(leaf) {
return false;
}
cpuid(leaf).edx & (1 << 8) != 0
}
/// Returns the processor model name as a null-terminated ASCII string.
pub fn cpu_name() -> Option<[u8; 48]> {
if !cpuid_has_leaf(0x8000_0004) {
return None;
}
#[rustfmt::skip]
let result = [
cpuid_array(0x8000_0002),
cpuid_array(0x8000_0003),
cpuid_array(0x8000_0004),
];
// SAFETY: Converting from `u32` to bytes.
Some(unsafe { std::mem::transmute(result) })
}
}
mod measure {
use super::*;
/// Returns the TSC frequency by measuring it.
pub fn measure_frequency() -> f64 {
const TRIES: usize = 8;
// Start with delay of 1ms up to 256ms (2^TRIES).
let mut delay_ms = 1;
let mut prev_measure = f64::NEG_INFINITY;
let mut measures = [0.0; TRIES];
for slot in &mut measures {
let measure = measure_frequency_once(Duration::from_millis(delay_ms));
// This measurement is sufficiently accurate if within 0.1% of the
// previous.
if measure * 0.999 < prev_measure && prev_measure < measure * 1.001 {
return measure;
}
*slot = measure;
prev_measure = measure;
delay_ms *= 2;
}
// If no frequencies were within 0.1% of each other, find the frequency
// with the smallest delta.
let mut min_delta = f64::INFINITY;
let mut result_index = 0;
for i in 0..TRIES {
for j in (i + 1)..TRIES {
let delta = (measures[i] - measures[j]).abs();
if delta < min_delta {
min_delta = delta;
result_index = i;
}
}
}
measures[result_index]
}
fn measure_frequency_once(delay: Duration) -> f64 {
let (start_tsc, start_instant) = tsc_instant_pair();
std::thread::sleep(delay);
let (end_tsc, end_instant) = tsc_instant_pair();
let elapsed_tsc = end_tsc.saturating_sub(start_tsc);
let elapsed_duration = end_instant.duration_since(start_instant);
(elapsed_tsc as f64 / elapsed_duration.as_nanos() as f64) * 1e9
}
/// Returns a timestamp/instant pair that has a small latency between
/// getting the two values.
fn tsc_instant_pair() -> (u64, Instant) {
let mut best_latency = Duration::MAX;
let mut best_pair = (0, Instant::now());
// Make up to 100 attempts to get a low latency pair.
for _ in 0..100 {
let instant = Instant::now();
let tsc = util::rdtsc();
let latency = instant.elapsed();
let pair = (tsc, instant);
if latency.is_zero() {
return pair;
}
if latency < best_latency {
best_latency = latency;
best_pair = pair;
}
}
best_pair
}
}