From 8d0e3c542258cc50081af93aa85e0e3cbf8900c3 Mon Sep 17 00:00:00 2001 From: Fernando Sahmkow Date: Wed, 5 Feb 2020 14:13:16 -0400 Subject: [PATCH] Tests: Add tests for fibers and refactor/fix Fiber class --- src/common/fiber.cpp | 32 +++--- src/common/fiber.h | 19 +++- src/tests/CMakeLists.txt | 1 + src/tests/common/fibers.cpp | 214 ++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 19 deletions(-) create mode 100644 src/tests/common/fibers.cpp diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index eb59f1aa9..a2c0401c4 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -3,18 +3,21 @@ // Refer to the license.txt file included. #include "common/fiber.h" +#ifdef _MSC_VER +#include +#else +#include +#endif namespace Common { #ifdef _MSC_VER -#include struct Fiber::FiberImpl { LPVOID handle = nullptr; }; -void Fiber::_start([[maybe_unused]] void* parameter) { - guard.lock(); +void Fiber::start() { if (previous_fiber) { previous_fiber->guard.unlock(); previous_fiber = nullptr; @@ -22,10 +25,10 @@ void Fiber::_start([[maybe_unused]] void* parameter) { entry_point(start_parameter); } -static void __stdcall FiberStartFunc(LPVOID lpFiberParameter) +void __stdcall Fiber::FiberStartFunc(void* fiber_parameter) { - auto fiber = static_cast(lpFiberParameter); - fiber->_start(nullptr); + auto fiber = static_cast(fiber_parameter); + fiber->start(); } Fiber::Fiber(std::function&& entry_point_func, void* start_parameter) @@ -74,30 +77,26 @@ std::shared_ptr Fiber::ThreadToFiber() { #else -#include - constexpr std::size_t default_stack_size = 1024 * 1024 * 4; // 4MB -struct Fiber::FiberImpl { - boost::context::detail::fcontext_t context; +struct alignas(64) Fiber::FiberImpl { std::array stack; + boost::context::detail::fcontext_t context; }; -void Fiber::_start(void* parameter) { - guard.lock(); - boost::context::detail::transfer_t* transfer = static_cast(parameter); +void Fiber::start(boost::context::detail::transfer_t& transfer) { if (previous_fiber) { - previous_fiber->impl->context = transfer->fctx; + previous_fiber->impl->context = transfer.fctx; previous_fiber->guard.unlock(); previous_fiber = nullptr; } entry_point(start_parameter); } -static void FiberStartFunc(boost::context::detail::transfer_t transfer) +void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { auto fiber = static_cast(transfer.data); - fiber->_start(&transfer); + fiber->start(transfer); } Fiber::Fiber(std::function&& entry_point_func, void* start_parameter) @@ -139,6 +138,7 @@ void Fiber::YieldTo(std::shared_ptr from, std::shared_ptr to) { std::shared_ptr Fiber::ThreadToFiber() { std::shared_ptr fiber = std::shared_ptr{new Fiber()}; + fiber->guard.lock(); fiber->is_thread_fiber = true; return fiber; } diff --git a/src/common/fiber.h b/src/common/fiber.h index ab44905cf..812d6644a 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -10,6 +10,12 @@ #include "common/common_types.h" #include "common/spin_lock.h" +#ifndef _MSC_VER +namespace boost::context::detail { + struct transfer_t; +} +#endif + namespace Common { class Fiber { @@ -31,9 +37,6 @@ public: /// Only call from main thread's fiber void Exit(); - /// Used internally but required to be public, Shall not be used - void _start(void* parameter); - /// Changes the start parameter of the fiber. Has no effect if the fiber already started void SetStartParameter(void* new_parameter) { start_parameter = new_parameter; @@ -42,6 +45,16 @@ public: private: Fiber(); +#ifdef _MSC_VER + void start(); + static void FiberStartFunc(void* fiber_parameter); +#else + void start(boost::context::detail::transfer_t& transfer); + static void FiberStartFunc(boost::context::detail::transfer_t transfer); +#endif + + + struct FiberImpl; SpinLock guard; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index c7038b217..47ef30aa9 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable(tests common/bit_field.cpp common/bit_utils.cpp + common/fibers.cpp common/multi_level_queue.cpp common/param_package.cpp common/ring_buffer.cpp diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp new file mode 100644 index 000000000..ff840afa6 --- /dev/null +++ b/src/tests/common/fibers.cpp @@ -0,0 +1,214 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "common/common_types.h" +#include "common/fiber.h" +#include "common/spin_lock.h" + +namespace Common { + +class TestControl1 { +public: + TestControl1() = default; + + void DoWork(); + + void ExecuteThread(u32 id); + + std::unordered_map ids; + std::vector> thread_fibers; + std::vector> work_fibers; + std::vector items; + std::vector results; +}; + +static void WorkControl1(void* control) { + TestControl1* test_control = static_cast(control); + test_control->DoWork(); +} + +void TestControl1::DoWork() { + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + u32 value = items[id]; + for (u32 i = 0; i < id; i++) { + value++; + } + results[id] = value; + Fiber::YieldTo(work_fibers[id], thread_fibers[id]); +} + +void TestControl1::ExecuteThread(u32 id) { + std::thread::id this_id = std::this_thread::get_id(); + ids[this_id] = id; + auto thread_fiber = Fiber::ThreadToFiber(); + thread_fibers[id] = thread_fiber; + work_fibers[id] = std::make_shared(std::function{WorkControl1}, this); + items[id] = rand() % 256; + Fiber::YieldTo(thread_fibers[id], work_fibers[id]); + thread_fibers[id]->Exit(); +} + +static void ThreadStart1(u32 id, TestControl1& test_control) { + test_control.ExecuteThread(id); +} + + +TEST_CASE("Fibers::Setup", "[common]") { + constexpr u32 num_threads = 7; + TestControl1 test_control{}; + test_control.thread_fibers.resize(num_threads, nullptr); + test_control.work_fibers.resize(num_threads, nullptr); + test_control.items.resize(num_threads, 0); + test_control.results.resize(num_threads, 0); + std::vector threads; + for (u32 i = 0; i < num_threads; i++) { + threads.emplace_back(ThreadStart1, i, std::ref(test_control)); + } + for (u32 i = 0; i < num_threads; i++) { + threads[i].join(); + } + for (u32 i = 0; i < num_threads; i++) { + REQUIRE(test_control.items[i] + i == test_control.results[i]); + } +} + +class TestControl2 { +public: + TestControl2() = default; + + void DoWork1() { + trap2 = false; + while (trap.load()); + for (u32 i = 0; i < 12000; i++) { + value1 += i; + } + Fiber::YieldTo(fiber1, fiber3); + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + assert1 = id == 1; + value2 += 5000; + Fiber::YieldTo(fiber1, thread_fibers[id]); + } + + void DoWork2() { + while (trap2.load()); + value2 = 2000; + trap = false; + Fiber::YieldTo(fiber2, fiber1); + assert3 = false; + } + + void DoWork3() { + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + assert2 = id == 0; + value1 += 1000; + Fiber::YieldTo(fiber3, thread_fibers[id]); + } + + void ExecuteThread(u32 id); + + void CallFiber1() { + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + Fiber::YieldTo(thread_fibers[id], fiber1); + } + + void CallFiber2() { + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + Fiber::YieldTo(thread_fibers[id], fiber2); + } + + void Exit(); + + bool assert1{}; + bool assert2{}; + bool assert3{true}; + u32 value1{}; + u32 value2{}; + std::atomic trap{true}; + std::atomic trap2{true}; + std::unordered_map ids; + std::vector> thread_fibers; + std::shared_ptr fiber1; + std::shared_ptr fiber2; + std::shared_ptr fiber3; +}; + +static void WorkControl2_1(void* control) { + TestControl2* test_control = static_cast(control); + test_control->DoWork1(); +} + +static void WorkControl2_2(void* control) { + TestControl2* test_control = static_cast(control); + test_control->DoWork2(); +} + +static void WorkControl2_3(void* control) { + TestControl2* test_control = static_cast(control); + test_control->DoWork3(); +} + +void TestControl2::ExecuteThread(u32 id) { + std::thread::id this_id = std::this_thread::get_id(); + ids[this_id] = id; + auto thread_fiber = Fiber::ThreadToFiber(); + thread_fibers[id] = thread_fiber; +} + +void TestControl2::Exit() { + std::thread::id this_id = std::this_thread::get_id(); + u32 id = ids[this_id]; + thread_fibers[id]->Exit(); +} + +static void ThreadStart2_1(u32 id, TestControl2& test_control) { + test_control.ExecuteThread(id); + test_control.CallFiber1(); + test_control.Exit(); +} + +static void ThreadStart2_2(u32 id, TestControl2& test_control) { + test_control.ExecuteThread(id); + test_control.CallFiber2(); + test_control.Exit(); +} + +TEST_CASE("Fibers::InterExchange", "[common]") { + TestControl2 test_control{}; + test_control.thread_fibers.resize(2, nullptr); + test_control.fiber1 = std::make_shared(std::function{WorkControl2_1}, &test_control); + test_control.fiber2 = std::make_shared(std::function{WorkControl2_2}, &test_control); + test_control.fiber3 = std::make_shared(std::function{WorkControl2_3}, &test_control); + std::thread thread1(ThreadStart2_1, 0, std::ref(test_control)); + std::thread thread2(ThreadStart2_2, 1, std::ref(test_control)); + thread1.join(); + thread2.join(); + REQUIRE(test_control.assert1); + REQUIRE(test_control.assert2); + REQUIRE(test_control.assert3); + REQUIRE(test_control.value2 == 7000); + u32 cal_value = 0; + for (u32 i = 0; i < 12000; i++) { + cal_value += i; + } + cal_value += 1000; + REQUIRE(test_control.value1 == cal_value); +} + + +} // namespace Common