// 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";