summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/CMakeLists.txt13
-rw-r--r--src/common/atomic_ops.cpp70
-rw-r--r--src/common/atomic_ops.h17
-rw-r--r--src/common/fiber.cpp222
-rw-r--r--src/common/fiber.h92
-rw-r--r--src/common/spin_lock.cpp54
-rw-r--r--src/common/spin_lock.h26
-rw-r--r--src/common/thread.cpp52
-rw-r--r--src/common/thread.h13
-rw-r--r--src/common/uint128.cpp26
-rw-r--r--src/common/uint128.h3
-rw-r--r--src/common/wall_clock.cpp91
-rw-r--r--src/common/wall_clock.h53
-rw-r--r--src/common/x64/cpu_detect.cpp33
-rw-r--r--src/common/x64/cpu_detect.h12
-rw-r--r--src/common/x64/native_clock.cpp103
-rw-r--r--src/common/x64/native_clock.h48
17 files changed, 925 insertions, 3 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
98 algorithm.h 98 algorithm.h
99 alignment.h 99 alignment.h
100 assert.h 100 assert.h
101 atomic_ops.cpp
102 atomic_ops.h
101 detached_tasks.cpp 103 detached_tasks.cpp
102 detached_tasks.h 104 detached_tasks.h
103 bit_field.h 105 bit_field.h
@@ -110,6 +112,8 @@ add_library(common STATIC
110 common_types.h 112 common_types.h
111 dynamic_library.cpp 113 dynamic_library.cpp
112 dynamic_library.h 114 dynamic_library.h
115 fiber.cpp
116 fiber.h
113 file_util.cpp 117 file_util.cpp
114 file_util.h 118 file_util.h
115 hash.h 119 hash.h
@@ -143,6 +147,8 @@ add_library(common STATIC
143 scm_rev.cpp 147 scm_rev.cpp
144 scm_rev.h 148 scm_rev.h
145 scope_exit.h 149 scope_exit.h
150 spin_lock.cpp
151 spin_lock.h
146 string_util.cpp 152 string_util.cpp
147 string_util.h 153 string_util.h
148 swap.h 154 swap.h
@@ -163,6 +169,8 @@ add_library(common STATIC
163 vector_math.h 169 vector_math.h
164 virtual_buffer.cpp 170 virtual_buffer.cpp
165 virtual_buffer.h 171 virtual_buffer.h
172 wall_clock.cpp
173 wall_clock.h
166 web_result.h 174 web_result.h
167 zstd_compression.cpp 175 zstd_compression.cpp
168 zstd_compression.h 176 zstd_compression.h
@@ -173,12 +181,15 @@ if(ARCHITECTURE_x86_64)
173 PRIVATE 181 PRIVATE
174 x64/cpu_detect.cpp 182 x64/cpu_detect.cpp
175 x64/cpu_detect.h 183 x64/cpu_detect.h
184 x64/native_clock.cpp
185 x64/native_clock.h
176 x64/xbyak_abi.h 186 x64/xbyak_abi.h
177 x64/xbyak_util.h 187 x64/xbyak_util.h
178 ) 188 )
179endif() 189endif()
180 190
181create_target_directory_groups(common) 191create_target_directory_groups(common)
192find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
182 193
183target_link_libraries(common PUBLIC Boost::boost fmt::fmt microprofile) 194target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
184target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) 195target_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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6
7#include "common/atomic_ops.h"
8
9#if _MSC_VER
10#include <intrin.h>
11#endif
12
13namespace Common {
14
15#if _MSC_VER
16
17bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
18 u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected);
19 return result == expected;
20}
21
22bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
23 u16 result = _InterlockedCompareExchange16((short*)pointer, value, expected);
24 return result == expected;
25}
26
27bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
28 u32 result = _InterlockedCompareExchange((long*)pointer, value, expected);
29 return result == expected;
30}
31
32bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
33 u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, expected);
34 return result == expected;
35}
36
37bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
38 return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0],
39 (__int64*)expected.data()) != 0;
40}
41
42#else
43
44bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
45 return __sync_bool_compare_and_swap(pointer, expected, value);
46}
47
48bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
49 return __sync_bool_compare_and_swap(pointer, expected, value);
50}
51
52bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
53 return __sync_bool_compare_and_swap(pointer, expected, value);
54}
55
56bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
57 return __sync_bool_compare_and_swap(pointer, expected, value);
58}
59
60bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
61 unsigned __int128 value_a;
62 unsigned __int128 expected_a;
63 std::memcpy(&value_a, value.data(), sizeof(u128));
64 std::memcpy(&expected_a, expected.data(), sizeof(u128));
65 return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
66}
67
68#endif
69
70} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_types.h"
8
9namespace Common {
10
11bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected);
12bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected);
13bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected);
14bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected);
15bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected);
16
17} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "common/fiber.h"
7#if defined(_WIN32) || defined(WIN32)
8#include <windows.h>
9#else
10#include <boost/context/detail/fcontext.hpp>
11#endif
12
13namespace Common {
14
15constexpr std::size_t default_stack_size = 256 * 1024; // 256kb
16
17#if defined(_WIN32) || defined(WIN32)
18
19struct Fiber::FiberImpl {
20 LPVOID handle = nullptr;
21 LPVOID rewind_handle = nullptr;
22};
23
24void Fiber::Start() {
25 ASSERT(previous_fiber != nullptr);
26 previous_fiber->guard.unlock();
27 previous_fiber.reset();
28 entry_point(start_parameter);
29 UNREACHABLE();
30}
31
32void Fiber::OnRewind() {
33 ASSERT(impl->handle != nullptr);
34 DeleteFiber(impl->handle);
35 impl->handle = impl->rewind_handle;
36 impl->rewind_handle = nullptr;
37 rewind_point(rewind_parameter);
38 UNREACHABLE();
39}
40
41void Fiber::FiberStartFunc(void* fiber_parameter) {
42 auto fiber = static_cast<Fiber*>(fiber_parameter);
43 fiber->Start();
44}
45
46void Fiber::RewindStartFunc(void* fiber_parameter) {
47 auto fiber = static_cast<Fiber*>(fiber_parameter);
48 fiber->OnRewind();
49}
50
51Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
52 : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
53 impl = std::make_unique<FiberImpl>();
54 impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this);
55}
56
57Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
58
59Fiber::~Fiber() {
60 if (released) {
61 return;
62 }
63 // Make sure the Fiber is not being used
64 const bool locked = guard.try_lock();
65 ASSERT_MSG(locked, "Destroying a fiber that's still running");
66 if (locked) {
67 guard.unlock();
68 }
69 DeleteFiber(impl->handle);
70}
71
72void Fiber::Exit() {
73 ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
74 if (!is_thread_fiber) {
75 return;
76 }
77 ConvertFiberToThread();
78 guard.unlock();
79 released = true;
80}
81
82void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
83 rewind_point = std::move(rewind_func);
84 rewind_parameter = start_parameter;
85}
86
87void Fiber::Rewind() {
88 ASSERT(rewind_point);
89 ASSERT(impl->rewind_handle == nullptr);
90 impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
91 SwitchToFiber(impl->rewind_handle);
92}
93
94void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
95 ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
96 ASSERT_MSG(to != nullptr, "Next fiber is null!");
97 to->guard.lock();
98 to->previous_fiber = from;
99 SwitchToFiber(to->impl->handle);
100 ASSERT(from->previous_fiber != nullptr);
101 from->previous_fiber->guard.unlock();
102 from->previous_fiber.reset();
103}
104
105std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
106 std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
107 fiber->guard.lock();
108 fiber->impl->handle = ConvertThreadToFiber(nullptr);
109 fiber->is_thread_fiber = true;
110 return fiber;
111}
112
113#else
114
115struct Fiber::FiberImpl {
116 alignas(64) std::array<u8, default_stack_size> stack;
117 alignas(64) std::array<u8, default_stack_size> rewind_stack;
118 u8* stack_limit;
119 u8* rewind_stack_limit;
120 boost::context::detail::fcontext_t context;
121 boost::context::detail::fcontext_t rewind_context;
122};
123
124void Fiber::Start(boost::context::detail::transfer_t& transfer) {
125 ASSERT(previous_fiber != nullptr);
126 previous_fiber->impl->context = transfer.fctx;
127 previous_fiber->guard.unlock();
128 previous_fiber.reset();
129 entry_point(start_parameter);
130 UNREACHABLE();
131}
132
133void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) {
134 ASSERT(impl->context != nullptr);
135 impl->context = impl->rewind_context;
136 impl->rewind_context = nullptr;
137 u8* tmp = impl->stack_limit;
138 impl->stack_limit = impl->rewind_stack_limit;
139 impl->rewind_stack_limit = tmp;
140 rewind_point(rewind_parameter);
141 UNREACHABLE();
142}
143
144void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
145 auto fiber = static_cast<Fiber*>(transfer.data);
146 fiber->Start(transfer);
147}
148
149void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
150 auto fiber = static_cast<Fiber*>(transfer.data);
151 fiber->OnRewind(transfer);
152}
153
154Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
155 : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
156 impl = std::make_unique<FiberImpl>();
157 impl->stack_limit = impl->stack.data();
158 impl->rewind_stack_limit = impl->rewind_stack.data();
159 u8* stack_base = impl->stack_limit + default_stack_size;
160 impl->context =
161 boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
162}
163
164void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
165 rewind_point = std::move(rewind_func);
166 rewind_parameter = start_parameter;
167}
168
169Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
170
171Fiber::~Fiber() {
172 if (released) {
173 return;
174 }
175 // Make sure the Fiber is not being used
176 const bool locked = guard.try_lock();
177 ASSERT_MSG(locked, "Destroying a fiber that's still running");
178 if (locked) {
179 guard.unlock();
180 }
181}
182
183void Fiber::Exit() {
184
185 ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
186 if (!is_thread_fiber) {
187 return;
188 }
189 guard.unlock();
190 released = true;
191}
192
193void Fiber::Rewind() {
194 ASSERT(rewind_point);
195 ASSERT(impl->rewind_context == nullptr);
196 u8* stack_base = impl->rewind_stack_limit + default_stack_size;
197 impl->rewind_context =
198 boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc);
199 boost::context::detail::jump_fcontext(impl->rewind_context, this);
200}
201
202void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
203 ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
204 ASSERT_MSG(to != nullptr, "Next fiber is null!");
205 to->guard.lock();
206 to->previous_fiber = from;
207 auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
208 ASSERT(from->previous_fiber != nullptr);
209 from->previous_fiber->impl->context = transfer.fctx;
210 from->previous_fiber->guard.unlock();
211 from->previous_fiber.reset();
212}
213
214std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
215 std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
216 fiber->guard.lock();
217 fiber->is_thread_fiber = true;
218 return fiber;
219}
220
221#endif
222} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9
10#include "common/common_types.h"
11#include "common/spin_lock.h"
12
13#if !defined(_WIN32) && !defined(WIN32)
14namespace boost::context::detail {
15struct transfer_t;
16}
17#endif
18
19namespace Common {
20
21/**
22 * Fiber class
23 * a fiber is a userspace thread with it's own context. They can be used to
24 * implement coroutines, emulated threading systems and certain asynchronous
25 * patterns.
26 *
27 * This class implements fibers at a low level, thus allowing greater freedom
28 * to implement such patterns. This fiber class is 'threadsafe' only one fiber
29 * can be running at a time and threads will be locked while trying to yield to
30 * a running fiber until it yields. WARNING exchanging two running fibers between
31 * threads will cause a deadlock. In order to prevent a deadlock, each thread should
32 * have an intermediary fiber, you switch to the intermediary fiber of the current
33 * thread and then from it switch to the expected fiber. This way you can exchange
34 * 2 fibers within 2 different threads.
35 */
36class Fiber {
37public:
38 Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter);
39 ~Fiber();
40
41 Fiber(const Fiber&) = delete;
42 Fiber& operator=(const Fiber&) = delete;
43
44 Fiber(Fiber&&) = default;
45 Fiber& operator=(Fiber&&) = default;
46
47 /// Yields control from Fiber 'from' to Fiber 'to'
48 /// Fiber 'from' must be the currently running fiber.
49 static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to);
50 static std::shared_ptr<Fiber> ThreadToFiber();
51
52 void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter);
53
54 void Rewind();
55
56 /// Only call from main thread's fiber
57 void Exit();
58
59 /// Changes the start parameter of the fiber. Has no effect if the fiber already started
60 void SetStartParameter(void* new_parameter) {
61 start_parameter = new_parameter;
62 }
63
64private:
65 Fiber();
66
67#if defined(_WIN32) || defined(WIN32)
68 void OnRewind();
69 void Start();
70 static void FiberStartFunc(void* fiber_parameter);
71 static void RewindStartFunc(void* fiber_parameter);
72#else
73 void OnRewind(boost::context::detail::transfer_t& transfer);
74 void Start(boost::context::detail::transfer_t& transfer);
75 static void FiberStartFunc(boost::context::detail::transfer_t transfer);
76 static void RewindStartFunc(boost::context::detail::transfer_t transfer);
77#endif
78
79 struct FiberImpl;
80
81 SpinLock guard{};
82 std::function<void(void*)> entry_point;
83 std::function<void(void*)> rewind_point;
84 void* rewind_parameter{};
85 void* start_parameter{};
86 std::shared_ptr<Fiber> previous_fiber;
87 std::unique_ptr<FiberImpl> impl;
88 bool is_thread_fiber{};
89 bool released{};
90};
91
92} // namespace Common
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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/spin_lock.h"
6
7#if _MSC_VER
8#include <intrin.h>
9#if _M_AMD64
10#define __x86_64__ 1
11#endif
12#if _M_ARM64
13#define __aarch64__ 1
14#endif
15#else
16#if __x86_64__
17#include <xmmintrin.h>
18#endif
19#endif
20
21namespace {
22
23void ThreadPause() {
24#if __x86_64__
25 _mm_pause();
26#elif __aarch64__ && _MSC_VER
27 __yield();
28#elif __aarch64__
29 asm("yield");
30#endif
31}
32
33} // Anonymous namespace
34
35namespace Common {
36
37void SpinLock::lock() {
38 while (lck.test_and_set(std::memory_order_acquire)) {
39 ThreadPause();
40 }
41}
42
43void SpinLock::unlock() {
44 lck.clear(std::memory_order_release);
45}
46
47bool SpinLock::try_lock() {
48 if (lck.test_and_set(std::memory_order_acquire)) {
49 return false;
50 }
51 return true;
52}
53
54} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8
9namespace Common {
10
11/**
12 * SpinLock class
13 * a lock similar to mutex that forces a thread to spin wait instead calling the
14 * supervisor. Should be used on short sequences of code.
15 */
16class SpinLock {
17public:
18 void lock();
19 void unlock();
20 bool try_lock();
21
22private:
23 std::atomic_flag lck = ATOMIC_FLAG_INIT;
24};
25
26} // 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 @@
25 25
26namespace Common { 26namespace Common {
27 27
28#ifdef _WIN32
29
30void SetCurrentThreadPriority(ThreadPriority new_priority) {
31 auto handle = GetCurrentThread();
32 int windows_priority = 0;
33 switch (new_priority) {
34 case ThreadPriority::Low:
35 windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
36 break;
37 case ThreadPriority::Normal:
38 windows_priority = THREAD_PRIORITY_NORMAL;
39 break;
40 case ThreadPriority::High:
41 windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
42 break;
43 case ThreadPriority::VeryHigh:
44 windows_priority = THREAD_PRIORITY_HIGHEST;
45 break;
46 default:
47 windows_priority = THREAD_PRIORITY_NORMAL;
48 break;
49 }
50 SetThreadPriority(handle, windows_priority);
51}
52
53#else
54
55void SetCurrentThreadPriority(ThreadPriority new_priority) {
56 pthread_t this_thread = pthread_self();
57
58 s32 max_prio = sched_get_priority_max(SCHED_OTHER);
59 s32 min_prio = sched_get_priority_min(SCHED_OTHER);
60 u32 level = static_cast<u32>(new_priority) + 1;
61
62 struct sched_param params;
63 if (max_prio > min_prio) {
64 params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
65 } else {
66 params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
67 }
68
69 pthread_setschedparam(this_thread, SCHED_OTHER, &params);
70}
71
72#endif
73
28#ifdef _MSC_VER 74#ifdef _MSC_VER
29 75
30// Sets the debugger-visible name of the current thread. 76// Sets the debugger-visible name of the current thread.
@@ -70,6 +116,12 @@ void SetCurrentThreadName(const char* name) {
70} 116}
71#endif 117#endif
72 118
119#if defined(_WIN32)
120void SetCurrentThreadName(const char* name) {
121 // Do Nothing on MingW
122}
123#endif
124
73#endif 125#endif
74 126
75} // namespace Common 127} // 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 @@
9#include <cstddef> 9#include <cstddef>
10#include <mutex> 10#include <mutex>
11#include <thread> 11#include <thread>
12#include "common/common_types.h"
12 13
13namespace Common { 14namespace Common {
14 15
@@ -28,8 +29,7 @@ public:
28 is_set = false; 29 is_set = false;
29 } 30 }
30 31
31 template <class Duration> 32 bool WaitFor(const std::chrono::nanoseconds& time) {
32 bool WaitFor(const std::chrono::duration<Duration>& time) {
33 std::unique_lock lk{mutex}; 33 std::unique_lock lk{mutex};
34 if (!condvar.wait_for(lk, time, [this] { return is_set; })) 34 if (!condvar.wait_for(lk, time, [this] { return is_set; }))
35 return false; 35 return false;
@@ -86,6 +86,15 @@ private:
86 std::size_t generation = 0; // Incremented once each time the barrier is used 86 std::size_t generation = 0; // Incremented once each time the barrier is used
87}; 87};
88 88
89enum class ThreadPriority : u32 {
90 Low = 0,
91 Normal = 1,
92 High = 2,
93 VeryHigh = 3,
94};
95
96void SetCurrentThreadPriority(ThreadPriority new_priority);
97
89void SetCurrentThreadName(const char* name); 98void SetCurrentThreadName(const char* name);
90 99
91} // namespace Common 100} // 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 @@
6#include <intrin.h> 6#include <intrin.h>
7 7
8#pragma intrinsic(_umul128) 8#pragma intrinsic(_umul128)
9#pragma intrinsic(_udiv128)
9#endif 10#endif
10#include <cstring> 11#include <cstring>
11#include "common/uint128.h" 12#include "common/uint128.h"
12 13
13namespace Common { 14namespace Common {
14 15
16#ifdef _MSC_VER
17
18u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
19 u128 r{};
20 r[0] = _umul128(a, b, &r[1]);
21 u64 remainder;
22#if _MSC_VER < 1923
23 return udiv128(r[1], r[0], d, &remainder);
24#else
25 return _udiv128(r[1], r[0], d, &remainder);
26#endif
27}
28
29#else
30
31u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
32 const u64 diva = a / d;
33 const u64 moda = a % d;
34 const u64 divb = b / d;
35 const u64 modb = b % d;
36 return diva * b + moda * divb + moda * modb / d;
37}
38
39#endif
40
15u128 Multiply64Into128(u64 a, u64 b) { 41u128 Multiply64Into128(u64 a, u64 b) {
16 u128 result; 42 u128 result;
17#ifdef _MSC_VER 43#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 @@
9 9
10namespace Common { 10namespace Common {
11 11
12// This function multiplies 2 u64 values and divides it by a u64 value.
13u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
14
12// This function multiplies 2 u64 values and produces a u128 value; 15// This function multiplies 2 u64 values and produces a u128 value;
13u128 Multiply64Into128(u64 a, u64 b); 16u128 Multiply64Into128(u64 a, u64 b);
14 17
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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/uint128.h"
6#include "common/wall_clock.h"
7
8#ifdef ARCHITECTURE_x86_64
9#include "common/x64/cpu_detect.h"
10#include "common/x64/native_clock.h"
11#endif
12
13namespace Common {
14
15using base_timer = std::chrono::steady_clock;
16using base_time_point = std::chrono::time_point<base_timer>;
17
18class StandardWallClock : public WallClock {
19public:
20 StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency)
21 : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) {
22 start_time = base_timer::now();
23 }
24
25 std::chrono::nanoseconds GetTimeNS() override {
26 base_time_point current = base_timer::now();
27 auto elapsed = current - start_time;
28 return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
29 }
30
31 std::chrono::microseconds GetTimeUS() override {
32 base_time_point current = base_timer::now();
33 auto elapsed = current - start_time;
34 return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
35 }
36
37 std::chrono::milliseconds GetTimeMS() override {
38 base_time_point current = base_timer::now();
39 auto elapsed = current - start_time;
40 return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
41 }
42
43 u64 GetClockCycles() override {
44 std::chrono::nanoseconds time_now = GetTimeNS();
45 const u128 temporary =
46 Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
47 return Common::Divide128On32(temporary, 1000000000).first;
48 }
49
50 u64 GetCPUCycles() override {
51 std::chrono::nanoseconds time_now = GetTimeNS();
52 const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
53 return Common::Divide128On32(temporary, 1000000000).first;
54 }
55
56 void Pause(bool is_paused) override {
57 // Do nothing in this clock type.
58 }
59
60private:
61 base_time_point start_time;
62};
63
64#ifdef ARCHITECTURE_x86_64
65
66std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
67 u32 emulated_clock_frequency) {
68 const auto& caps = GetCPUCaps();
69 u64 rtsc_frequency = 0;
70 if (caps.invariant_tsc) {
71 rtsc_frequency = EstimateRDTSCFrequency();
72 }
73 if (rtsc_frequency == 0) {
74 return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
75 emulated_clock_frequency);
76 } else {
77 return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
78 rtsc_frequency);
79 }
80}
81
82#else
83
84std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
85 u32 emulated_clock_frequency) {
86 return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
87}
88
89#endif
90
91} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <chrono>
8#include <memory>
9
10#include "common/common_types.h"
11
12namespace Common {
13
14class WallClock {
15public:
16 /// Returns current wall time in nanoseconds
17 virtual std::chrono::nanoseconds GetTimeNS() = 0;
18
19 /// Returns current wall time in microseconds
20 virtual std::chrono::microseconds GetTimeUS() = 0;
21
22 /// Returns current wall time in milliseconds
23 virtual std::chrono::milliseconds GetTimeMS() = 0;
24
25 /// Returns current wall time in emulated clock cycles
26 virtual u64 GetClockCycles() = 0;
27
28 /// Returns current wall time in emulated cpu cycles
29 virtual u64 GetCPUCycles() = 0;
30
31 virtual void Pause(bool is_paused) = 0;
32
33 /// Tells if the wall clock, uses the host CPU's hardware clock
34 bool IsNative() const {
35 return is_native;
36 }
37
38protected:
39 WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
40 : emulated_cpu_frequency{emulated_cpu_frequency},
41 emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
42
43 u64 emulated_cpu_frequency;
44 u64 emulated_clock_frequency;
45
46private:
47 bool is_native;
48};
49
50std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
51 u32 emulated_clock_frequency);
52
53} // 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() {
62 std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int)); 62 std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int));
63 std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int)); 63 std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int));
64 std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int)); 64 std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int));
65 if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69)
66 caps.manufacturer = Manufacturer::Intel;
67 else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65)
68 caps.manufacturer = Manufacturer::AMD;
69 else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e)
70 caps.manufacturer = Manufacturer::Hygon;
71 else
72 caps.manufacturer = Manufacturer::Unknown;
73
74 u32 family = {};
75 u32 model = {};
65 76
66 __cpuid(cpu_id, 0x80000000); 77 __cpuid(cpu_id, 0x80000000);
67 78
@@ -73,6 +84,14 @@ static CPUCaps Detect() {
73 // Detect family and other miscellaneous features 84 // Detect family and other miscellaneous features
74 if (max_std_fn >= 1) { 85 if (max_std_fn >= 1) {
75 __cpuid(cpu_id, 0x00000001); 86 __cpuid(cpu_id, 0x00000001);
87 family = (cpu_id[0] >> 8) & 0xf;
88 model = (cpu_id[0] >> 4) & 0xf;
89 if (family == 0xf) {
90 family += (cpu_id[0] >> 20) & 0xff;
91 }
92 if (family >= 6) {
93 model += ((cpu_id[0] >> 16) & 0xf) << 4;
94 }
76 95
77 if ((cpu_id[3] >> 25) & 1) 96 if ((cpu_id[3] >> 25) & 1)
78 caps.sse = true; 97 caps.sse = true;
@@ -135,6 +154,20 @@ static CPUCaps Detect() {
135 caps.fma4 = true; 154 caps.fma4 = true;
136 } 155 }
137 156
157 if (max_ex_fn >= 0x80000007) {
158 __cpuid(cpu_id, 0x80000007);
159 if (cpu_id[3] & (1 << 8)) {
160 caps.invariant_tsc = true;
161 }
162 }
163
164 if (max_std_fn >= 0x16) {
165 __cpuid(cpu_id, 0x16);
166 caps.base_frequency = cpu_id[0];
167 caps.max_frequency = cpu_id[1];
168 caps.bus_frequency = cpu_id[2];
169 }
170
138 return caps; 171 return caps;
139} 172}
140 173
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 @@
6 6
7namespace Common { 7namespace Common {
8 8
9enum class Manufacturer : u32 {
10 Intel = 0,
11 AMD = 1,
12 Hygon = 2,
13 Unknown = 3,
14};
15
9/// x86/x64 CPU capabilities that may be detected by this module 16/// x86/x64 CPU capabilities that may be detected by this module
10struct CPUCaps { 17struct CPUCaps {
18 Manufacturer manufacturer;
11 char cpu_string[0x21]; 19 char cpu_string[0x21];
12 char brand_string[0x41]; 20 char brand_string[0x41];
13 bool sse; 21 bool sse;
@@ -25,6 +33,10 @@ struct CPUCaps {
25 bool fma; 33 bool fma;
26 bool fma4; 34 bool fma4;
27 bool aes; 35 bool aes;
36 bool invariant_tsc;
37 u32 base_frequency;
38 u32 max_frequency;
39 u32 bus_frequency;
28}; 40};
29 41
30/** 42/**
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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <mutex>
7#include <thread>
8
9#ifdef _MSC_VER
10#include <intrin.h>
11#else
12#include <x86intrin.h>
13#endif
14
15#include "common/uint128.h"
16#include "common/x64/native_clock.h"
17
18namespace Common {
19
20u64 EstimateRDTSCFrequency() {
21 const auto milli_10 = std::chrono::milliseconds{10};
22 // get current time
23 _mm_mfence();
24 const u64 tscStart = __rdtsc();
25 const auto startTime = std::chrono::high_resolution_clock::now();
26 // wait roughly 3 seconds
27 while (true) {
28 auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
29 std::chrono::high_resolution_clock::now() - startTime);
30 if (milli.count() >= 3000)
31 break;
32 std::this_thread::sleep_for(milli_10);
33 }
34 const auto endTime = std::chrono::high_resolution_clock::now();
35 _mm_mfence();
36 const u64 tscEnd = __rdtsc();
37 // calculate difference
38 const u64 timer_diff =
39 std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
40 const u64 tsc_diff = tscEnd - tscStart;
41 const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
42 return tsc_freq;
43}
44
45namespace X64 {
46NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
47 u64 rtsc_frequency)
48 : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
49 rtsc_frequency} {
50 _mm_mfence();
51 last_measure = __rdtsc();
52 accumulated_ticks = 0U;
53}
54
55u64 NativeClock::GetRTSC() {
56 std::scoped_lock scope{rtsc_serialize};
57 _mm_mfence();
58 const u64 current_measure = __rdtsc();
59 u64 diff = current_measure - last_measure;
60 diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
61 if (current_measure > last_measure) {
62 last_measure = current_measure;
63 }
64 accumulated_ticks += diff;
65 /// The clock cannot be more precise than the guest timer, remove the lower bits
66 return accumulated_ticks & inaccuracy_mask;
67}
68
69void NativeClock::Pause(bool is_paused) {
70 if (!is_paused) {
71 _mm_mfence();
72 last_measure = __rdtsc();
73 }
74}
75
76std::chrono::nanoseconds NativeClock::GetTimeNS() {
77 const u64 rtsc_value = GetRTSC();
78 return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
79}
80
81std::chrono::microseconds NativeClock::GetTimeUS() {
82 const u64 rtsc_value = GetRTSC();
83 return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
84}
85
86std::chrono::milliseconds NativeClock::GetTimeMS() {
87 const u64 rtsc_value = GetRTSC();
88 return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
89}
90
91u64 NativeClock::GetClockCycles() {
92 const u64 rtsc_value = GetRTSC();
93 return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
94}
95
96u64 NativeClock::GetCPUCycles() {
97 const u64 rtsc_value = GetRTSC();
98 return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
99}
100
101} // namespace X64
102
103} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <optional>
8
9#include "common/spin_lock.h"
10#include "common/wall_clock.h"
11
12namespace Common {
13
14namespace X64 {
15class NativeClock : public WallClock {
16public:
17 NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency);
18
19 std::chrono::nanoseconds GetTimeNS() override;
20
21 std::chrono::microseconds GetTimeUS() override;
22
23 std::chrono::milliseconds GetTimeMS() override;
24
25 u64 GetClockCycles() override;
26
27 u64 GetCPUCycles() override;
28
29 void Pause(bool is_paused) override;
30
31private:
32 u64 GetRTSC();
33
34 /// value used to reduce the native clocks accuracy as some apss rely on
35 /// undefined behavior where the level of accuracy in the clock shouldn't
36 /// be higher.
37 static constexpr u64 inaccuracy_mask = ~(0x400 - 1);
38
39 SpinLock rtsc_serialize{};
40 u64 last_measure{};
41 u64 accumulated_ticks{};
42 u64 rtsc_frequency;
43};
44} // namespace X64
45
46u64 EstimateRDTSCFrequency();
47
48} // namespace Common