84 lines
2.8 KiB
Plaintext
84 lines
2.8 KiB
Plaintext
// RDTSC ticks, converted with the TSC frequency from CPUID leaf 0x15, or with the base frequency
|
|
// from leaf 0x16 where 0x15 is missing or incomplete.
|
|
|
|
TIMING_SUPPORTED :: true;
|
|
|
|
timing_read_ticks :: () -> u64 #expand {
|
|
return X64.rdtsc();
|
|
}
|
|
|
|
timing_read_tick_frequency_hz :: () -> u64 {
|
|
hz := timing_cpuid_tsc_frequency_hz();
|
|
if hz return hz;
|
|
|
|
timing_check_cpu_id_frequency_information();
|
|
return cast(u64) CPU_BASE_FREQUENCY * 1_000_000;
|
|
}
|
|
|
|
// All units in MHz
|
|
CPU_BASE_FREQUENCY: u32; // Useful for timing with RDTSC.
|
|
CPU_MAX_FREQUENCY: u32; // This is not reliable, will return different values depending on which CPU the thread is called on.
|
|
// e.g. on 12900K, we see 5200 on P cores and 3900 on some E-cores.
|
|
// In general this isn't useful since all measurements are relative to base frequency.
|
|
CPU_BUS_FREQUENCY: u32; // reference bus frequency
|
|
CPU_FREQ_RESERVED: u32; // not sure what this is for; most likely unused.
|
|
freq_calc_start_time : float64;
|
|
freq_calc_start_tick : u64;
|
|
|
|
// A leaf above the CPU's maximum returns the highest basic leaf's data rather than zeros, so every
|
|
// leaf read here is checked against this first. That also makes a vendor check unnecessary: a CPU
|
|
// without a leaf reports a lower maximum.
|
|
timing_cpuid_max_basic_leaf :: () -> u32 {
|
|
max_basic_leaf: u32 = ---;
|
|
#asm {
|
|
xor.d a: gpr === a, a;
|
|
xor.d c: gpr === c, c;
|
|
cpuid a, b:, c, d:;
|
|
mov.d max_basic_leaf, a;
|
|
}
|
|
return max_basic_leaf;
|
|
}
|
|
|
|
// Leaf 0x15: TSC Hz = crystal Hz (ECX) * EBX / EAX. 0 when the leaf is missing or leaves any of
|
|
// the three out; many CPUs (Skylake client among them) give the ratio but not the crystal.
|
|
timing_cpuid_tsc_frequency_hz :: () -> u64 {
|
|
if timing_cpuid_max_basic_leaf() < 0x15 return 0;
|
|
|
|
denominator: u32 = ---;
|
|
numerator: u32 = ---;
|
|
crystal_hz: u32 = ---;
|
|
#asm {
|
|
mov.d a: gpr === a, 0x15;
|
|
xor.d c: gpr === c, c;
|
|
cpuid a, b:, c, d:;
|
|
mov.d denominator, a;
|
|
mov.d numerator, b;
|
|
mov.d crystal_hz, c;
|
|
}
|
|
if !denominator || !numerator || !crystal_hz return 0;
|
|
|
|
return cast(u64) crystal_hz * numerator / denominator;
|
|
}
|
|
|
|
// Leaf 0x16 is Intel's (Skylake on). Below it the values stay 0.
|
|
timing_check_cpu_id_frequency_information :: () {
|
|
if timing_cpuid_max_basic_leaf() < 0x16 return;
|
|
|
|
base_frequency := *CPU_BASE_FREQUENCY;
|
|
max_freq := *CPU_MAX_FREQUENCY;
|
|
bus_freq := *CPU_BUS_FREQUENCY;
|
|
idk_freq := *CPU_FREQ_RESERVED;
|
|
|
|
#asm {
|
|
mov.d a: gpr === a, 0x16 ; // mov eax, 22 ; 0x16 is the frequency info
|
|
xor.d c: gpr === c, c ; // xor ecx, ecx
|
|
cpuid a, b:, c, d: ; // cpuid
|
|
mov.d [base_frequency], a; // eax
|
|
mov.d [max_freq], b; // ebx
|
|
mov.d [bus_freq], c; // ecx
|
|
mov.d [idk_freq], d; // edx
|
|
}
|
|
}
|
|
|
|
X64 :: #import "Machine_X64";
|