(wall, native)_clock: Rework NativeClock

This commit is contained in:
Morph 2023-04-22 23:08:28 -04:00
parent dd12dd4c67
commit 1492a65454
5 changed files with 94 additions and 259 deletions

View File

@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units. // GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
static constexpr s64 Multiplier = 100; static constexpr s64 Multiplier = 100;
// Convert Windows epoch to Unix epoch. // Convert Windows epoch to Unix epoch.
static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL; static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
FILETIME filetime; FILETIME filetime;
GetSystemTimePreciseAsFileTime(&filetime); GetSystemTimePreciseAsFileTime(&filetime);
return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) + return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
static_cast<s64>(filetime.dwLowDateTime)) - static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
WindowsEpochToUnixEpochNS;
} }
#endif #endif

View File

@ -2,88 +2,71 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/steady_clock.h" #include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/wall_clock.h" #include "common/wall_clock.h"
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#include "common/x64/native_clock.h" #include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
#endif #endif
namespace Common { namespace Common {
class StandardWallClock final : public WallClock { class StandardWallClock final : public WallClock {
public: public:
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_) explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
start_time{SteadyClock::Now()} {}
std::chrono::nanoseconds GetTimeNS() override { std::chrono::nanoseconds GetTimeNS() const override {
return SteadyClock::Now() - start_time; return SteadyClock::Now() - start_time;
} }
std::chrono::microseconds GetTimeUS() override { std::chrono::microseconds GetTimeUS() const override {
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS()); return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
} }
std::chrono::milliseconds GetTimeMS() override { std::chrono::milliseconds GetTimeMS() const override {
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS()); return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
} }
u64 GetClockCycles() override { u64 GetCNTPCT() const override {
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency); return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
return Common::Divide128On32(temp, NS_RATIO).first;
} }
u64 GetCPUCycles() override { u64 GetHostTicksNow() const override {
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency); return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
return Common::Divide128On32(temp, NS_RATIO).first;
} }
void Pause([[maybe_unused]] bool is_paused) override { u64 GetHostTicksElapsed() const override {
// Do nothing in this clock type. return static_cast<u64>(GetTimeNS().count());
}
bool IsNative() const override {
return false;
} }
private: private:
SteadyClock::time_point start_time; SteadyClock::time_point start_time;
}; };
std::unique_ptr<WallClock> CreateOptimalClock() {
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
u64 emulated_clock_frequency) {
const auto& caps = GetCPUCaps(); const auto& caps = GetCPUCaps();
u64 rtsc_frequency = 0;
if (caps.invariant_tsc) {
rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
}
// Fallback to StandardWallClock if the hardware TSC does not have the precision greater than: if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::CNTFRQ) {
// - A nanosecond return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
// - The emulated CPU frequency
// - The emulated clock counter frequency (CNTFRQ)
if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency ||
rtsc_frequency <= emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
emulated_clock_frequency);
} else { } else {
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency, // Fallback to StandardWallClock if the hardware TSC
rtsc_frequency); // - Is not invariant
// - Is not more precise than CNTFRQ
return std::make_unique<StandardWallClock>();
} }
}
#else #else
return std::make_unique<StandardWallClock>();
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, #endif
u64 emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
} }
#endif std::unique_ptr<WallClock> CreateStandardWallClock() {
return std::make_unique<StandardWallClock>();
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
u64 emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
} }
} // namespace Common } // namespace Common

View File

@ -5,6 +5,7 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <ratio>
#include "common/common_types.h" #include "common/common_types.h"
@ -12,50 +13,43 @@ namespace Common {
class WallClock { class WallClock {
public: public:
static constexpr u64 NS_RATIO = 1'000'000'000; static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
static constexpr u64 US_RATIO = 1'000'000;
static constexpr u64 MS_RATIO = 1'000;
virtual ~WallClock() = default; virtual ~WallClock() = default;
/// Returns current wall time in nanoseconds /// @returns The time in nanoseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; virtual std::chrono::nanoseconds GetTimeNS() const = 0;
/// Returns current wall time in microseconds /// @returns The time in microseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0; virtual std::chrono::microseconds GetTimeUS() const = 0;
/// Returns current wall time in milliseconds /// @returns The time in milliseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0; virtual std::chrono::milliseconds GetTimeMS() const = 0;
/// Returns current wall time in emulated clock cycles /// @returns The guest CNTPCT ticks since the construction of this clock.
[[nodiscard]] virtual u64 GetClockCycles() = 0; virtual u64 GetCNTPCT() const = 0;
/// Returns current wall time in emulated cpu cycles /// @returns The raw host timer ticks since an indeterminate epoch.
[[nodiscard]] virtual u64 GetCPUCycles() = 0; virtual u64 GetHostTicksNow() const = 0;
virtual void Pause(bool is_paused) = 0; /// @returns The raw host timer ticks since the construction of this clock.
virtual u64 GetHostTicksElapsed() const = 0;
/// Tells if the wall clock, uses the host CPU's hardware clock /// @returns Whether the clock directly uses the host's hardware clock.
[[nodiscard]] bool IsNative() const { virtual bool IsNative() const = 0;
return is_native;
}
protected: protected:
explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_) using NsRatio = std::nano;
: emulated_cpu_frequency{emulated_cpu_frequency_}, using UsRatio = std::micro;
emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {} using MsRatio = std::milli;
u64 emulated_cpu_frequency; using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
u64 emulated_clock_frequency; using NsToMsRatio = std::ratio_divide<std::nano, std::milli>;
using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
private:
bool is_native;
}; };
[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, std::unique_ptr<WallClock> CreateOptimalClock();
u64 emulated_clock_frequency);
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency, std::unique_ptr<WallClock> CreateStandardWallClock();
u64 emulated_clock_frequency);
} // namespace Common } // namespace Common

View File

@ -1,164 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
#include <thread>
#include "common/atomic_ops.h"
#include "common/steady_clock.h"
#include "common/uint128.h" #include "common/uint128.h"
#include "common/x64/native_clock.h" #include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
#ifdef _MSC_VER namespace Common::X64 {
#include <intrin.h>
#endif
namespace Common { NativeClock::NativeClock(u64 rdtsc_frequency_)
: start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)} {}
#ifdef _MSC_VER std::chrono::nanoseconds NativeClock::GetTimeNS() const {
__forceinline static u64 FencedRDTSC() { return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
}
#else
static u64 FencedRDTSC() {
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
"rdtsc\n\t"
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
}
#endif
template <u64 Nearest>
static u64 RoundToNearest(u64 value) {
const auto mod = value % Nearest;
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
} }
u64 EstimateRDTSCFrequency() { std::chrono::microseconds NativeClock::GetTimeUS() const {
// Discard the first result measuring the rdtsc. return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
FencedRDTSC();
std::this_thread::sleep_for(std::chrono::milliseconds{1});
FencedRDTSC();
// Get the current time.
const auto start_time = Common::RealTimeClock::Now();
const u64 tsc_start = FencedRDTSC();
// Wait for 250 milliseconds.
std::this_thread::sleep_for(std::chrono::milliseconds{250});
const auto end_time = Common::RealTimeClock::Now();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
return RoundToNearest<1000>(tsc_freq);
} }
namespace X64 { std::chrono::milliseconds NativeClock::GetTimeMS() const {
NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
u64 rtsc_frequency_)
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
rtsc_frequency_} {
// Thread to re-adjust the RDTSC frequency after 10 seconds has elapsed.
time_sync_thread = std::jthread{[this](std::stop_token token) {
// Get the current time.
const auto start_time = Common::RealTimeClock::Now();
const u64 tsc_start = FencedRDTSC();
// Wait for 10 seconds.
if (!Common::StoppableTimedWait(token, std::chrono::seconds{10})) {
return;
}
const auto end_time = Common::RealTimeClock::Now();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
rtsc_frequency = tsc_freq;
CalculateAndSetFactors();
}};
time_point.inner.last_measure = FencedRDTSC();
time_point.inner.accumulated_ticks = 0U;
CalculateAndSetFactors();
} }
u64 NativeClock::GetRTSC() { u64 NativeClock::GetCNTPCT() const {
TimePoint new_time_point{}; return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
TimePoint current_time_point{};
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
const u64 current_measure = FencedRDTSC();
u64 diff = current_measure - current_time_point.inner.last_measure;
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
? current_measure
: current_time_point.inner.last_measure;
new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
current_time_point.pack, current_time_point.pack));
return new_time_point.inner.accumulated_ticks;
} }
void NativeClock::Pause(bool is_paused) { u64 NativeClock::GetHostTicksNow() const {
if (!is_paused) { return FencedRDTSC();
TimePoint current_time_point{};
TimePoint new_time_point{};
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
new_time_point.pack = current_time_point.pack;
new_time_point.inner.last_measure = FencedRDTSC();
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
current_time_point.pack, current_time_point.pack));
}
} }
std::chrono::nanoseconds NativeClock::GetTimeNS() { u64 NativeClock::GetHostTicksElapsed() const {
const u64 rtsc_value = GetRTSC(); return FencedRDTSC() - start_ticks;
return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
} }
std::chrono::microseconds NativeClock::GetTimeUS() { bool NativeClock::IsNative() const {
const u64 rtsc_value = GetRTSC(); return true;
return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
} }
std::chrono::milliseconds NativeClock::GetTimeMS() { } // namespace Common::X64
const u64 rtsc_value = GetRTSC();
return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
}
u64 NativeClock::GetClockCycles() {
const u64 rtsc_value = GetRTSC();
return MultiplyHigh(rtsc_value, clock_rtsc_factor);
}
u64 NativeClock::GetCPUCycles() {
const u64 rtsc_value = GetRTSC();
return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
}
void NativeClock::CalculateAndSetFactors() {
ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency);
clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
}
} // namespace X64
} // namespace Common

View File

@ -3,58 +3,36 @@
#pragma once #pragma once
#include "common/polyfill_thread.h"
#include "common/wall_clock.h" #include "common/wall_clock.h"
namespace Common { namespace Common::X64 {
namespace X64 {
class NativeClock final : public WallClock { class NativeClock final : public WallClock {
public: public:
explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, explicit NativeClock(u64 rdtsc_frequency_);
u64 rtsc_frequency_);
std::chrono::nanoseconds GetTimeNS() override; std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() override; std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() override; std::chrono::milliseconds GetTimeMS() const override;
u64 GetClockCycles() override; u64 GetCNTPCT() const override;
u64 GetCPUCycles() override; u64 GetHostTicksNow() const override;
void Pause(bool is_paused) override; u64 GetHostTicksElapsed() const override;
bool IsNative() const override;
private: private:
u64 GetRTSC(); u64 start_ticks;
u64 rdtsc_frequency;
void CalculateAndSetFactors(); u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
union alignas(16) TimePoint { u64 ms_rdtsc_factor;
TimePoint() : pack{} {} u64 cntpct_rdtsc_factor;
u128 pack{};
struct Inner {
u64 last_measure{};
u64 accumulated_ticks{};
} inner;
}; };
TimePoint time_point; } // namespace Common::X64
// factors
u64 clock_rtsc_factor{};
u64 cpu_rtsc_factor{};
u64 ns_rtsc_factor{};
u64 us_rtsc_factor{};
u64 ms_rtsc_factor{};
u64 rtsc_frequency;
std::jthread time_sync_thread;
};
} // namespace X64
u64 EstimateRDTSCFrequency();
} // namespace Common