diff options
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/CMakeLists.txt | 11 | ||||
| -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.cpp | 106 | ||||
| -rw-r--r-- | src/common/thread.h | 14 | ||||
| -rw-r--r-- | src/common/wall_clock.cpp | 197 | ||||
| -rw-r--r-- | src/common/x64/cpu_detect.h | 82 | ||||
| -rw-r--r-- | src/common/x64/cpu_wait.cpp | 75 | ||||
| -rw-r--r-- | src/common/x64/cpu_wait.h | 10 |
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 |
