aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/CMakeLists.txt11
-rw-r--r--src/common/cpu_features.cpp (renamed from src/common/x64/cpu_detect.cpp)316
-rw-r--r--src/common/cpu_features.h (renamed from src/common/wall_clock.h)90
-rw-r--r--src/common/thread.cpp106
-rw-r--r--src/common/thread.h14
-rw-r--r--src/common/wall_clock.cpp197
-rw-r--r--src/common/x64/cpu_detect.h82
-rw-r--r--src/common/x64/cpu_wait.cpp75
-rw-r--r--src/common/x64/cpu_wait.h10
9 files changed, 438 insertions, 463 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index c5740bfb83..5896ae4d1f 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -141,12 +141,12 @@ add_library(
vector_math.h
virtual_buffer.cpp
virtual_buffer.h
- wall_clock.cpp
- wall_clock.h
zstd_compression.cpp
zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
fs/symlink.h fs/symlink.cpp
+ cpu_features.cpp
+ cpu_features.h
httplib.h
net/net.h net/net.cpp)
@@ -180,10 +180,7 @@ endif()
if(ARCHITECTURE_x86_64)
target_sources(
common
- PRIVATE x64/cpu_detect.cpp
- x64/cpu_detect.h
- x64/cpu_wait.cpp
- x64/cpu_wait.h
+ PRIVATE
x64/rdtsc.cpp
x64/rdtsc.h
x64/xbyak.h)
@@ -234,7 +231,7 @@ if(CXX_CLANG)
endif()
if (BOOST_NO_HEADERS)
- target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool)
+ target_link_libraries(common PUBLIC Boost::algorithm Boost::heap Boost::icl Boost::pool)
else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/cpu_features.cpp
index d613954eb5..2f2fae3a77 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/cpu_features.cpp
@@ -10,34 +10,32 @@
#include <fstream>
#include <iterator>
#include <optional>
+#include <algorithm>
#include <string_view>
#include <thread>
#include <vector>
-#include "common/bit_util.h"
-#include "common/common_types.h"
-#include "common/logging.h"
-#include "common/x64/cpu_detect.h"
-#include "common/x64/rdtsc.h"
-
#ifdef _WIN32
#include <windows.h>
+#elif defined(__DragonFly__) || defined(__FreeBSD__)
+#include <sys/types.h>
+#include <machine/cpufunc.h>
+#elif defined(__ANDROID__)
+#include <sys/system_properties.h>
#endif
+#include "common/steady_clock.h"
+#include "common/uint128.h"
+#include "common/bit_util.h"
+#include "common/common_types.h"
+#include "common/cpu_features.h"
+#include "common/logging.h"
+
+#ifdef ARCHITECTURE_x86_64
+#include "common/x64/rdtsc.h"
#ifdef _MSC_VER
#include <intrin.h>
-
-static inline u64 xgetbv(u32 index) {
- return _xgetbv(index);
-}
+static inline u64 xgetbv(u32 index) { return _xgetbv(index); }
#else
-
-#if defined(__DragonFly__) || defined(__FreeBSD__)
-// clang-format off
-#include <sys/types.h>
-#include <machine/cpufunc.h>
-// clang-format on
-#endif
-
static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) {
#if defined(__DragonFly__) || defined(__FreeBSD__)
// Despite the name, this is just do_cpuid() with ECX as second input.
@@ -50,11 +48,7 @@ static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) {
: "a"(function_id), "c"(subfunction_id));
#endif
}
-
-static inline void __cpuid(int info[4], u32 function_id) {
- return __cpuidex(info, function_id, 0);
-}
-
+static inline void __cpuid(int info[4], u32 function_id) { return __cpuidex(info, function_id, 0); }
#define _XCR_XFEATURE_ENABLED_MASK 0
static inline u64 xgetbv(u32 index) {
u32 eax, edx;
@@ -62,9 +56,10 @@ static inline u64 xgetbv(u32 index) {
return ((u64)edx << 32) | eax;
}
#endif // _MSC_VER
+#endif
namespace Common {
-
+#ifdef ARCHITECTURE_x86_64
CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string) {
if (brand_string == "GenuineIntel") {
return Manufacturer::Intel;
@@ -76,13 +71,53 @@ CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string)
return Manufacturer::Unknown;
}
-// Detects the various CPU features
-static CPUCaps Detect() {
+std::optional<int> GetProcessorCount() {
+#if defined(_WIN32)
+ // Get the buffer length.
+ DWORD length = 0;
+ GetLogicalProcessorInformation(nullptr, &length);
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ LOG_ERROR(Frontend, "Failed to query core count.");
+ return std::nullopt;
+ }
+ std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer(
+ length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
+ // Now query the core count.
+ if (!GetLogicalProcessorInformation(buffer.data(), &length)) {
+ LOG_ERROR(Frontend, "Failed to query core count.");
+ return std::nullopt;
+ }
+ return static_cast<int>(
+ std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) {
+ return proc_info.Relationship == RelationProcessorCore;
+ }));
+#elif defined(__unix__)
+ const int thread_count = std::thread::hardware_concurrency();
+ std::ifstream smt("/sys/devices/system/cpu/smt/active");
+ char state = '0';
+ if (smt) {
+ smt.read(&state, sizeof(state));
+ }
+ switch (state) {
+ case '0':
+ return thread_count;
+ case '1':
+ return thread_count / 2;
+ default:
+ return std::nullopt;
+ }
+#else
+ // Shame on you
+ return std::nullopt;
+#endif
+}
+
+/// @brief Detects the various CPU features
+const CPUCaps g_cpu_caps = [] {
CPUCaps caps = {};
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
// yuzu at all anyway
-
int cpu_id[4];
// Detect CPU's CPUID capabilities and grab manufacturer string
@@ -183,9 +218,8 @@ static CPUCaps Detect() {
// https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569
// but it's easier to just estimate the TSC tick rate for these cases.
if (caps.tsc_crystal_ratio_denominator) {
- caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
- caps.tsc_crystal_ratio_numerator /
- caps.tsc_crystal_ratio_denominator;
+ caps.tsc_frequency = u64(caps.crystal_frequency)
+ * caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_denominator;
} else {
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
}
@@ -197,54 +231,196 @@ static CPUCaps Detect() {
caps.max_frequency = cpu_id[1];
caps.bus_frequency = cpu_id[2];
}
-
return caps;
+}();
+
+#else
+
+#endif
+
+#if defined(ARCHITECTURE_x86_64)
+WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
+ : rdtsc_frequency{rdtsc_frequency_}
+ , ns_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_) : 0}
+ , us_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_) : 0}
+ , ms_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_) : 0}
+ , rdtsc_ns_factor{invariant_ ? GetFixedPoint64Factor(rdtsc_frequency_, NsRatio::den) : 1}
+ , cntpct_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_) : 0}
+ , gputick_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_) : 0}
+ , invariant{invariant_}
+{}
+
+std::chrono::nanoseconds WallClock::GetTimeNS() const {
+ if (!invariant)
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch());
+ return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
-const CPUCaps& GetCPUCaps() {
- static CPUCaps caps = Detect();
- return caps;
+std::chrono::microseconds WallClock::GetTimeUS() const {
+ if (!invariant)
+ return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
+ return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
-std::optional<int> GetProcessorCount() {
-#if defined(_WIN32)
- // Get the buffer length.
- DWORD length = 0;
- GetLogicalProcessorInformation(nullptr, &length);
- if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
- LOG_ERROR(Frontend, "Failed to query core count.");
- return std::nullopt;
- }
- std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer(
- length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
- // Now query the core count.
- if (!GetLogicalProcessorInformation(buffer.data(), &length)) {
- LOG_ERROR(Frontend, "Failed to query core count.");
- return std::nullopt;
- }
- return static_cast<int>(
- std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) {
- return proc_info.Relationship == RelationProcessorCore;
- }));
-#elif defined(__unix__)
- const int thread_count = std::thread::hardware_concurrency();
- std::ifstream smt("/sys/devices/system/cpu/smt/active");
- char state = '0';
- if (smt) {
- smt.read(&state, sizeof(state));
- }
- switch (state) {
- case '0':
- return thread_count;
- case '1':
- return thread_count / 2;
- default:
- return std::nullopt;
+std::chrono::milliseconds WallClock::GetTimeMS() const {
+ if (!invariant)
+ return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch());
+ return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
+}
+
+s64 WallClock::GetCNTPCT() const {
+ if (!invariant)
+ return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
+ return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
+}
+
+s64 WallClock::GetGPUTick() const {
+ if (!invariant)
+ return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
+ return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
+}
+
+s64 WallClock::GetUptime() const {
+ if (!invariant)
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
+ return s64(Common::X64::FencedRDTSC());
+}
+
+bool WallClock::IsNative() const {
+ return invariant;
+}
+
+u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
+ return invariant ? MultiplyHigh(ns.count(), rdtsc_ns_factor) : ns.count();
+}
+#elif defined(HAS_NCE)
+namespace {
+[[nodiscard]] Common::WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
+ return (Common::WallClock::FactorType(num) << 64) / den;
+}
+[[nodiscard]] u64 MultiplyHigh(u64 m, Common::WallClock::FactorType factor) noexcept {
+ return static_cast<u64>((m * factor) >> 64);
+}
+[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
+ u64 cntfrq_el0 = 0;
+#ifdef ANDROID
+ std::string_view board{""};
+ char buffer[PROP_VALUE_MAX];
+ int len{__system_property_get("ro.product.board", buffer)};
+ board = std::string_view(buffer, static_cast<size_t>(len));
+ if (board == "s5e9925") { // Exynos 2200
+ cntfrq_el0 = 25600000;
+ } else if (board == "exynos2100") { // Exynos 2100
+ cntfrq_el0 = 26000000;
+ } else if (board == "exynos9810") { // Exynos 9810
+ cntfrq_el0 = 26000000;
+ } else if (board == "s5e8825") { // Exynos 1280
+ cntfrq_el0 = 26000000;
+ } else {
+ asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
}
+ return cntfrq_el0;
#else
- // Shame on you
- return std::nullopt;
+ asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+ return cntfrq_el0;
#endif
}
+} // namespace
+
+WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {
+ const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1);
+ ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
+ us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
+ ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
+ cntfrq_ns_factor = GetFixedPointFactor(host_cntfrq, NsRatio::den);
+ guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
+ gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
+}
+
+std::chrono::nanoseconds WallClock::GetTimeNS() const {
+ return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
+}
+
+std::chrono::microseconds WallClock::GetTimeUS() const {
+ return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
+}
+
+std::chrono::milliseconds WallClock::GetTimeMS() const {
+ return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
+}
+
+s64 WallClock::GetCNTPCT() const {
+ return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
+}
+
+s64 WallClock::GetGPUTick() const {
+ return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
+}
+
+s64 WallClock::GetUptime() const {
+ s64 cntvct_el0 = 0;
+ asm volatile(
+ "dsb ish\n\t"
+ "mrs %[cntvct_el0], cntvct_el0\n\t"
+ "dsb ish\n\t"
+ : [cntvct_el0] "=r"(cntvct_el0)
+ );
+ return cntvct_el0;
+}
+
+bool WallClock::IsNative() const {
+ return true;
+}
+
+u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
+ return MultiplyHigh(ns.count(), cntfrq_ns_factor);
+}
+#else
+WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {}
+
+std::chrono::nanoseconds WallClock::GetTimeNS() const {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch());
+}
+
+std::chrono::microseconds WallClock::GetTimeUS() const {
+ return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
+}
+
+std::chrono::milliseconds WallClock::GetTimeMS() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch());
+}
+
+s64 WallClock::GetCNTPCT() const {
+ return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
+}
+
+s64 WallClock::GetGPUTick() const {
+ return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
+}
+
+s64 WallClock::GetUptime() const {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
+}
+
+bool WallClock::IsNative() const {
+ return false;
+}
+u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
+ return ns.count();
+}
+#endif
+
+// Wall clock MUST be initialized AFTER g_cpu_caps
+// C++ only guarantees ctor init in the order they appear in TU
+const WallClock g_wall_clock = [] {
+#if defined(ARCHITECTURE_x86_64)
+ auto const& caps = Common::g_cpu_caps;
+ return WallClock(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den, caps.tsc_frequency);
+#elif defined(HAS_NCE)
+ return WallClock(false, 1);
+#else
+ return WallClock(true, 1);
+#endif
+}();
} // namespace Common
diff --git a/src/common/wall_clock.h b/src/common/cpu_features.h
index 6a6d56a610..7dcb550f8f 100644
--- a/src/common/wall_clock.h
+++ b/src/common/cpu_features.h
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <optional>
+#include <string_view>
#include <chrono>
#include <memory>
#include <ratio>
@@ -43,6 +46,9 @@ public:
/// @returns Whether the clock directly uses the host's hardware clock.
bool IsNative() const;
+ // @returns Nanoseconds to native ticks
+ u64 NsToTicks(std::chrono::nanoseconds ns) const;
+
static inline u64 NSToCNTPCT(u64 ns) {
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
@@ -69,7 +75,6 @@ public:
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
}
-protected:
using NsRatio = std::nano;
using UsRatio = std::micro;
using MsRatio = std::milli;
@@ -87,31 +92,96 @@ protected:
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
#if defined(ARCHITECTURE_x86_64)
- bool invariant;
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
+ u64 rdtsc_ns_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
+ bool invariant;
#elif defined(HAS_NCE)
-public:
using FactorType = unsigned __int128;
-
- FactorType GetGuestCNTFRQFactor() const {
+ [[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
-protected:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
+ FactorType cntfrq_ns_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
-#else
-
#endif
};
-[[nodiscard]] WallClock CreateOptimalClock() noexcept;
+#ifdef ARCHITECTURE_x86_64
+/// x86/x64 CPU capabilities that may be detected by this module
+struct CPUCaps {
+ enum class Manufacturer : u8 {
+ Unknown = 0,
+ Intel = 1,
+ AMD = 2,
+ Hygon = 3,
+ };
+
+ static Manufacturer ParseManufacturer(std::string_view brand_string);
+
+ Manufacturer manufacturer;
+ char brand_string[13];
+
+ char cpu_string[48];
+
+ u32 base_frequency;
+ u32 max_frequency;
+ u32 bus_frequency;
+
+ u32 tsc_crystal_ratio_denominator;
+ u32 tsc_crystal_ratio_numerator;
+ u32 crystal_frequency;
+ u64 tsc_frequency; // Derived from the above three values
+
+ bool sse3 : 1;
+ bool ssse3 : 1;
+ bool sse4_1 : 1;
+ bool sse4_2 : 1;
+
+ bool avx : 1;
+ bool avx2 : 1;
+ bool avx512f : 1;
+ bool avx512dq : 1;
+ bool avx512cd : 1;
+ bool avx512bw : 1;
+ bool avx512vl : 1;
+ bool avx512vbmi : 1;
+ bool avx512bitalg : 1;
+
+ bool aes : 1;
+ bool bmi1 : 1;
+ bool bmi2 : 1;
+ bool f16c : 1;
+ bool fma : 1;
+ bool gfni : 1;
+ bool invariant_tsc : 1;
+ bool lzcnt : 1;
+ bool monitorx : 1;
+ bool movbe : 1;
+ bool pclmulqdq : 1;
+ bool popcnt : 1;
+ bool sha : 1;
+ bool waitpkg : 1;
+};
+#else
+struct CPUCaps {
+ bool padding;
+};
+#endif
+
+/// Detects CPU core count
+std::optional<int> GetProcessorCount();
+
+/// @brief Global cpu caps
+extern const CPUCaps g_cpu_caps;
+/// @brief Global wall clock
+extern const WallClock g_wall_clock;
} // namespace Common
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index f4bdb3f7c0..49f6f5cf98 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <chrono>
+#include <limits>
#include <string>
#include <thread>
@@ -18,24 +20,35 @@
#elif defined(_WIN32)
#include <windows.h>
#include "common/string_util.h"
+#include "common/windows/timer_resolution.h"
#else
#if defined(__FreeBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
+// Compatibility with CPUset
+#define cpu_set_t cpuset_t
#elif defined(__DragonFly__) || defined(__OpenBSD__) || defined(__Bitrig__)
#include <pthread_np.h>
#endif
#include <pthread.h>
#include <sched.h>
#endif
+
#ifndef _WIN32
#include <unistd.h>
#endif
-#ifdef __FreeBSD__
-# define cpu_set_t cpuset_t
+#include "common/cpu_features.h"
+#ifdef ARCHITECTURE_x86_64
+#ifdef _MSC_VER
+#include <intrin.h>
+#else
+#include <x86intrin.h>
#endif
+#include "common/x64/rdtsc.h"
+#endif
+#include "core/core_timing.h"
namespace Common {
@@ -144,4 +157,93 @@ void PinCurrentThreadToPerformanceCore(size_t core_id) {
}
}
+#ifdef ARCHITECTURE_x86_64
+// On Linux and UNIX systems, a futex would nominally be used to cover the costs
+// the idea is that it's intuitivelly cheaper to use a direct instruction as opposed to a full futex call
+// the underlying libc++ implementation uses pthread_cond_timedwait which MAY invoke a futex
+// Let's pretend the OS is too expensive to jump into, and avoid ANY context switches
+// this should *IN THEORY* lower CPU usage while just waiting for stuff effectively
+// For windows the minimal quanta resolution is about 500us, and normal CRT cond var is 1.5ms(?)
+// so may as well avoid that too
+// Let's just give ALL platforms the same mechanisms (almost) for when they have umonitor OR waitpkg
+#ifdef __clang__
+__attribute__((target("waitpkg,mwaitx")))
+#elif defined(__GNUC__)
+#pragma GCC target("waitpkg")
+#pragma GCC target("mwaitx")
+#endif
+bool Event::WaitFor(const std::chrono::nanoseconds time) {
+#ifdef _WIN32
+ auto const start = Common::X64::FencedRDTSC();
+ auto const& caps = Common::g_cpu_caps;
+ [[maybe_unused]] auto const end = start + Common::g_wall_clock.NsToTicks(time);
+ if (caps.monitorx) {
+ while (true) {
+ // Armed monitor, as per manual, MWAITX must be conditional if the condition isn't satisfied
+ // to prevent a race condition.
+ _mm_monitorx(reinterpret_cast<u64*>(std::addressof(is_set)), 0, 0);
+ if (!is_set.load()) {
+ // RDTSC may be fenced here due to atomic load
+ auto const now = _rdtsc();
+ if (end > now) {
+ u32 const cycles = std::min<u32>((std::numeric_limits<u32>::max)(), s64(end) - s64(now));
+ // See here: https://github.com/torvalds/linux/blob/948a64995aca6820abefd17f1a4258f5835c5ad9/arch/x86/lib/delay.c#L93
+ // MWAITX accepts a 32-bit input timer which determines the total number of cycles to wait for
+ // NOT THE TOTAL ABSOLUTE TSC VALUE, it's just a delta
+ // BIT[1] = use a timer
+ // Hint = 0: Use C1 state when sleepy (means slower wakeup but better savings)
+ _mm_mwaitx(1 << 1, 0u, cycles);
+ if (!is_set.load())
+ return false;
+ } else
+ return false; //timeout
+ }
+ bool expected = true;
+ if (is_set.compare_exchange_weak(expected, false, std::memory_order_release))
+ return true;
+ }
+ } else if (caps.waitpkg) {
+ // #UD If CPUID.7.0:ECX.WAITPKG[bit 5]=0.
+ while (true) {
+ _umonitor(std::addressof(is_set));
+ if (!is_set.load() && !_umwait(0, end)) //umwait is absolute time!!!
+ return false;
+ bool expected = true;
+ if (is_set.compare_exchange_weak(expected, false, std::memory_order_release))
+ return true;
+ }
+ } else {
+ while (!is_set.load() && end > _rdtsc())
+ Common::Windows::SleepForOneTick();
+ if (is_set.load())
+ Reset();
+ return true;
+ }
+#else
+ std::unique_lock lk{mutex};
+ if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
+ return false;
+ is_set = false;
+ return true;
+#endif
+}
+#else
+bool Event::WaitFor(const std::chrono::nanoseconds time) {
+#ifdef _WIN32
+ auto const end = Common::g_wall_clock.GetTimeNS() + time;
+ while (!is_set.load() && end > Common::g_wall_clock.GetTimeNS())
+ Common::Windows::SleepForOneTick();
+ if (is_set.load())
+ Reset();
+ return true;
+#else
+ std::unique_lock lk{mutex};
+ if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
+ return false;
+ is_set = false;
+ return true;
+#endif
+}
+#endif
+
} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index ea6f5d6b3b..a75e342802 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -34,16 +34,10 @@ public:
is_set = false;
}
- bool WaitFor(const std::chrono::nanoseconds& time) {
- std::unique_lock lk{mutex};
- if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
- return false;
- is_set = false;
- return true;
- }
+ bool WaitFor(const std::chrono::nanoseconds time);
- template <class Clock, class Duration>
- bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
+ template<class Clock, class Duration>
+ bool WaitUntil(const std::chrono::time_point<Clock, Duration> time) {
std::unique_lock lk{mutex};
if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
return false;
@@ -63,9 +57,9 @@ public:
}
private:
+ alignas(64) std::atomic<bool> is_set{false};
std::condition_variable condvar;
std::mutex mutex;
- std::atomic_bool is_set{false};
};
class Barrier {
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
deleted file mode 100644
index c06f9b6318..0000000000
--- a/src/common/wall_clock.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/steady_clock.h"
-#include "common/uint128.h"
-#include "common/wall_clock.h"
-
-#ifdef __ANDROID__
-#include <sys/system_properties.h>
-#endif
-#ifdef ARCHITECTURE_x86_64
-#include "common/x64/cpu_detect.h"
-#include "common/x64/rdtsc.h"
-#endif
-
-namespace Common {
-
-#if defined(ARCHITECTURE_x86_64)
-WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
- : invariant{invariant_}
- , rdtsc_frequency{rdtsc_frequency_}
- , ns_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)}
- , us_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)}
- , ms_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)}
- , cntpct_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)}
- , gputick_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)}
-{}
-
-std::chrono::nanoseconds WallClock::GetTimeNS() const {
- if (invariant)
- return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
- return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
-}
-
-std::chrono::microseconds WallClock::GetTimeUS() const {
- if (invariant)
- return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
- return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
-}
-
-std::chrono::milliseconds WallClock::GetTimeMS() const {
- if (invariant)
- return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
- return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
-}
-
-s64 WallClock::GetCNTPCT() const {
- if (invariant)
- return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
- return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
-}
-
-s64 WallClock::GetGPUTick() const {
- if (invariant)
- return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
- return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
-}
-
-s64 WallClock::GetUptime() const {
- if (invariant)
- return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
- return s64(Common::X64::FencedRDTSC());
-}
-
-bool WallClock::IsNative() const {
- if (invariant)
- return false;
- return true;
-}
-#elif defined(HAS_NCE)
-namespace {
-
-[[nodiscard]] WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
- return (WallClock::FactorType(num) << 64) / den;
-}
-
-[[nodiscard]] u64 MultiplyHigh(u64 m, WallClock::FactorType factor) noexcept {
- return static_cast<u64>((m * factor) >> 64);
-}
-
-[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
- u64 cntfrq_el0 = 0;
-#ifdef ANDROID
- std::string_view board{""};
- char buffer[PROP_VALUE_MAX];
- int len{__system_property_get("ro.product.board", buffer)};
- board = std::string_view(buffer, static_cast<size_t>(len));
- if (board == "s5e9925") { // Exynos 2200
- cntfrq_el0 = 25600000;
- } else if (board == "exynos2100") { // Exynos 2100
- cntfrq_el0 = 26000000;
- } else if (board == "exynos9810") { // Exynos 9810
- cntfrq_el0 = 26000000;
- } else if (board == "s5e8825") { // Exynos 1280
- cntfrq_el0 = 26000000;
- } else {
- asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
- }
- return cntfrq_el0;
-#else
- asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
- return cntfrq_el0;
-#endif
-}
-
-} // namespace
-
-WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {
- const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1);
- ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
- us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
- ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
- guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
- gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
-}
-
-std::chrono::nanoseconds WallClock::GetTimeNS() const {
- return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
-}
-
-std::chrono::microseconds WallClock::GetTimeUS() const {
- return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
-}
-
-std::chrono::milliseconds WallClock::GetTimeMS() const {
- return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
-}
-
-s64 WallClock::GetCNTPCT() const {
- return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
-}
-
-s64 WallClock::GetGPUTick() const {
- return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
-}
-
-s64 WallClock::GetUptime() const {
- s64 cntvct_el0 = 0;
- asm volatile(
- "dsb ish\n\t"
- "mrs %[cntvct_el0], cntvct_el0\n\t"
- "dsb ish\n\t"
- : [cntvct_el0] "=r"(cntvct_el0)
- );
- return cntvct_el0;
-}
-
-bool WallClock::IsNative() const {
- return true;
-}
-#else
-WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {}
-
-std::chrono::nanoseconds WallClock::GetTimeNS() const {
- return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
-}
-
-std::chrono::microseconds WallClock::GetTimeUS() const {
- return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
-}
-
-std::chrono::milliseconds WallClock::GetTimeMS() const {
- return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
-}
-
-s64 WallClock::GetCNTPCT() const {
- return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
-}
-
-s64 WallClock::GetGPUTick() const {
- return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
-}
-
-s64 WallClock::GetUptime() const {
- return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
-}
-
-bool WallClock::IsNative() const {
- return false;
-}
-#endif
-
-WallClock CreateOptimalClock() noexcept {
-#if defined(ARCHITECTURE_x86_64)
- auto const& caps = GetCPUCaps();
- return WallClock(!(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den), caps.tsc_frequency);
-#elif defined(HAS_NCE)
- return WallClock(false, 1);
-#else
- return WallClock(true, 1);
-#endif
-}
-
-} // namespace Common
diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h
deleted file mode 100644
index af93c36e2c..0000000000
--- a/src/common/x64/cpu_detect.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <optional>
-#include <string_view>
-#include "common/common_types.h"
-
-namespace Common {
-
-/// x86/x64 CPU capabilities that may be detected by this module
-struct CPUCaps {
-
- enum class Manufacturer : u8 {
- Unknown = 0,
- Intel = 1,
- AMD = 2,
- Hygon = 3,
- };
-
- static Manufacturer ParseManufacturer(std::string_view brand_string);
-
- Manufacturer manufacturer;
- char brand_string[13];
-
- char cpu_string[48];
-
- u32 base_frequency;
- u32 max_frequency;
- u32 bus_frequency;
-
- u32 tsc_crystal_ratio_denominator;
- u32 tsc_crystal_ratio_numerator;
- u32 crystal_frequency;
- u64 tsc_frequency; // Derived from the above three values
-
- bool sse3 : 1;
- bool ssse3 : 1;
- bool sse4_1 : 1;
- bool sse4_2 : 1;
-
- bool avx : 1;
- bool avx2 : 1;
- bool avx512f : 1;
- bool avx512dq : 1;
- bool avx512cd : 1;
- bool avx512bw : 1;
- bool avx512vl : 1;
- bool avx512vbmi : 1;
- bool avx512bitalg : 1;
-
- bool aes : 1;
- bool bmi1 : 1;
- bool bmi2 : 1;
- bool f16c : 1;
- bool fma : 1;
- bool gfni : 1;
- bool invariant_tsc : 1;
- bool lzcnt : 1;
- bool monitorx : 1;
- bool movbe : 1;
- bool pclmulqdq : 1;
- bool popcnt : 1;
- bool sha : 1;
- bool waitpkg : 1;
-};
-
-/**
- * Gets the supported capabilities of the host CPU
- * @return Reference to a CPUCaps struct with the detected host CPU capabilities
- */
-const CPUCaps& GetCPUCaps();
-
-/// Detects CPU core count
-std::optional<int> GetProcessorCount();
-
-} // namespace Common
diff --git a/src/common/x64/cpu_wait.cpp b/src/common/x64/cpu_wait.cpp
deleted file mode 100644
index 85d27161ba..0000000000
--- a/src/common/x64/cpu_wait.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include <thread>
-
-#ifdef _MSC_VER
-#include <intrin.h>
-#endif
-
-#include "common/x64/cpu_detect.h"
-#include "common/x64/cpu_wait.h"
-#include "common/x64/rdtsc.h"
-
-namespace Common::X64 {
-
-namespace {
-
-// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
-// For reference:
-// At 1 GHz, 100K cycles is 100us
-// At 2 GHz, 100K cycles is 50us
-// At 4 GHz, 100K cycles is 25us
-constexpr auto PauseCycles = 100'000U;
-
-} // Anonymous namespace
-
-#if defined(_MSC_VER) && !defined(__clang__)
-__forceinline static void TPAUSE() {
- static constexpr auto RequestC02State = 0U;
- _tpause(RequestC02State, FencedRDTSC() + PauseCycles);
-}
-
-__forceinline static void MWAITX() {
- static constexpr auto EnableWaitTimeFlag = 1U << 1;
- static constexpr auto RequestC1State = 0U;
-
- // monitor_var should be aligned to a cache line.
- alignas(64) u64 monitor_var{};
- _mm_monitorx(&monitor_var, 0, 0);
- _mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
-}
-#else
-static void TPAUSE() {
- static constexpr auto RequestC02State = 0U;
- const auto tsc = FencedRDTSC() + PauseCycles;
- const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
- const auto edx = static_cast<u32>(tsc >> 32);
- asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
-}
-
-static void MWAITX() {
- static constexpr auto EnableWaitTimeFlag = 1U << 1;
- static constexpr auto RequestC1State = 0U;
-
- // monitor_var should be aligned to a cache line.
- alignas(64) u64 monitor_var{};
- asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
- asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
-}
-#endif
-
-void MicroSleep() {
- static const bool has_waitpkg = GetCPUCaps().waitpkg;
- static const bool has_monitorx = GetCPUCaps().monitorx;
-
- if (has_waitpkg) {
- TPAUSE();
- } else if (has_monitorx) {
- MWAITX();
- } else {
- std::this_thread::yield();
- }
-}
-
-} // namespace Common::X64
diff --git a/src/common/x64/cpu_wait.h b/src/common/x64/cpu_wait.h
deleted file mode 100644
index 472ddca815..0000000000
--- a/src/common/x64/cpu_wait.h
+++ /dev/null
@@ -1,10 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-namespace Common::X64 {
-
-void MicroSleep();
-
-} // namespace Common::X64