diff options
Diffstat (limited to '')
-rw-r--r-- | src/common/CMakeLists.txt | 13 | ||||
-rw-r--r-- | src/common/atomic_ops.cpp | 70 | ||||
-rw-r--r-- | src/common/atomic_ops.h | 17 | ||||
-rw-r--r-- | src/common/fiber.cpp | 222 | ||||
-rw-r--r-- | src/common/fiber.h | 92 | ||||
-rw-r--r-- | src/common/memory_detect.cpp | 23 | ||||
-rw-r--r-- | src/common/spin_lock.cpp | 54 | ||||
-rw-r--r-- | src/common/spin_lock.h | 26 | ||||
-rw-r--r-- | src/common/thread.cpp | 52 | ||||
-rw-r--r-- | src/common/thread.h | 13 | ||||
-rw-r--r-- | src/common/uint128.cpp | 26 | ||||
-rw-r--r-- | src/common/uint128.h | 3 | ||||
-rw-r--r-- | src/common/wall_clock.cpp | 91 | ||||
-rw-r--r-- | src/common/wall_clock.h | 53 | ||||
-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 | 103 | ||||
-rw-r--r-- | src/common/x64/native_clock.h | 48 |
18 files changed, 943 insertions, 8 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0a3e2f4d1..d120c8d3d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -98,6 +98,8 @@ add_library(common STATIC algorithm.h alignment.h assert.h + atomic_ops.cpp + atomic_ops.h detached_tasks.cpp detached_tasks.h bit_field.h @@ -110,6 +112,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 +147,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 +169,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 +181,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/atomic_ops.cpp b/src/common/atomic_ops.cpp new file mode 100644 index 000000000..1098e21ff --- /dev/null +++ b/src/common/atomic_ops.cpp @@ -0,0 +1,70 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "common/atomic_ops.h" + +#if _MSC_VER +#include <intrin.h> +#endif + +namespace Common { + +#if _MSC_VER + +bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) { + u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected); + return result == expected; +} + +bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) { + u16 result = _InterlockedCompareExchange16((short*)pointer, value, expected); + return result == expected; +} + +bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) { + u32 result = _InterlockedCompareExchange((long*)pointer, value, expected); + return result == expected; +} + +bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) { + u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, expected); + return result == expected; +} + +bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) { + return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0], + (__int64*)expected.data()) != 0; +} + +#else + +bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) { + unsigned __int128 value_a; + unsigned __int128 expected_a; + std::memcpy(&value_a, value.data(), sizeof(u128)); + std::memcpy(&expected_a, expected.data(), sizeof(u128)); + return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a); +} + +#endif + +} // namespace Common diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h new file mode 100644 index 000000000..e6181d521 --- /dev/null +++ b/src/common/atomic_ops.h @@ -0,0 +1,17 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Common { + +bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected); +bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected); +bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected); +bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected); +bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected); + +} // namespace Common diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp new file mode 100644 index 000000000..1c1d09ccb --- /dev/null +++ b/src/common/fiber.cpp @@ -0,0 +1,222 @@ +// 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; + alignas(64) std::array<u8, default_stack_size> rewind_stack; + u8* stack_limit; + 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/memory_detect.cpp b/src/common/memory_detect.cpp index 3fdc309a2..8cff6ec37 100644 --- a/src/common/memory_detect.cpp +++ b/src/common/memory_detect.cpp @@ -9,10 +9,12 @@ // clang-format on #else #include <sys/types.h> -#ifdef __APPLE__ +#if defined(__APPLE__) || defined(__FreeBSD__) #include <sys/sysctl.h> -#else +#elif defined(__linux__) #include <sys/sysinfo.h> +#else +#include <unistd.h> #endif #endif @@ -38,15 +40,26 @@ static MemoryInfo Detect() { // hw and vm are defined in sysctl.h // https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471 // sysctlbyname(const char *, void *, size_t *, void *, size_t); - sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, NULL, 0); - sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, NULL, 0); + sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, nullptr, 0); + sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0); mem_info.TotalPhysicalMemory = ramsize; mem_info.TotalSwapMemory = vmusage.xsu_total; -#else +#elif defined(__FreeBSD__) + u_long physmem, swap_total; + std::size_t sizeof_u_long = sizeof(u_long); + // sysctlbyname(const char *, void *, size_t *, const void *, size_t); + sysctlbyname("hw.physmem", &physmem, &sizeof_u_long, nullptr, 0); + sysctlbyname("vm.swap_total", &swap_total, &sizeof_u_long, nullptr, 0); + mem_info.TotalPhysicalMemory = physmem; + mem_info.TotalSwapMemory = swap_total; +#elif defined(__linux__) struct sysinfo meminfo; sysinfo(&meminfo); mem_info.TotalPhysicalMemory = meminfo.totalram; mem_info.TotalSwapMemory = meminfo.totalswap; +#else + mem_info.TotalPhysicalMemory = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE); + mem_info.TotalSwapMemory = 0; #endif return mem_info; diff --git a/src/common/spin_lock.cpp b/src/common/spin_lock.cpp new file mode 100644 index 000000000..c1524220f --- /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 ThreadPause() { +#if __x86_64__ + _mm_pause(); +#elif __aarch64__ && _MSC_VER + __yield(); +#elif __aarch64__ + asm("yield"); +#endif +} + +} // Anonymous namespace + +namespace Common { + +void SpinLock::lock() { + while (lck.test_and_set(std::memory_order_acquire)) { + ThreadPause(); + } +} + +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..1df5528c4 --- /dev/null +++ b/src/common/spin_lock.h @@ -0,0 +1,26 @@ +// 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 { + +/** + * SpinLock class + * a lock similar to mutex that forces a thread to spin wait instead calling the + * supervisor. Should be used on short sequences of code. + */ +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.cpp b/src/common/thread.cpp index 0cd2d10bf..8e5935e6a 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -25,6 +25,52 @@ namespace Common { +#ifdef _WIN32 + +void SetCurrentThreadPriority(ThreadPriority new_priority) { + auto handle = GetCurrentThread(); + int windows_priority = 0; + switch (new_priority) { + case ThreadPriority::Low: + windows_priority = THREAD_PRIORITY_BELOW_NORMAL; + break; + case ThreadPriority::Normal: + windows_priority = THREAD_PRIORITY_NORMAL; + break; + case ThreadPriority::High: + windows_priority = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case ThreadPriority::VeryHigh: + windows_priority = THREAD_PRIORITY_HIGHEST; + break; + default: + windows_priority = THREAD_PRIORITY_NORMAL; + break; + } + SetThreadPriority(handle, windows_priority); +} + +#else + +void SetCurrentThreadPriority(ThreadPriority new_priority) { + pthread_t this_thread = pthread_self(); + + s32 max_prio = sched_get_priority_max(SCHED_OTHER); + s32 min_prio = sched_get_priority_min(SCHED_OTHER); + u32 level = static_cast<u32>(new_priority) + 1; + + struct sched_param params; + if (max_prio > min_prio) { + params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; + } else { + params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; + } + + pthread_setschedparam(this_thread, SCHED_OTHER, ¶ms); +} + +#endif + #ifdef _MSC_VER // Sets the debugger-visible name of the current thread. @@ -70,6 +116,12 @@ void SetCurrentThreadName(const char* name) { } #endif +#if defined(_WIN32) +void SetCurrentThreadName(const char* name) { + // Do Nothing on MingW +} +#endif + #endif } // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index 2fc071685..52b359413 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; @@ -86,6 +86,15 @@ private: std::size_t generation = 0; // Incremented once each time the barrier is used }; +enum class ThreadPriority : u32 { + Low = 0, + Normal = 1, + High = 2, + VeryHigh = 3, +}; + +void SetCurrentThreadPriority(ThreadPriority new_priority); + void SetCurrentThreadName(const char* name); } // namespace Common 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..3afbdb898 --- /dev/null +++ b/src/common/wall_clock.cpp @@ -0,0 +1,91 @@ +// 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; + } + + void Pause(bool is_paused) override { + // Do nothing in this clock type. + } + +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) { + 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..367d72134 --- /dev/null +++ b/src/common/wall_clock.h @@ -0,0 +1,53 @@ +// 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; + + virtual void Pause(bool is_paused) = 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..424b39b1f --- /dev/null +++ b/src/common/x64/native_clock.cpp @@ -0,0 +1,103 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <mutex> +#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() { + std::scoped_lock scope{rtsc_serialize}; + _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; + /// The clock cannot be more precise than the guest timer, remove the lower bits + return accumulated_ticks & inaccuracy_mask; +} + +void NativeClock::Pause(bool is_paused) { + if (!is_paused) { + _mm_mfence(); + last_measure = __rdtsc(); + } +} + +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..891a3bbfd --- /dev/null +++ b/src/common/x64/native_clock.h @@ -0,0 +1,48 @@ +// 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; + + void Pause(bool is_paused) override; + +private: + u64 GetRTSC(); + + /// value used to reduce the native clocks accuracy as some apss rely on + /// undefined behavior where the level of accuracy in the clock shouldn't + /// be higher. + static constexpr u64 inaccuracy_mask = ~(0x400 - 1); + + SpinLock rtsc_serialize{}; + u64 last_measure{}; + u64 accumulated_ticks{}; + u64 rtsc_frequency; +}; +} // namespace X64 + +u64 EstimateRDTSCFrequency(); + +} // namespace Common |