diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/common/fiber.cpp | 226 | ||||
-rw-r--r-- | src/common/fiber.h | 92 | ||||
-rw-r--r-- | src/common/spin_lock.cpp | 54 | ||||
-rw-r--r-- | src/common/spin_lock.h | 21 | ||||
-rw-r--r-- | src/common/thread.h | 4 | ||||
-rw-r--r-- | src/common/uint128.cpp | 26 | ||||
-rw-r--r-- | src/common/uint128.h | 3 | ||||
-rw-r--r-- | src/common/wall_clock.cpp | 92 | ||||
-rw-r--r-- | src/common/wall_clock.h | 51 | ||||
-rw-r--r-- | src/common/x64/cpu_detect.cpp | 33 | ||||
-rw-r--r-- | src/common/x64/cpu_detect.h | 12 | ||||
-rw-r--r-- | src/common/x64/native_clock.cpp | 95 | ||||
-rw-r--r-- | src/common/x64/native_clock.h | 41 |
14 files changed, 758 insertions, 3 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0a3e2f4d1..3cc17d0e9 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -110,6 +110,8 @@ add_library(common STATIC common_types.h dynamic_library.cpp dynamic_library.h + fiber.cpp + fiber.h file_util.cpp file_util.h hash.h @@ -143,6 +145,8 @@ add_library(common STATIC scm_rev.cpp scm_rev.h scope_exit.h + spin_lock.cpp + spin_lock.h string_util.cpp string_util.h swap.h @@ -163,6 +167,8 @@ add_library(common STATIC vector_math.h virtual_buffer.cpp virtual_buffer.h + wall_clock.cpp + wall_clock.h web_result.h zstd_compression.cpp zstd_compression.h @@ -173,12 +179,15 @@ if(ARCHITECTURE_x86_64) PRIVATE x64/cpu_detect.cpp x64/cpu_detect.h + x64/native_clock.cpp + x64/native_clock.h x64/xbyak_abi.h x64/xbyak_util.h ) endif() create_target_directory_groups(common) +find_package(Boost 1.71 COMPONENTS context headers REQUIRED) -target_link_libraries(common PUBLIC Boost::boost fmt::fmt microprofile) +target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile) target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp new file mode 100644 index 000000000..f97ad433b --- /dev/null +++ b/src/common/fiber.cpp @@ -0,0 +1,226 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/fiber.h" +#if defined(_WIN32) || defined(WIN32) +#include <windows.h> +#else +#include <boost/context/detail/fcontext.hpp> +#endif + +namespace Common { + +constexpr std::size_t default_stack_size = 256 * 1024; // 256kb + +#if defined(_WIN32) || defined(WIN32) + +struct Fiber::FiberImpl { + LPVOID handle = nullptr; + LPVOID rewind_handle = nullptr; +}; + +void Fiber::Start() { + ASSERT(previous_fiber != nullptr); + previous_fiber->guard.unlock(); + previous_fiber.reset(); + entry_point(start_parameter); + UNREACHABLE(); +} + +void Fiber::OnRewind() { + ASSERT(impl->handle != nullptr); + DeleteFiber(impl->handle); + impl->handle = impl->rewind_handle; + impl->rewind_handle = nullptr; + rewind_point(rewind_parameter); + UNREACHABLE(); +} + +void Fiber::FiberStartFunc(void* fiber_parameter) { + auto fiber = static_cast<Fiber*>(fiber_parameter); + fiber->Start(); +} + +void Fiber::RewindStartFunc(void* fiber_parameter) { + auto fiber = static_cast<Fiber*>(fiber_parameter); + fiber->OnRewind(); +} + +Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) + : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { + impl = std::make_unique<FiberImpl>(); + impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this); +} + +Fiber::Fiber() { + impl = std::make_unique<FiberImpl>(); +} + +Fiber::~Fiber() { + if (released) { + return; + } + // Make sure the Fiber is not being used + const bool locked = guard.try_lock(); + ASSERT_MSG(locked, "Destroying a fiber that's still running"); + if (locked) { + guard.unlock(); + } + DeleteFiber(impl->handle); +} + +void Fiber::Exit() { + ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); + if (!is_thread_fiber) { + return; + } + ConvertFiberToThread(); + guard.unlock(); + released = true; +} + +void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) { + rewind_point = std::move(rewind_func); + rewind_parameter = start_parameter; +} + +void Fiber::Rewind() { + ASSERT(rewind_point); + ASSERT(impl->rewind_handle == nullptr); + impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this); + SwitchToFiber(impl->rewind_handle); +} + +void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) { + ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); + ASSERT_MSG(to != nullptr, "Next fiber is null!"); + to->guard.lock(); + to->previous_fiber = from; + SwitchToFiber(to->impl->handle); + ASSERT(from->previous_fiber != nullptr); + from->previous_fiber->guard.unlock(); + from->previous_fiber.reset(); +} + +std::shared_ptr<Fiber> Fiber::ThreadToFiber() { + std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; + fiber->guard.lock(); + fiber->impl->handle = ConvertThreadToFiber(nullptr); + fiber->is_thread_fiber = true; + return fiber; +} + +#else + +struct Fiber::FiberImpl { + alignas(64) std::array<u8, default_stack_size> stack; + u8* stack_limit; + alignas(64) std::array<u8, default_stack_size> rewind_stack; + u8* rewind_stack_limit; + boost::context::detail::fcontext_t context; + boost::context::detail::fcontext_t rewind_context; +}; + +void Fiber::Start(boost::context::detail::transfer_t& transfer) { + ASSERT(previous_fiber != nullptr); + previous_fiber->impl->context = transfer.fctx; + previous_fiber->guard.unlock(); + previous_fiber.reset(); + entry_point(start_parameter); + UNREACHABLE(); +} + +void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) { + ASSERT(impl->context != nullptr); + impl->context = impl->rewind_context; + impl->rewind_context = nullptr; + u8* tmp = impl->stack_limit; + impl->stack_limit = impl->rewind_stack_limit; + impl->rewind_stack_limit = tmp; + rewind_point(rewind_parameter); + UNREACHABLE(); +} + +void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { + auto fiber = static_cast<Fiber*>(transfer.data); + fiber->Start(transfer); +} + +void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) { + auto fiber = static_cast<Fiber*>(transfer.data); + fiber->OnRewind(transfer); +} + +Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) + : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { + impl = std::make_unique<FiberImpl>(); + impl->stack_limit = impl->stack.data(); + impl->rewind_stack_limit = impl->rewind_stack.data(); + u8* stack_base = impl->stack_limit + default_stack_size; + impl->context = + boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc); +} + +void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) { + rewind_point = std::move(rewind_func); + rewind_parameter = start_parameter; +} + +Fiber::Fiber() { + impl = std::make_unique<FiberImpl>(); +} + +Fiber::~Fiber() { + if (released) { + return; + } + // Make sure the Fiber is not being used + const bool locked = guard.try_lock(); + ASSERT_MSG(locked, "Destroying a fiber that's still running"); + if (locked) { + guard.unlock(); + } +} + +void Fiber::Exit() { + + ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); + if (!is_thread_fiber) { + return; + } + guard.unlock(); + released = true; +} + +void Fiber::Rewind() { + ASSERT(rewind_point); + ASSERT(impl->rewind_context == nullptr); + u8* stack_base = impl->rewind_stack_limit + default_stack_size; + impl->rewind_context = + boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc); + boost::context::detail::jump_fcontext(impl->rewind_context, this); +} + +void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) { + ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); + ASSERT_MSG(to != nullptr, "Next fiber is null!"); + to->guard.lock(); + to->previous_fiber = from; + auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get()); + ASSERT(from->previous_fiber != nullptr); + from->previous_fiber->impl->context = transfer.fctx; + from->previous_fiber->guard.unlock(); + from->previous_fiber.reset(); +} + +std::shared_ptr<Fiber> Fiber::ThreadToFiber() { + std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; + fiber->guard.lock(); + fiber->is_thread_fiber = true; + return fiber; +} + +#endif +} // namespace Common diff --git a/src/common/fiber.h b/src/common/fiber.h new file mode 100644 index 000000000..dafc1100e --- /dev/null +++ b/src/common/fiber.h @@ -0,0 +1,92 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> + +#include "common/common_types.h" +#include "common/spin_lock.h" + +#if !defined(_WIN32) && !defined(WIN32) +namespace boost::context::detail { +struct transfer_t; +} +#endif + +namespace Common { + +/** + * Fiber class + * a fiber is a userspace thread with it's own context. They can be used to + * implement coroutines, emulated threading systems and certain asynchronous + * patterns. + * + * This class implements fibers at a low level, thus allowing greater freedom + * to implement such patterns. This fiber class is 'threadsafe' only one fiber + * can be running at a time and threads will be locked while trying to yield to + * a running fiber until it yields. WARNING exchanging two running fibers between + * threads will cause a deadlock. In order to prevent a deadlock, each thread should + * have an intermediary fiber, you switch to the intermediary fiber of the current + * thread and then from it switch to the expected fiber. This way you can exchange + * 2 fibers within 2 different threads. + */ +class Fiber { +public: + Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter); + ~Fiber(); + + Fiber(const Fiber&) = delete; + Fiber& operator=(const Fiber&) = delete; + + Fiber(Fiber&&) = default; + Fiber& operator=(Fiber&&) = default; + + /// Yields control from Fiber 'from' to Fiber 'to' + /// Fiber 'from' must be the currently running fiber. + static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to); + static std::shared_ptr<Fiber> ThreadToFiber(); + + void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter); + + void Rewind(); + + /// Only call from main thread's fiber + void Exit(); + + /// Changes the start parameter of the fiber. Has no effect if the fiber already started + void SetStartParameter(void* new_parameter) { + start_parameter = new_parameter; + } + +private: + Fiber(); + +#if defined(_WIN32) || defined(WIN32) + void OnRewind(); + void Start(); + static void FiberStartFunc(void* fiber_parameter); + static void RewindStartFunc(void* fiber_parameter); +#else + void OnRewind(boost::context::detail::transfer_t& transfer); + void Start(boost::context::detail::transfer_t& transfer); + static void FiberStartFunc(boost::context::detail::transfer_t transfer); + static void RewindStartFunc(boost::context::detail::transfer_t transfer); +#endif + + struct FiberImpl; + + SpinLock guard{}; + std::function<void(void*)> entry_point; + std::function<void(void*)> rewind_point; + void* rewind_parameter{}; + void* start_parameter{}; + std::shared_ptr<Fiber> previous_fiber; + std::unique_ptr<FiberImpl> impl; + bool is_thread_fiber{}; + bool released{}; +}; + +} // namespace Common diff --git a/src/common/spin_lock.cpp b/src/common/spin_lock.cpp new file mode 100644 index 000000000..c7b46aac6 --- /dev/null +++ b/src/common/spin_lock.cpp @@ -0,0 +1,54 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/spin_lock.h" + +#if _MSC_VER +#include <intrin.h> +#if _M_AMD64 +#define __x86_64__ 1 +#endif +#if _M_ARM64 +#define __aarch64__ 1 +#endif +#else +#if __x86_64__ +#include <xmmintrin.h> +#endif +#endif + +namespace { + +void thread_pause() { +#if __x86_64__ + _mm_pause(); +#elif __aarch64__ && _MSC_VER + __yield(); +#elif __aarch64__ + asm("yield"); +#endif +} + +} // namespace + +namespace Common { + +void SpinLock::lock() { + while (lck.test_and_set(std::memory_order_acquire)) { + thread_pause(); + } +} + +void SpinLock::unlock() { + lck.clear(std::memory_order_release); +} + +bool SpinLock::try_lock() { + if (lck.test_and_set(std::memory_order_acquire)) { + return false; + } + return true; +} + +} // namespace Common diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h new file mode 100644 index 000000000..70282a961 --- /dev/null +++ b/src/common/spin_lock.h @@ -0,0 +1,21 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> + +namespace Common { + +class SpinLock { +public: + void lock(); + void unlock(); + bool try_lock(); + +private: + std::atomic_flag lck = ATOMIC_FLAG_INIT; +}; + +} // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index 2fc071685..127cc7e23 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -9,6 +9,7 @@ #include <cstddef> #include <mutex> #include <thread> +#include "common/common_types.h" namespace Common { @@ -28,8 +29,7 @@ public: is_set = false; } - template <class Duration> - bool WaitFor(const std::chrono::duration<Duration>& time) { + bool WaitFor(const std::chrono::nanoseconds& time) { std::unique_lock lk{mutex}; if (!condvar.wait_for(lk, time, [this] { return is_set; })) return false; diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp index 32bf56730..16bf7c828 100644 --- a/src/common/uint128.cpp +++ b/src/common/uint128.cpp @@ -6,12 +6,38 @@ #include <intrin.h> #pragma intrinsic(_umul128) +#pragma intrinsic(_udiv128) #endif #include <cstring> #include "common/uint128.h" namespace Common { +#ifdef _MSC_VER + +u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) { + u128 r{}; + r[0] = _umul128(a, b, &r[1]); + u64 remainder; +#if _MSC_VER < 1923 + return udiv128(r[1], r[0], d, &remainder); +#else + return _udiv128(r[1], r[0], d, &remainder); +#endif +} + +#else + +u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) { + const u64 diva = a / d; + const u64 moda = a % d; + const u64 divb = b / d; + const u64 modb = b % d; + return diva * b + moda * divb + moda * modb / d; +} + +#endif + u128 Multiply64Into128(u64 a, u64 b) { u128 result; #ifdef _MSC_VER diff --git a/src/common/uint128.h b/src/common/uint128.h index a3be2a2cb..503cd2d0c 100644 --- a/src/common/uint128.h +++ b/src/common/uint128.h @@ -9,6 +9,9 @@ namespace Common { +// This function multiplies 2 u64 values and divides it by a u64 value. +u64 MultiplyAndDivide64(u64 a, u64 b, u64 d); + // This function multiplies 2 u64 values and produces a u128 value; u128 Multiply64Into128(u64 a, u64 b); diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp new file mode 100644 index 000000000..d4d35f4e7 --- /dev/null +++ b/src/common/wall_clock.cpp @@ -0,0 +1,92 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/uint128.h" +#include "common/wall_clock.h" + +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/cpu_detect.h" +#include "common/x64/native_clock.h" +#endif + +namespace Common { + +using base_timer = std::chrono::steady_clock; +using base_time_point = std::chrono::time_point<base_timer>; + +class StandardWallClock : public WallClock { +public: + StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency) + : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) { + start_time = base_timer::now(); + } + + std::chrono::nanoseconds GetTimeNS() override { + base_time_point current = base_timer::now(); + auto elapsed = current - start_time; + return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed); + } + + std::chrono::microseconds GetTimeUS() override { + base_time_point current = base_timer::now(); + auto elapsed = current - start_time; + return std::chrono::duration_cast<std::chrono::microseconds>(elapsed); + } + + std::chrono::milliseconds GetTimeMS() override { + base_time_point current = base_timer::now(); + auto elapsed = current - start_time; + return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed); + } + + u64 GetClockCycles() override { + std::chrono::nanoseconds time_now = GetTimeNS(); + const u128 temporary = + Common::Multiply64Into128(time_now.count(), emulated_clock_frequency); + return Common::Divide128On32(temporary, 1000000000).first; + } + + u64 GetCPUCycles() override { + std::chrono::nanoseconds time_now = GetTimeNS(); + const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency); + return Common::Divide128On32(temporary, 1000000000).first; + } + +private: + base_time_point start_time; +}; + +#ifdef ARCHITECTURE_x86_64 + +std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency, + u32 emulated_clock_frequency) { + const auto& caps = GetCPUCaps(); + u64 rtsc_frequency = 0; + if (caps.invariant_tsc) { + if (caps.base_frequency != 0) { + rtsc_frequency = static_cast<u64>(caps.base_frequency) * 1000000U; + } + if (rtsc_frequency == 0) { + rtsc_frequency = EstimateRDTSCFrequency(); + } + } + if (rtsc_frequency == 0) { + return std::make_unique<StandardWallClock>(emulated_cpu_frequency, + emulated_clock_frequency); + } else { + return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency, + rtsc_frequency); + } +} + +#else + +std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency, + u32 emulated_clock_frequency) { + return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency); +} + +#endif + +} // namespace Common diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h new file mode 100644 index 000000000..ed284cf50 --- /dev/null +++ b/src/common/wall_clock.h @@ -0,0 +1,51 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <memory> + +#include "common/common_types.h" + +namespace Common { + +class WallClock { +public: + /// Returns current wall time in nanoseconds + virtual std::chrono::nanoseconds GetTimeNS() = 0; + + /// Returns current wall time in microseconds + virtual std::chrono::microseconds GetTimeUS() = 0; + + /// Returns current wall time in milliseconds + virtual std::chrono::milliseconds GetTimeMS() = 0; + + /// Returns current wall time in emulated clock cycles + virtual u64 GetClockCycles() = 0; + + /// Returns current wall time in emulated cpu cycles + virtual u64 GetCPUCycles() = 0; + + /// Tells if the wall clock, uses the host CPU's hardware clock + bool IsNative() const { + return is_native; + } + +protected: + WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native) + : emulated_cpu_frequency{emulated_cpu_frequency}, + emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {} + + u64 emulated_cpu_frequency; + u64 emulated_clock_frequency; + +private: + bool is_native; +}; + +std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency, + u32 emulated_clock_frequency); + +} // namespace Common diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index f35dcb498..fccd2eee5 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -62,6 +62,17 @@ static CPUCaps Detect() { std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int)); std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int)); std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int)); + if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69) + caps.manufacturer = Manufacturer::Intel; + else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65) + caps.manufacturer = Manufacturer::AMD; + else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e) + caps.manufacturer = Manufacturer::Hygon; + else + caps.manufacturer = Manufacturer::Unknown; + + u32 family = {}; + u32 model = {}; __cpuid(cpu_id, 0x80000000); @@ -73,6 +84,14 @@ static CPUCaps Detect() { // Detect family and other miscellaneous features if (max_std_fn >= 1) { __cpuid(cpu_id, 0x00000001); + family = (cpu_id[0] >> 8) & 0xf; + model = (cpu_id[0] >> 4) & 0xf; + if (family == 0xf) { + family += (cpu_id[0] >> 20) & 0xff; + } + if (family >= 6) { + model += ((cpu_id[0] >> 16) & 0xf) << 4; + } if ((cpu_id[3] >> 25) & 1) caps.sse = true; @@ -135,6 +154,20 @@ static CPUCaps Detect() { caps.fma4 = true; } + if (max_ex_fn >= 0x80000007) { + __cpuid(cpu_id, 0x80000007); + if (cpu_id[3] & (1 << 8)) { + caps.invariant_tsc = true; + } + } + + if (max_std_fn >= 0x16) { + __cpuid(cpu_id, 0x16); + caps.base_frequency = cpu_id[0]; + caps.max_frequency = cpu_id[1]; + caps.bus_frequency = cpu_id[2]; + } + return caps; } diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index 7606c3f7b..e3b63302e 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h @@ -6,8 +6,16 @@ namespace Common { +enum class Manufacturer : u32 { + Intel = 0, + AMD = 1, + Hygon = 2, + Unknown = 3, +}; + /// x86/x64 CPU capabilities that may be detected by this module struct CPUCaps { + Manufacturer manufacturer; char cpu_string[0x21]; char brand_string[0x41]; bool sse; @@ -25,6 +33,10 @@ struct CPUCaps { bool fma; bool fma4; bool aes; + bool invariant_tsc; + u32 base_frequency; + u32 max_frequency; + u32 bus_frequency; }; /** diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp new file mode 100644 index 000000000..26d4d0ba6 --- /dev/null +++ b/src/common/x64/native_clock.cpp @@ -0,0 +1,95 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <thread> + +#ifdef _MSC_VER +#include <intrin.h> +#else +#include <x86intrin.h> +#endif + +#include "common/uint128.h" +#include "common/x64/native_clock.h" + +namespace Common { + +u64 EstimateRDTSCFrequency() { + const auto milli_10 = std::chrono::milliseconds{10}; + // get current time + _mm_mfence(); + const u64 tscStart = __rdtsc(); + const auto startTime = std::chrono::high_resolution_clock::now(); + // wait roughly 3 seconds + while (true) { + auto milli = std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::high_resolution_clock::now() - startTime); + if (milli.count() >= 3000) + break; + std::this_thread::sleep_for(milli_10); + } + const auto endTime = std::chrono::high_resolution_clock::now(); + _mm_mfence(); + const u64 tscEnd = __rdtsc(); + // calculate difference + const u64 timer_diff = + std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count(); + const u64 tsc_diff = tscEnd - tscStart; + const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff); + return tsc_freq; +} + +namespace X64 { +NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, + u64 rtsc_frequency) + : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{ + rtsc_frequency} { + _mm_mfence(); + last_measure = __rdtsc(); + accumulated_ticks = 0U; +} + +u64 NativeClock::GetRTSC() { + rtsc_serialize.lock(); + _mm_mfence(); + const u64 current_measure = __rdtsc(); + u64 diff = current_measure - last_measure; + diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0) + if (current_measure > last_measure) { + last_measure = current_measure; + } + accumulated_ticks += diff; + rtsc_serialize.unlock(); + return accumulated_ticks; +} + +std::chrono::nanoseconds NativeClock::GetTimeNS() { + const u64 rtsc_value = GetRTSC(); + return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)}; +} + +std::chrono::microseconds NativeClock::GetTimeUS() { + const u64 rtsc_value = GetRTSC(); + return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)}; +} + +std::chrono::milliseconds NativeClock::GetTimeMS() { + const u64 rtsc_value = GetRTSC(); + return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)}; +} + +u64 NativeClock::GetClockCycles() { + const u64 rtsc_value = GetRTSC(); + return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency); +} + +u64 NativeClock::GetCPUCycles() { + const u64 rtsc_value = GetRTSC(); + return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency); +} + +} // namespace X64 + +} // namespace Common diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h new file mode 100644 index 000000000..b58cf9f5a --- /dev/null +++ b/src/common/x64/native_clock.h @@ -0,0 +1,41 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> + +#include "common/spin_lock.h" +#include "common/wall_clock.h" + +namespace Common { + +namespace X64 { +class NativeClock : public WallClock { +public: + NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency); + + std::chrono::nanoseconds GetTimeNS() override; + + std::chrono::microseconds GetTimeUS() override; + + std::chrono::milliseconds GetTimeMS() override; + + u64 GetClockCycles() override; + + u64 GetCPUCycles() override; + +private: + u64 GetRTSC(); + + SpinLock rtsc_serialize{}; + u64 last_measure{}; + u64 accumulated_ticks{}; + u64 rtsc_frequency; +}; +} // namespace X64 + +u64 EstimateRDTSCFrequency(); + +} // namespace Common |