summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt11
-rw-r--r--src/common/fiber.cpp226
-rw-r--r--src/common/fiber.h92
-rw-r--r--src/common/spin_lock.cpp54
-rw-r--r--src/common/spin_lock.h21
-rw-r--r--src/common/thread.h4
-rw-r--r--src/common/uint128.cpp26
-rw-r--r--src/common/uint128.h3
-rw-r--r--src/common/wall_clock.cpp92
-rw-r--r--src/common/wall_clock.h51
-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.cpp95
-rw-r--r--src/common/x64/native_clock.h41
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/core_timing_util.cpp15
-rw-r--r--src/core/core_timing_util.h3
-rw-r--r--src/core/host_timing.cpp206
-rw-r--r--src/core/host_timing.h160
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/common/fibers.cpp358
-rw-r--r--src/tests/core/host_timing.cpp142
22 files changed, 1646 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
110 common_types.h 110 common_types.h
111 dynamic_library.cpp 111 dynamic_library.cpp
112 dynamic_library.h 112 dynamic_library.h
113 fiber.cpp
114 fiber.h
113 file_util.cpp 115 file_util.cpp
114 file_util.h 116 file_util.h
115 hash.h 117 hash.h
@@ -143,6 +145,8 @@ add_library(common STATIC
143 scm_rev.cpp 145 scm_rev.cpp
144 scm_rev.h 146 scm_rev.h
145 scope_exit.h 147 scope_exit.h
148 spin_lock.cpp
149 spin_lock.h
146 string_util.cpp 150 string_util.cpp
147 string_util.h 151 string_util.h
148 swap.h 152 swap.h
@@ -163,6 +167,8 @@ add_library(common STATIC
163 vector_math.h 167 vector_math.h
164 virtual_buffer.cpp 168 virtual_buffer.cpp
165 virtual_buffer.h 169 virtual_buffer.h
170 wall_clock.cpp
171 wall_clock.h
166 web_result.h 172 web_result.h
167 zstd_compression.cpp 173 zstd_compression.cpp
168 zstd_compression.h 174 zstd_compression.h
@@ -173,12 +179,15 @@ if(ARCHITECTURE_x86_64)
173 PRIVATE 179 PRIVATE
174 x64/cpu_detect.cpp 180 x64/cpu_detect.cpp
175 x64/cpu_detect.h 181 x64/cpu_detect.h
182 x64/native_clock.cpp
183 x64/native_clock.h
176 x64/xbyak_abi.h 184 x64/xbyak_abi.h
177 x64/xbyak_util.h 185 x64/xbyak_util.h
178 ) 186 )
179endif() 187endif()
180 188
181create_target_directory_groups(common) 189create_target_directory_groups(common)
190find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
182 191
183target_link_libraries(common PUBLIC Boost::boost fmt::fmt microprofile) 192target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
184target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) 193target_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 @@
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() {
58 impl = std::make_unique<FiberImpl>();
59}
60
61Fiber::~Fiber() {
62 if (released) {
63 return;
64 }
65 // Make sure the Fiber is not being used
66 const bool locked = guard.try_lock();
67 ASSERT_MSG(locked, "Destroying a fiber that's still running");
68 if (locked) {
69 guard.unlock();
70 }
71 DeleteFiber(impl->handle);
72}
73
74void Fiber::Exit() {
75 ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
76 if (!is_thread_fiber) {
77 return;
78 }
79 ConvertFiberToThread();
80 guard.unlock();
81 released = true;
82}
83
84void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
85 rewind_point = std::move(rewind_func);
86 rewind_parameter = start_parameter;
87}
88
89void Fiber::Rewind() {
90 ASSERT(rewind_point);
91 ASSERT(impl->rewind_handle == nullptr);
92 impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
93 SwitchToFiber(impl->rewind_handle);
94}
95
96void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
97 ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
98 ASSERT_MSG(to != nullptr, "Next fiber is null!");
99 to->guard.lock();
100 to->previous_fiber = from;
101 SwitchToFiber(to->impl->handle);
102 ASSERT(from->previous_fiber != nullptr);
103 from->previous_fiber->guard.unlock();
104 from->previous_fiber.reset();
105}
106
107std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
108 std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
109 fiber->guard.lock();
110 fiber->impl->handle = ConvertThreadToFiber(nullptr);
111 fiber->is_thread_fiber = true;
112 return fiber;
113}
114
115#else
116
117struct Fiber::FiberImpl {
118 alignas(64) std::array<u8, default_stack_size> stack;
119 u8* stack_limit;
120 alignas(64) std::array<u8, default_stack_size> rewind_stack;
121 u8* rewind_stack_limit;
122 boost::context::detail::fcontext_t context;
123 boost::context::detail::fcontext_t rewind_context;
124};
125
126void Fiber::Start(boost::context::detail::transfer_t& transfer) {
127 ASSERT(previous_fiber != nullptr);
128 previous_fiber->impl->context = transfer.fctx;
129 previous_fiber->guard.unlock();
130 previous_fiber.reset();
131 entry_point(start_parameter);
132 UNREACHABLE();
133}
134
135void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) {
136 ASSERT(impl->context != nullptr);
137 impl->context = impl->rewind_context;
138 impl->rewind_context = nullptr;
139 u8* tmp = impl->stack_limit;
140 impl->stack_limit = impl->rewind_stack_limit;
141 impl->rewind_stack_limit = tmp;
142 rewind_point(rewind_parameter);
143 UNREACHABLE();
144}
145
146void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
147 auto fiber = static_cast<Fiber*>(transfer.data);
148 fiber->Start(transfer);
149}
150
151void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
152 auto fiber = static_cast<Fiber*>(transfer.data);
153 fiber->OnRewind(transfer);
154}
155
156Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
157 : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
158 impl = std::make_unique<FiberImpl>();
159 impl->stack_limit = impl->stack.data();
160 impl->rewind_stack_limit = impl->rewind_stack.data();
161 u8* stack_base = impl->stack_limit + default_stack_size;
162 impl->context =
163 boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
164}
165
166void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
167 rewind_point = std::move(rewind_func);
168 rewind_parameter = start_parameter;
169}
170
171Fiber::Fiber() {
172 impl = std::make_unique<FiberImpl>();
173}
174
175Fiber::~Fiber() {
176 if (released) {
177 return;
178 }
179 // Make sure the Fiber is not being used
180 const bool locked = guard.try_lock();
181 ASSERT_MSG(locked, "Destroying a fiber that's still running");
182 if (locked) {
183 guard.unlock();
184 }
185}
186
187void Fiber::Exit() {
188
189 ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
190 if (!is_thread_fiber) {
191 return;
192 }
193 guard.unlock();
194 released = true;
195}
196
197void Fiber::Rewind() {
198 ASSERT(rewind_point);
199 ASSERT(impl->rewind_context == nullptr);
200 u8* stack_base = impl->rewind_stack_limit + default_stack_size;
201 impl->rewind_context =
202 boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc);
203 boost::context::detail::jump_fcontext(impl->rewind_context, this);
204}
205
206void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
207 ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
208 ASSERT_MSG(to != nullptr, "Next fiber is null!");
209 to->guard.lock();
210 to->previous_fiber = from;
211 auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
212 ASSERT(from->previous_fiber != nullptr);
213 from->previous_fiber->impl->context = transfer.fctx;
214 from->previous_fiber->guard.unlock();
215 from->previous_fiber.reset();
216}
217
218std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
219 std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
220 fiber->guard.lock();
221 fiber->is_thread_fiber = true;
222 return fiber;
223}
224
225#endif
226} // 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..c7b46aac6
--- /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 thread_pause() {
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} // namespace
34
35namespace Common {
36
37void SpinLock::lock() {
38 while (lck.test_and_set(std::memory_order_acquire)) {
39 thread_pause();
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..70282a961
--- /dev/null
+++ b/src/common/spin_lock.h
@@ -0,0 +1,21 @@
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
11class SpinLock {
12public:
13 void lock();
14 void unlock();
15 bool try_lock();
16
17private:
18 std::atomic_flag lck = ATOMIC_FLAG_INIT;
19};
20
21} // 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 @@
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;
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..d4d35f4e7
--- /dev/null
+++ b/src/common/wall_clock.cpp
@@ -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#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
56private:
57 base_time_point start_time;
58};
59
60#ifdef ARCHITECTURE_x86_64
61
62std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
63 u32 emulated_clock_frequency) {
64 const auto& caps = GetCPUCaps();
65 u64 rtsc_frequency = 0;
66 if (caps.invariant_tsc) {
67 if (caps.base_frequency != 0) {
68 rtsc_frequency = static_cast<u64>(caps.base_frequency) * 1000000U;
69 }
70 if (rtsc_frequency == 0) {
71 rtsc_frequency = EstimateRDTSCFrequency();
72 }
73 }
74 if (rtsc_frequency == 0) {
75 return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
76 emulated_clock_frequency);
77 } else {
78 return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
79 rtsc_frequency);
80 }
81}
82
83#else
84
85std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
86 u32 emulated_clock_frequency) {
87 return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
88}
89
90#endif
91
92} // 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 @@
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 /// Tells if the wall clock, uses the host CPU's hardware clock
32 bool IsNative() const {
33 return is_native;
34 }
35
36protected:
37 WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
38 : emulated_cpu_frequency{emulated_cpu_frequency},
39 emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
40
41 u64 emulated_cpu_frequency;
42 u64 emulated_clock_frequency;
43
44private:
45 bool is_native;
46};
47
48std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
49 u32 emulated_clock_frequency);
50
51} // 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..26d4d0ba6
--- /dev/null
+++ b/src/common/x64/native_clock.cpp
@@ -0,0 +1,95 @@
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 <thread>
7
8#ifdef _MSC_VER
9#include <intrin.h>
10#else
11#include <x86intrin.h>
12#endif
13
14#include "common/uint128.h"
15#include "common/x64/native_clock.h"
16
17namespace Common {
18
19u64 EstimateRDTSCFrequency() {
20 const auto milli_10 = std::chrono::milliseconds{10};
21 // get current time
22 _mm_mfence();
23 const u64 tscStart = __rdtsc();
24 const auto startTime = std::chrono::high_resolution_clock::now();
25 // wait roughly 3 seconds
26 while (true) {
27 auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
28 std::chrono::high_resolution_clock::now() - startTime);
29 if (milli.count() >= 3000)
30 break;
31 std::this_thread::sleep_for(milli_10);
32 }
33 const auto endTime = std::chrono::high_resolution_clock::now();
34 _mm_mfence();
35 const u64 tscEnd = __rdtsc();
36 // calculate difference
37 const u64 timer_diff =
38 std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
39 const u64 tsc_diff = tscEnd - tscStart;
40 const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
41 return tsc_freq;
42}
43
44namespace X64 {
45NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
46 u64 rtsc_frequency)
47 : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
48 rtsc_frequency} {
49 _mm_mfence();
50 last_measure = __rdtsc();
51 accumulated_ticks = 0U;
52}
53
54u64 NativeClock::GetRTSC() {
55 rtsc_serialize.lock();
56 _mm_mfence();
57 const u64 current_measure = __rdtsc();
58 u64 diff = current_measure - last_measure;
59 diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
60 if (current_measure > last_measure) {
61 last_measure = current_measure;
62 }
63 accumulated_ticks += diff;
64 rtsc_serialize.unlock();
65 return accumulated_ticks;
66}
67
68std::chrono::nanoseconds NativeClock::GetTimeNS() {
69 const u64 rtsc_value = GetRTSC();
70 return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
71}
72
73std::chrono::microseconds NativeClock::GetTimeUS() {
74 const u64 rtsc_value = GetRTSC();
75 return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
76}
77
78std::chrono::milliseconds NativeClock::GetTimeMS() {
79 const u64 rtsc_value = GetRTSC();
80 return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
81}
82
83u64 NativeClock::GetClockCycles() {
84 const u64 rtsc_value = GetRTSC();
85 return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
86}
87
88u64 NativeClock::GetCPUCycles() {
89 const u64 rtsc_value = GetRTSC();
90 return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
91}
92
93} // namespace X64
94
95} // 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 @@
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
29private:
30 u64 GetRTSC();
31
32 SpinLock rtsc_serialize{};
33 u64 last_measure{};
34 u64 accumulated_ticks{};
35 u64 rtsc_frequency;
36};
37} // namespace X64
38
39u64 EstimateRDTSCFrequency();
40
41} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index cb9ced5c9..efbad628f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -547,6 +547,8 @@ add_library(core STATIC
547 hle/service/vi/vi_u.h 547 hle/service/vi/vi_u.h
548 hle/service/wlan/wlan.cpp 548 hle/service/wlan/wlan.cpp
549 hle/service/wlan/wlan.h 549 hle/service/wlan/wlan.h
550 host_timing.cpp
551 host_timing.h
550 loader/deconstructed_rom_directory.cpp 552 loader/deconstructed_rom_directory.cpp
551 loader/deconstructed_rom_directory.h 553 loader/deconstructed_rom_directory.h
552 loader/elf.cpp 554 loader/elf.cpp
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index de50d3b14..be34b26fe 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -49,6 +49,21 @@ s64 nsToCycles(std::chrono::nanoseconds ns) {
49 return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000; 49 return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000;
50} 50}
51 51
52u64 msToClockCycles(std::chrono::milliseconds ns) {
53 const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
54 return Common::Divide128On32(temp, 1000).first;
55}
56
57u64 usToClockCycles(std::chrono::microseconds ns) {
58 const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
59 return Common::Divide128On32(temp, 1000000).first;
60}
61
62u64 nsToClockCycles(std::chrono::nanoseconds ns) {
63 const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
64 return Common::Divide128On32(temp, 1000000000).first;
65}
66
52u64 CpuCyclesToClockCycles(u64 ticks) { 67u64 CpuCyclesToClockCycles(u64 ticks) {
53 const u128 temporal = Common::Multiply64Into128(ticks, Hardware::CNTFREQ); 68 const u128 temporal = Common::Multiply64Into128(ticks, Hardware::CNTFREQ);
54 return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first; 69 return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index addc72b19..b3c58447d 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -13,6 +13,9 @@ namespace Core::Timing {
13s64 msToCycles(std::chrono::milliseconds ms); 13s64 msToCycles(std::chrono::milliseconds ms);
14s64 usToCycles(std::chrono::microseconds us); 14s64 usToCycles(std::chrono::microseconds us);
15s64 nsToCycles(std::chrono::nanoseconds ns); 15s64 nsToCycles(std::chrono::nanoseconds ns);
16u64 msToClockCycles(std::chrono::milliseconds ns);
17u64 usToClockCycles(std::chrono::microseconds ns);
18u64 nsToClockCycles(std::chrono::nanoseconds ns);
16 19
17inline std::chrono::milliseconds CyclesToMs(s64 cycles) { 20inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
18 return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE); 21 return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE);
diff --git a/src/core/host_timing.cpp b/src/core/host_timing.cpp
new file mode 100644
index 000000000..2f40de1a1
--- /dev/null
+++ b/src/core/host_timing.cpp
@@ -0,0 +1,206 @@
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 "core/host_timing.h"
6
7#include <algorithm>
8#include <mutex>
9#include <string>
10#include <tuple>
11
12#include "common/assert.h"
13#include "core/core_timing_util.h"
14
15namespace Core::HostTiming {
16
17std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
18 return std::make_shared<EventType>(std::move(callback), std::move(name));
19}
20
21struct CoreTiming::Event {
22 u64 time;
23 u64 fifo_order;
24 u64 userdata;
25 std::weak_ptr<EventType> type;
26
27 // Sort by time, unless the times are the same, in which case sort by
28 // the order added to the queue
29 friend bool operator>(const Event& left, const Event& right) {
30 return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order);
31 }
32
33 friend bool operator<(const Event& left, const Event& right) {
34 return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order);
35 }
36};
37
38CoreTiming::CoreTiming() {
39 clock =
40 Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ);
41}
42
43CoreTiming::~CoreTiming() = default;
44
45void CoreTiming::ThreadEntry(CoreTiming& instance) {
46 instance.ThreadLoop();
47}
48
49void CoreTiming::Initialize() {
50 event_fifo_id = 0;
51 const auto empty_timed_callback = [](u64, s64) {};
52 ev_lost = CreateEvent("_lost_event", empty_timed_callback);
53 timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
54}
55
56void CoreTiming::Shutdown() {
57 paused = true;
58 shutting_down = true;
59 event.Set();
60 timer_thread->join();
61 ClearPendingEvents();
62 timer_thread.reset();
63 has_started = false;
64}
65
66void CoreTiming::Pause(bool is_paused) {
67 paused = is_paused;
68}
69
70void CoreTiming::SyncPause(bool is_paused) {
71 if (is_paused == paused && paused_set == paused) {
72 return;
73 }
74 Pause(is_paused);
75 event.Set();
76 while (paused_set != is_paused)
77 ;
78}
79
80bool CoreTiming::IsRunning() const {
81 return !paused_set;
82}
83
84bool CoreTiming::HasPendingEvents() const {
85 return !(wait_set && event_queue.empty());
86}
87
88void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
89 u64 userdata) {
90 basic_lock.lock();
91 const u64 timeout = static_cast<u64>(GetGlobalTimeNs().count() + ns_into_future);
92
93 event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
94
95 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
96 basic_lock.unlock();
97 event.Set();
98}
99
100void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) {
101 basic_lock.lock();
102 const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
103 return e.type.lock().get() == event_type.get() && e.userdata == userdata;
104 });
105
106 // Removing random items breaks the invariant so we have to re-establish it.
107 if (itr != event_queue.end()) {
108 event_queue.erase(itr, event_queue.end());
109 std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
110 }
111 basic_lock.unlock();
112}
113
114void CoreTiming::AddTicks(std::size_t core_index, u64 ticks) {
115 ticks_count[core_index] += ticks;
116}
117
118void CoreTiming::ResetTicks(std::size_t core_index) {
119 ticks_count[core_index] = 0;
120}
121
122u64 CoreTiming::GetCPUTicks() const {
123 return clock->GetCPUCycles();
124}
125
126u64 CoreTiming::GetClockTicks() const {
127 return clock->GetClockCycles();
128}
129
130void CoreTiming::ClearPendingEvents() {
131 event_queue.clear();
132}
133
134void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
135 basic_lock.lock();
136
137 const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
138 return e.type.lock().get() == event_type.get();
139 });
140
141 // Removing random items breaks the invariant so we have to re-establish it.
142 if (itr != event_queue.end()) {
143 event_queue.erase(itr, event_queue.end());
144 std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
145 }
146 basic_lock.unlock();
147}
148
149std::optional<u64> CoreTiming::Advance() {
150 advance_lock.lock();
151 basic_lock.lock();
152 global_timer = GetGlobalTimeNs().count();
153
154 while (!event_queue.empty() && event_queue.front().time <= global_timer) {
155 Event evt = std::move(event_queue.front());
156 std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
157 event_queue.pop_back();
158 basic_lock.unlock();
159
160 if (auto event_type{evt.type.lock()}) {
161 event_type->callback(evt.userdata, global_timer - evt.time);
162 }
163
164 basic_lock.lock();
165 }
166
167 if (!event_queue.empty()) {
168 const u64 next_time = event_queue.front().time - global_timer;
169 basic_lock.unlock();
170 advance_lock.unlock();
171 return next_time;
172 } else {
173 basic_lock.unlock();
174 advance_lock.unlock();
175 return std::nullopt;
176 }
177}
178
179void CoreTiming::ThreadLoop() {
180 has_started = true;
181 while (!shutting_down) {
182 while (!paused) {
183 paused_set = false;
184 const auto next_time = Advance();
185 if (next_time) {
186 std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
187 event.WaitFor(next_time_ns);
188 } else {
189 wait_set = true;
190 event.Wait();
191 }
192 wait_set = false;
193 }
194 paused_set = true;
195 }
196}
197
198std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
199 return clock->GetTimeNS();
200}
201
202std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
203 return clock->GetTimeUS();
204}
205
206} // namespace Core::HostTiming
diff --git a/src/core/host_timing.h b/src/core/host_timing.h
new file mode 100644
index 000000000..be6b68d7c
--- /dev/null
+++ b/src/core/host_timing.h
@@ -0,0 +1,160 @@
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#include <chrono>
9#include <functional>
10#include <memory>
11#include <mutex>
12#include <optional>
13#include <string>
14#include <thread>
15#include <vector>
16
17#include "common/common_types.h"
18#include "common/spin_lock.h"
19#include "common/thread.h"
20#include "common/threadsafe_queue.h"
21#include "common/wall_clock.h"
22#include "core/hardware_properties.h"
23
24namespace Core::HostTiming {
25
26/// A callback that may be scheduled for a particular core timing event.
27using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>;
28
29/// Contains the characteristics of a particular event.
30struct EventType {
31 EventType(TimedCallback&& callback, std::string&& name)
32 : callback{std::move(callback)}, name{std::move(name)} {}
33
34 /// The event's callback function.
35 TimedCallback callback;
36 /// A pointer to the name of the event.
37 const std::string name;
38};
39
40/**
41 * This is a system to schedule events into the emulated machine's future. Time is measured
42 * in main CPU clock cycles.
43 *
44 * To schedule an event, you first have to register its type. This is where you pass in the
45 * callback. You then schedule events using the type id you get back.
46 *
47 * The int cyclesLate that the callbacks get is how many cycles late it was.
48 * So to schedule a new event on a regular basis:
49 * inside callback:
50 * ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
51 */
52class CoreTiming {
53public:
54 CoreTiming();
55 ~CoreTiming();
56
57 CoreTiming(const CoreTiming&) = delete;
58 CoreTiming(CoreTiming&&) = delete;
59
60 CoreTiming& operator=(const CoreTiming&) = delete;
61 CoreTiming& operator=(CoreTiming&&) = delete;
62
63 /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
64 /// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
65 void Initialize();
66
67 /// Tears down all timing related functionality.
68 void Shutdown();
69
70 /// Pauses/Unpauses the execution of the timer thread.
71 void Pause(bool is_paused);
72
73 /// Pauses/Unpauses the execution of the timer thread and waits until paused.
74 void SyncPause(bool is_paused);
75
76 /// Checks if core timing is running.
77 bool IsRunning() const;
78
79 /// Checks if the timer thread has started.
80 bool HasStarted() const {
81 return has_started;
82 }
83
84 /// Checks if there are any pending time events.
85 bool HasPendingEvents() const;
86
87 /// Schedules an event in core timing
88 void ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
89 u64 userdata = 0);
90
91 void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
92
93 /// We only permit one event of each type in the queue at a time.
94 void RemoveEvent(const std::shared_ptr<EventType>& event_type);
95
96 void AddTicks(std::size_t core_index, u64 ticks);
97
98 void ResetTicks(std::size_t core_index);
99
100 /// Returns current time in emulated CPU cycles
101 u64 GetCPUTicks() const;
102
103 /// Returns current time in emulated in Clock cycles
104 u64 GetClockTicks() const;
105
106 /// Returns current time in microseconds.
107 std::chrono::microseconds GetGlobalTimeUs() const;
108
109 /// Returns current time in nanoseconds.
110 std::chrono::nanoseconds GetGlobalTimeNs() const;
111
112 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
113 std::optional<u64> Advance();
114
115private:
116 struct Event;
117
118 /// Clear all pending events. This should ONLY be done on exit.
119 void ClearPendingEvents();
120
121 static void ThreadEntry(CoreTiming& instance);
122 void ThreadLoop();
123
124 std::unique_ptr<Common::WallClock> clock;
125
126 u64 global_timer = 0;
127
128 std::chrono::nanoseconds start_point;
129
130 // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
131 // We don't use std::priority_queue because we need to be able to serialize, unserialize and
132 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
133 // accomodated by the standard adaptor class.
134 std::vector<Event> event_queue;
135 u64 event_fifo_id = 0;
136
137 std::shared_ptr<EventType> ev_lost;
138 Common::Event event{};
139 Common::SpinLock basic_lock{};
140 Common::SpinLock advance_lock{};
141 std::unique_ptr<std::thread> timer_thread;
142 std::atomic<bool> paused{};
143 std::atomic<bool> paused_set{};
144 std::atomic<bool> wait_set{};
145 std::atomic<bool> shutting_down{};
146 std::atomic<bool> has_started{};
147
148 std::array<std::atomic<u64>, Core::Hardware::NUM_CPU_CORES> ticks_count{};
149};
150
151/// Creates a core timing event with the given name and callback.
152///
153/// @param name The name of the core timing event to create.
154/// @param callback The callback to execute for the event.
155///
156/// @returns An EventType instance representing the created event.
157///
158std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback);
159
160} // namespace Core::HostTiming
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c7038b217..3f750b51c 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,12 +1,14 @@
1add_executable(tests 1add_executable(tests
2 common/bit_field.cpp 2 common/bit_field.cpp
3 common/bit_utils.cpp 3 common/bit_utils.cpp
4 common/fibers.cpp
4 common/multi_level_queue.cpp 5 common/multi_level_queue.cpp
5 common/param_package.cpp 6 common/param_package.cpp
6 common/ring_buffer.cpp 7 common/ring_buffer.cpp
7 core/arm/arm_test_common.cpp 8 core/arm/arm_test_common.cpp
8 core/arm/arm_test_common.h 9 core/arm/arm_test_common.h
9 core/core_timing.cpp 10 core/core_timing.cpp
11 core/host_timing.cpp
10 tests.cpp 12 tests.cpp
11) 13)
12 14
diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp
new file mode 100644
index 000000000..12536b6d8
--- /dev/null
+++ b/src/tests/common/fibers.cpp
@@ -0,0 +1,358 @@
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 <atomic>
6#include <cstdlib>
7#include <functional>
8#include <memory>
9#include <thread>
10#include <unordered_map>
11#include <vector>
12
13#include <catch2/catch.hpp>
14#include <math.h>
15#include "common/common_types.h"
16#include "common/fiber.h"
17#include "common/spin_lock.h"
18
19namespace Common {
20
21class TestControl1 {
22public:
23 TestControl1() = default;
24
25 void DoWork();
26
27 void ExecuteThread(u32 id);
28
29 std::unordered_map<std::thread::id, u32> ids;
30 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
31 std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
32 std::vector<u32> items;
33 std::vector<u32> results;
34};
35
36static void WorkControl1(void* control) {
37 auto* test_control = static_cast<TestControl1*>(control);
38 test_control->DoWork();
39}
40
41void TestControl1::DoWork() {
42 std::thread::id this_id = std::this_thread::get_id();
43 u32 id = ids[this_id];
44 u32 value = items[id];
45 for (u32 i = 0; i < id; i++) {
46 value++;
47 }
48 results[id] = value;
49 Fiber::YieldTo(work_fibers[id], thread_fibers[id]);
50}
51
52void TestControl1::ExecuteThread(u32 id) {
53 std::thread::id this_id = std::this_thread::get_id();
54 ids[this_id] = id;
55 auto thread_fiber = Fiber::ThreadToFiber();
56 thread_fibers[id] = thread_fiber;
57 work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this);
58 items[id] = rand() % 256;
59 Fiber::YieldTo(thread_fibers[id], work_fibers[id]);
60 thread_fibers[id]->Exit();
61}
62
63static void ThreadStart1(u32 id, TestControl1& test_control) {
64 test_control.ExecuteThread(id);
65}
66
67/** This test checks for fiber setup configuration and validates that fibers are
68 * doing all the work required.
69 */
70TEST_CASE("Fibers::Setup", "[common]") {
71 constexpr u32 num_threads = 7;
72 TestControl1 test_control{};
73 test_control.thread_fibers.resize(num_threads);
74 test_control.work_fibers.resize(num_threads);
75 test_control.items.resize(num_threads, 0);
76 test_control.results.resize(num_threads, 0);
77 std::vector<std::thread> threads;
78 for (u32 i = 0; i < num_threads; i++) {
79 threads.emplace_back(ThreadStart1, i, std::ref(test_control));
80 }
81 for (u32 i = 0; i < num_threads; i++) {
82 threads[i].join();
83 }
84 for (u32 i = 0; i < num_threads; i++) {
85 REQUIRE(test_control.items[i] + i == test_control.results[i]);
86 }
87}
88
89class TestControl2 {
90public:
91 TestControl2() = default;
92
93 void DoWork1() {
94 trap2 = false;
95 while (trap.load())
96 ;
97 for (u32 i = 0; i < 12000; i++) {
98 value1 += i;
99 }
100 Fiber::YieldTo(fiber1, fiber3);
101 std::thread::id this_id = std::this_thread::get_id();
102 u32 id = ids[this_id];
103 assert1 = id == 1;
104 value2 += 5000;
105 Fiber::YieldTo(fiber1, thread_fibers[id]);
106 }
107
108 void DoWork2() {
109 while (trap2.load())
110 ;
111 value2 = 2000;
112 trap = false;
113 Fiber::YieldTo(fiber2, fiber1);
114 assert3 = false;
115 }
116
117 void DoWork3() {
118 std::thread::id this_id = std::this_thread::get_id();
119 u32 id = ids[this_id];
120 assert2 = id == 0;
121 value1 += 1000;
122 Fiber::YieldTo(fiber3, thread_fibers[id]);
123 }
124
125 void ExecuteThread(u32 id);
126
127 void CallFiber1() {
128 std::thread::id this_id = std::this_thread::get_id();
129 u32 id = ids[this_id];
130 Fiber::YieldTo(thread_fibers[id], fiber1);
131 }
132
133 void CallFiber2() {
134 std::thread::id this_id = std::this_thread::get_id();
135 u32 id = ids[this_id];
136 Fiber::YieldTo(thread_fibers[id], fiber2);
137 }
138
139 void Exit();
140
141 bool assert1{};
142 bool assert2{};
143 bool assert3{true};
144 u32 value1{};
145 u32 value2{};
146 std::atomic<bool> trap{true};
147 std::atomic<bool> trap2{true};
148 std::unordered_map<std::thread::id, u32> ids;
149 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
150 std::shared_ptr<Common::Fiber> fiber1;
151 std::shared_ptr<Common::Fiber> fiber2;
152 std::shared_ptr<Common::Fiber> fiber3;
153};
154
155static void WorkControl2_1(void* control) {
156 auto* test_control = static_cast<TestControl2*>(control);
157 test_control->DoWork1();
158}
159
160static void WorkControl2_2(void* control) {
161 auto* test_control = static_cast<TestControl2*>(control);
162 test_control->DoWork2();
163}
164
165static void WorkControl2_3(void* control) {
166 auto* test_control = static_cast<TestControl2*>(control);
167 test_control->DoWork3();
168}
169
170void TestControl2::ExecuteThread(u32 id) {
171 std::thread::id this_id = std::this_thread::get_id();
172 ids[this_id] = id;
173 auto thread_fiber = Fiber::ThreadToFiber();
174 thread_fibers[id] = thread_fiber;
175}
176
177void TestControl2::Exit() {
178 std::thread::id this_id = std::this_thread::get_id();
179 u32 id = ids[this_id];
180 thread_fibers[id]->Exit();
181}
182
183static void ThreadStart2_1(u32 id, TestControl2& test_control) {
184 test_control.ExecuteThread(id);
185 test_control.CallFiber1();
186 test_control.Exit();
187}
188
189static void ThreadStart2_2(u32 id, TestControl2& test_control) {
190 test_control.ExecuteThread(id);
191 test_control.CallFiber2();
192 test_control.Exit();
193}
194
195/** This test checks for fiber thread exchange configuration and validates that fibers are
196 * that a fiber has been succesfully transfered from one thread to another and that the TLS
197 * region of the thread is kept while changing fibers.
198 */
199TEST_CASE("Fibers::InterExchange", "[common]") {
200 TestControl2 test_control{};
201 test_control.thread_fibers.resize(2);
202 test_control.fiber1 =
203 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_1}, &test_control);
204 test_control.fiber2 =
205 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_2}, &test_control);
206 test_control.fiber3 =
207 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_3}, &test_control);
208 std::thread thread1(ThreadStart2_1, 0, std::ref(test_control));
209 std::thread thread2(ThreadStart2_2, 1, std::ref(test_control));
210 thread1.join();
211 thread2.join();
212 REQUIRE(test_control.assert1);
213 REQUIRE(test_control.assert2);
214 REQUIRE(test_control.assert3);
215 REQUIRE(test_control.value2 == 7000);
216 u32 cal_value = 0;
217 for (u32 i = 0; i < 12000; i++) {
218 cal_value += i;
219 }
220 cal_value += 1000;
221 REQUIRE(test_control.value1 == cal_value);
222}
223
224class TestControl3 {
225public:
226 TestControl3() = default;
227
228 void DoWork1() {
229 value1 += 1;
230 Fiber::YieldTo(fiber1, fiber2);
231 std::thread::id this_id = std::this_thread::get_id();
232 u32 id = ids[this_id];
233 value3 += 1;
234 Fiber::YieldTo(fiber1, thread_fibers[id]);
235 }
236
237 void DoWork2() {
238 value2 += 1;
239 std::thread::id this_id = std::this_thread::get_id();
240 u32 id = ids[this_id];
241 Fiber::YieldTo(fiber2, thread_fibers[id]);
242 }
243
244 void ExecuteThread(u32 id);
245
246 void CallFiber1() {
247 std::thread::id this_id = std::this_thread::get_id();
248 u32 id = ids[this_id];
249 Fiber::YieldTo(thread_fibers[id], fiber1);
250 }
251
252 void Exit();
253
254 u32 value1{};
255 u32 value2{};
256 u32 value3{};
257 std::unordered_map<std::thread::id, u32> ids;
258 std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
259 std::shared_ptr<Common::Fiber> fiber1;
260 std::shared_ptr<Common::Fiber> fiber2;
261};
262
263static void WorkControl3_1(void* control) {
264 auto* test_control = static_cast<TestControl3*>(control);
265 test_control->DoWork1();
266}
267
268static void WorkControl3_2(void* control) {
269 auto* test_control = static_cast<TestControl3*>(control);
270 test_control->DoWork2();
271}
272
273void TestControl3::ExecuteThread(u32 id) {
274 std::thread::id this_id = std::this_thread::get_id();
275 ids[this_id] = id;
276 auto thread_fiber = Fiber::ThreadToFiber();
277 thread_fibers[id] = thread_fiber;
278}
279
280void TestControl3::Exit() {
281 std::thread::id this_id = std::this_thread::get_id();
282 u32 id = ids[this_id];
283 thread_fibers[id]->Exit();
284}
285
286static void ThreadStart3(u32 id, TestControl3& test_control) {
287 test_control.ExecuteThread(id);
288 test_control.CallFiber1();
289 test_control.Exit();
290}
291
292/** This test checks for one two threads racing for starting the same fiber.
293 * It checks execution occured in an ordered manner and by no time there were
294 * two contexts at the same time.
295 */
296TEST_CASE("Fibers::StartRace", "[common]") {
297 TestControl3 test_control{};
298 test_control.thread_fibers.resize(2);
299 test_control.fiber1 =
300 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_1}, &test_control);
301 test_control.fiber2 =
302 std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_2}, &test_control);
303 std::thread thread1(ThreadStart3, 0, std::ref(test_control));
304 std::thread thread2(ThreadStart3, 1, std::ref(test_control));
305 thread1.join();
306 thread2.join();
307 REQUIRE(test_control.value1 == 1);
308 REQUIRE(test_control.value2 == 1);
309 REQUIRE(test_control.value3 == 1);
310}
311
312class TestControl4;
313
314static void WorkControl4(void* control);
315
316class TestControl4 {
317public:
318 TestControl4() {
319 fiber1 = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl4}, this);
320 goal_reached = false;
321 rewinded = false;
322 }
323
324 void Execute() {
325 thread_fiber = Fiber::ThreadToFiber();
326 Fiber::YieldTo(thread_fiber, fiber1);
327 thread_fiber->Exit();
328 }
329
330 void DoWork() {
331 fiber1->SetRewindPoint(std::function<void(void*)>{WorkControl4}, this);
332 if (rewinded) {
333 goal_reached = true;
334 Fiber::YieldTo(fiber1, thread_fiber);
335 }
336 rewinded = true;
337 fiber1->Rewind();
338 }
339
340 std::shared_ptr<Common::Fiber> fiber1;
341 std::shared_ptr<Common::Fiber> thread_fiber;
342 bool goal_reached;
343 bool rewinded;
344};
345
346static void WorkControl4(void* control) {
347 auto* test_control = static_cast<TestControl4*>(control);
348 test_control->DoWork();
349}
350
351TEST_CASE("Fibers::Rewind", "[common]") {
352 TestControl4 test_control{};
353 test_control.Execute();
354 REQUIRE(test_control.goal_reached);
355 REQUIRE(test_control.rewinded);
356}
357
358} // namespace Common
diff --git a/src/tests/core/host_timing.cpp b/src/tests/core/host_timing.cpp
new file mode 100644
index 000000000..556254098
--- /dev/null
+++ b/src/tests/core/host_timing.cpp
@@ -0,0 +1,142 @@
1// Copyright 2016 Dolphin Emulator Project / 2017 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <catch2/catch.hpp>
6
7#include <array>
8#include <bitset>
9#include <cstdlib>
10#include <memory>
11#include <string>
12
13#include "common/file_util.h"
14#include "core/core.h"
15#include "core/host_timing.h"
16
17// Numbers are chosen randomly to make sure the correct one is given.
18static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
19static constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
20static constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
21static std::array<s64, 5> delays{};
22
23static std::bitset<CB_IDS.size()> callbacks_ran_flags;
24static u64 expected_callback = 0;
25
26template <unsigned int IDX>
27void HostCallbackTemplate(u64 userdata, s64 nanoseconds_late) {
28 static_assert(IDX < CB_IDS.size(), "IDX out of range");
29 callbacks_ran_flags.set(IDX);
30 REQUIRE(CB_IDS[IDX] == userdata);
31 REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
32 delays[IDX] = nanoseconds_late;
33 ++expected_callback;
34}
35
36struct ScopeInit final {
37 ScopeInit() {
38 core_timing.Initialize();
39 }
40 ~ScopeInit() {
41 core_timing.Shutdown();
42 }
43
44 Core::HostTiming::CoreTiming core_timing;
45};
46
47#pragma optimize("", off)
48
49static u64 TestTimerSpeed(Core::HostTiming::CoreTiming& core_timing) {
50 u64 start = core_timing.GetGlobalTimeNs().count();
51 u64 placebo = 0;
52 for (std::size_t i = 0; i < 1000; i++) {
53 placebo += core_timing.GetGlobalTimeNs().count();
54 }
55 u64 end = core_timing.GetGlobalTimeNs().count();
56 return (end - start);
57}
58
59#pragma optimize("", on)
60
61TEST_CASE("HostTiming[BasicOrder]", "[core]") {
62 ScopeInit guard;
63 auto& core_timing = guard.core_timing;
64 std::vector<std::shared_ptr<Core::HostTiming::EventType>> events{
65 Core::HostTiming::CreateEvent("callbackA", HostCallbackTemplate<0>),
66 Core::HostTiming::CreateEvent("callbackB", HostCallbackTemplate<1>),
67 Core::HostTiming::CreateEvent("callbackC", HostCallbackTemplate<2>),
68 Core::HostTiming::CreateEvent("callbackD", HostCallbackTemplate<3>),
69 Core::HostTiming::CreateEvent("callbackE", HostCallbackTemplate<4>),
70 };
71
72 expected_callback = 0;
73
74 core_timing.SyncPause(true);
75
76 u64 one_micro = 1000U;
77 for (std::size_t i = 0; i < events.size(); i++) {
78 u64 order = calls_order[i];
79 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]);
80 }
81 /// test pause
82 REQUIRE(callbacks_ran_flags.none());
83
84 core_timing.Pause(false); // No need to sync
85
86 while (core_timing.HasPendingEvents())
87 ;
88
89 REQUIRE(callbacks_ran_flags.all());
90
91 for (std::size_t i = 0; i < delays.size(); i++) {
92 const double delay = static_cast<double>(delays[i]);
93 const double micro = delay / 1000.0f;
94 const double mili = micro / 1000.0f;
95 printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
96 }
97}
98
99TEST_CASE("HostTiming[BasicOrderNoPausing]", "[core]") {
100 ScopeInit guard;
101 auto& core_timing = guard.core_timing;
102 std::vector<std::shared_ptr<Core::HostTiming::EventType>> events{
103 Core::HostTiming::CreateEvent("callbackA", HostCallbackTemplate<0>),
104 Core::HostTiming::CreateEvent("callbackB", HostCallbackTemplate<1>),
105 Core::HostTiming::CreateEvent("callbackC", HostCallbackTemplate<2>),
106 Core::HostTiming::CreateEvent("callbackD", HostCallbackTemplate<3>),
107 Core::HostTiming::CreateEvent("callbackE", HostCallbackTemplate<4>),
108 };
109
110 core_timing.SyncPause(true);
111 core_timing.SyncPause(false);
112
113 expected_callback = 0;
114
115 u64 start = core_timing.GetGlobalTimeNs().count();
116 u64 one_micro = 1000U;
117 for (std::size_t i = 0; i < events.size(); i++) {
118 u64 order = calls_order[i];
119 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]);
120 }
121 u64 end = core_timing.GetGlobalTimeNs().count();
122 const double scheduling_time = static_cast<double>(end - start);
123 const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
124
125 while (core_timing.HasPendingEvents())
126 ;
127
128 REQUIRE(callbacks_ran_flags.all());
129
130 for (std::size_t i = 0; i < delays.size(); i++) {
131 const double delay = static_cast<double>(delays[i]);
132 const double micro = delay / 1000.0f;
133 const double mili = micro / 1000.0f;
134 printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
135 }
136
137 const double micro = scheduling_time / 1000.0f;
138 const double mili = micro / 1000.0f;
139 printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
140 printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
141 timer_time / 1000000.f);
142}