fibers.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <atomic>
  4. #include <cstdlib>
  5. #include <functional>
  6. #include <memory>
  7. #include <mutex>
  8. #include <stdexcept>
  9. #include <thread>
  10. #include <unordered_map>
  11. #include <vector>
  12. #include <catch2/catch.hpp>
  13. #include "common/common_types.h"
  14. #include "common/fiber.h"
  15. namespace Common {
  16. class ThreadIds {
  17. public:
  18. void Register(u32 id) {
  19. const auto thread_id = std::this_thread::get_id();
  20. std::scoped_lock lock{mutex};
  21. if (ids.contains(thread_id)) {
  22. throw std::logic_error{"Registering the same thread twice"};
  23. }
  24. ids.emplace(thread_id, id);
  25. }
  26. [[nodiscard]] u32 Get() const {
  27. std::scoped_lock lock{mutex};
  28. return ids.at(std::this_thread::get_id());
  29. }
  30. private:
  31. mutable std::mutex mutex;
  32. std::unordered_map<std::thread::id, u32> ids;
  33. };
  34. class TestControl1 {
  35. public:
  36. TestControl1() = default;
  37. void DoWork() {
  38. const u32 id = thread_ids.Get();
  39. u32 value = items[id];
  40. for (u32 i = 0; i < id; i++) {
  41. value++;
  42. }
  43. results[id] = value;
  44. Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
  45. }
  46. void ExecuteThread(u32 id);
  47. ThreadIds thread_ids;
  48. std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
  49. std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
  50. std::vector<u32> items;
  51. std::vector<u32> results;
  52. };
  53. void TestControl1::ExecuteThread(u32 id) {
  54. thread_ids.Register(id);
  55. auto thread_fiber = Fiber::ThreadToFiber();
  56. thread_fibers[id] = thread_fiber;
  57. work_fibers[id] = std::make_shared<Fiber>([this] { DoWork(); });
  58. items[id] = rand() % 256;
  59. Fiber::YieldTo(thread_fibers[id], *work_fibers[id]);
  60. thread_fibers[id]->Exit();
  61. }
  62. /** This test checks for fiber setup configuration and validates that fibers are
  63. * doing all the work required.
  64. */
  65. TEST_CASE("Fibers::Setup", "[common]") {
  66. constexpr std::size_t num_threads = 7;
  67. TestControl1 test_control{};
  68. test_control.thread_fibers.resize(num_threads);
  69. test_control.work_fibers.resize(num_threads);
  70. test_control.items.resize(num_threads, 0);
  71. test_control.results.resize(num_threads, 0);
  72. std::vector<std::thread> threads;
  73. for (u32 i = 0; i < num_threads; i++) {
  74. threads.emplace_back([&test_control, i] { test_control.ExecuteThread(i); });
  75. }
  76. for (u32 i = 0; i < num_threads; i++) {
  77. threads[i].join();
  78. }
  79. for (u32 i = 0; i < num_threads; i++) {
  80. REQUIRE(test_control.items[i] + i == test_control.results[i]);
  81. }
  82. }
  83. class TestControl2 {
  84. public:
  85. TestControl2() = default;
  86. void DoWork1() {
  87. trap2 = false;
  88. while (trap.load())
  89. ;
  90. for (u32 i = 0; i < 12000; i++) {
  91. value1 += i;
  92. }
  93. Fiber::YieldTo(fiber1, *fiber3);
  94. const u32 id = thread_ids.Get();
  95. assert1 = id == 1;
  96. value2 += 5000;
  97. Fiber::YieldTo(fiber1, *thread_fibers[id]);
  98. }
  99. void DoWork2() {
  100. while (trap2.load())
  101. ;
  102. value2 = 2000;
  103. trap = false;
  104. Fiber::YieldTo(fiber2, *fiber1);
  105. assert3 = false;
  106. }
  107. void DoWork3() {
  108. const u32 id = thread_ids.Get();
  109. assert2 = id == 0;
  110. value1 += 1000;
  111. Fiber::YieldTo(fiber3, *thread_fibers[id]);
  112. }
  113. void ExecuteThread(u32 id);
  114. void CallFiber1() {
  115. const u32 id = thread_ids.Get();
  116. Fiber::YieldTo(thread_fibers[id], *fiber1);
  117. }
  118. void CallFiber2() {
  119. const u32 id = thread_ids.Get();
  120. Fiber::YieldTo(thread_fibers[id], *fiber2);
  121. }
  122. void Exit();
  123. bool assert1{};
  124. bool assert2{};
  125. bool assert3{true};
  126. u32 value1{};
  127. u32 value2{};
  128. std::atomic<bool> trap{true};
  129. std::atomic<bool> trap2{true};
  130. ThreadIds thread_ids;
  131. std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
  132. std::shared_ptr<Common::Fiber> fiber1;
  133. std::shared_ptr<Common::Fiber> fiber2;
  134. std::shared_ptr<Common::Fiber> fiber3;
  135. };
  136. void TestControl2::ExecuteThread(u32 id) {
  137. thread_ids.Register(id);
  138. auto thread_fiber = Fiber::ThreadToFiber();
  139. thread_fibers[id] = thread_fiber;
  140. }
  141. void TestControl2::Exit() {
  142. const u32 id = thread_ids.Get();
  143. thread_fibers[id]->Exit();
  144. }
  145. /** This test checks for fiber thread exchange configuration and validates that fibers are
  146. * that a fiber has been successfully transferred from one thread to another and that the TLS
  147. * region of the thread is kept while changing fibers.
  148. */
  149. TEST_CASE("Fibers::InterExchange", "[common]") {
  150. TestControl2 test_control{};
  151. test_control.thread_fibers.resize(2);
  152. test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
  153. test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
  154. test_control.fiber3 = std::make_shared<Fiber>([&test_control] { test_control.DoWork3(); });
  155. std::thread thread1{[&test_control] {
  156. test_control.ExecuteThread(0);
  157. test_control.CallFiber1();
  158. test_control.Exit();
  159. }};
  160. std::thread thread2{[&test_control] {
  161. test_control.ExecuteThread(1);
  162. test_control.CallFiber2();
  163. test_control.Exit();
  164. }};
  165. thread1.join();
  166. thread2.join();
  167. REQUIRE(test_control.assert1);
  168. REQUIRE(test_control.assert2);
  169. REQUIRE(test_control.assert3);
  170. REQUIRE(test_control.value2 == 7000);
  171. u32 cal_value = 0;
  172. for (u32 i = 0; i < 12000; i++) {
  173. cal_value += i;
  174. }
  175. cal_value += 1000;
  176. REQUIRE(test_control.value1 == cal_value);
  177. }
  178. class TestControl3 {
  179. public:
  180. TestControl3() = default;
  181. void DoWork1() {
  182. value1 += 1;
  183. Fiber::YieldTo(fiber1, *fiber2);
  184. const u32 id = thread_ids.Get();
  185. value3 += 1;
  186. Fiber::YieldTo(fiber1, *thread_fibers[id]);
  187. }
  188. void DoWork2() {
  189. value2 += 1;
  190. const u32 id = thread_ids.Get();
  191. Fiber::YieldTo(fiber2, *thread_fibers[id]);
  192. }
  193. void ExecuteThread(u32 id);
  194. void CallFiber1() {
  195. const u32 id = thread_ids.Get();
  196. Fiber::YieldTo(thread_fibers[id], *fiber1);
  197. }
  198. void Exit();
  199. u32 value1{};
  200. u32 value2{};
  201. u32 value3{};
  202. ThreadIds thread_ids;
  203. std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
  204. std::shared_ptr<Common::Fiber> fiber1;
  205. std::shared_ptr<Common::Fiber> fiber2;
  206. };
  207. void TestControl3::ExecuteThread(u32 id) {
  208. thread_ids.Register(id);
  209. auto thread_fiber = Fiber::ThreadToFiber();
  210. thread_fibers[id] = thread_fiber;
  211. }
  212. void TestControl3::Exit() {
  213. const u32 id = thread_ids.Get();
  214. thread_fibers[id]->Exit();
  215. }
  216. /** This test checks for one two threads racing for starting the same fiber.
  217. * It checks execution occurred in an ordered manner and by no time there were
  218. * two contexts at the same time.
  219. */
  220. TEST_CASE("Fibers::StartRace", "[common]") {
  221. TestControl3 test_control{};
  222. test_control.thread_fibers.resize(2);
  223. test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
  224. test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
  225. const auto race_function{[&test_control](u32 id) {
  226. test_control.ExecuteThread(id);
  227. test_control.CallFiber1();
  228. test_control.Exit();
  229. }};
  230. std::thread thread1([&] { race_function(0); });
  231. std::thread thread2([&] { race_function(1); });
  232. thread1.join();
  233. thread2.join();
  234. REQUIRE(test_control.value1 == 1);
  235. REQUIRE(test_control.value2 == 1);
  236. REQUIRE(test_control.value3 == 1);
  237. }
  238. class TestControl4;
  239. class TestControl4 {
  240. public:
  241. TestControl4() {
  242. fiber1 = std::make_shared<Fiber>([this] { DoWork(); });
  243. goal_reached = false;
  244. rewinded = false;
  245. }
  246. void Execute() {
  247. thread_fiber = Fiber::ThreadToFiber();
  248. Fiber::YieldTo(thread_fiber, *fiber1);
  249. thread_fiber->Exit();
  250. }
  251. void DoWork() {
  252. fiber1->SetRewindPoint([this] { DoWork(); });
  253. if (rewinded) {
  254. goal_reached = true;
  255. Fiber::YieldTo(fiber1, *thread_fiber);
  256. }
  257. rewinded = true;
  258. fiber1->Rewind();
  259. }
  260. std::shared_ptr<Common::Fiber> fiber1;
  261. std::shared_ptr<Common::Fiber> thread_fiber;
  262. bool goal_reached;
  263. bool rewinded;
  264. };
  265. TEST_CASE("Fibers::Rewind", "[common]") {
  266. TestControl4 test_control{};
  267. test_control.Execute();
  268. REQUIRE(test_control.goal_reached);
  269. REQUIRE(test_control.rewinded);
  270. }
  271. } // namespace Common