diff options
Diffstat (limited to 'src/common/fiber.cpp')
-rw-r--r-- | src/common/fiber.cpp | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp new file mode 100644 index 000000000..3e3029cd1 --- /dev/null +++ b/src/common/fiber.cpp @@ -0,0 +1,233 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/fiber.h" +#include "common/spin_lock.h" + +#if defined(_WIN32) || defined(WIN32) +#include <windows.h> +#else +#include <boost/context/detail/fcontext.hpp> +#endif + +namespace Common { + +constexpr std::size_t default_stack_size = 256 * 1024; // 256kb + +struct Fiber::FiberImpl { + SpinLock guard{}; + std::function<void(void*)> entry_point; + std::function<void(void*)> rewind_point; + void* rewind_parameter{}; + void* start_parameter{}; + std::shared_ptr<Fiber> previous_fiber; + bool is_thread_fiber{}; + bool released{}; + +#if defined(_WIN32) || defined(WIN32) + LPVOID handle = nullptr; + LPVOID rewind_handle = nullptr; +#else + alignas(64) std::array<u8, default_stack_size> stack; + alignas(64) std::array<u8, default_stack_size> rewind_stack; + u8* stack_limit; + u8* rewind_stack_limit; + boost::context::detail::fcontext_t context; + boost::context::detail::fcontext_t rewind_context; +#endif +}; + +void Fiber::SetStartParameter(void* new_parameter) { + impl->start_parameter = new_parameter; +} + +void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param) { + impl->rewind_point = std::move(rewind_func); + impl->rewind_parameter = rewind_param; +} + +#if defined(_WIN32) || defined(WIN32) + +void Fiber::Start() { + ASSERT(impl->previous_fiber != nullptr); + impl->previous_fiber->impl->guard.unlock(); + impl->previous_fiber.reset(); + impl->entry_point(impl->start_parameter); + UNREACHABLE(); +} + +void Fiber::OnRewind() { + ASSERT(impl->handle != nullptr); + DeleteFiber(impl->handle); + impl->handle = impl->rewind_handle; + impl->rewind_handle = nullptr; + impl->rewind_point(impl->rewind_parameter); + UNREACHABLE(); +} + +void Fiber::FiberStartFunc(void* fiber_parameter) { + auto* fiber = static_cast<Fiber*>(fiber_parameter); + fiber->Start(); +} + +void Fiber::RewindStartFunc(void* fiber_parameter) { + auto* fiber = static_cast<Fiber*>(fiber_parameter); + fiber->OnRewind(); +} + +Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) + : impl{std::make_unique<FiberImpl>()} { + impl->entry_point = std::move(entry_point_func); + impl->start_parameter = start_parameter; + impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this); +} + +Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {} + +Fiber::~Fiber() { + if (impl->released) { + return; + } + // Make sure the Fiber is not being used + const bool locked = impl->guard.try_lock(); + ASSERT_MSG(locked, "Destroying a fiber that's still running"); + if (locked) { + impl->guard.unlock(); + } + DeleteFiber(impl->handle); +} + +void Fiber::Exit() { + ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber"); + if (!impl->is_thread_fiber) { + return; + } + ConvertFiberToThread(); + impl->guard.unlock(); + impl->released = true; +} + +void Fiber::Rewind() { + ASSERT(impl->rewind_point); + ASSERT(impl->rewind_handle == nullptr); + impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this); + SwitchToFiber(impl->rewind_handle); +} + +void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) { + ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); + ASSERT_MSG(to != nullptr, "Next fiber is null!"); + to->impl->guard.lock(); + to->impl->previous_fiber = from; + SwitchToFiber(to->impl->handle); + ASSERT(from->impl->previous_fiber != nullptr); + from->impl->previous_fiber->impl->guard.unlock(); + from->impl->previous_fiber.reset(); +} + +std::shared_ptr<Fiber> Fiber::ThreadToFiber() { + std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; + fiber->impl->guard.lock(); + fiber->impl->handle = ConvertThreadToFiber(nullptr); + fiber->impl->is_thread_fiber = true; + return fiber; +} + +#else + +void Fiber::Start(boost::context::detail::transfer_t& transfer) { + ASSERT(impl->previous_fiber != nullptr); + impl->previous_fiber->impl->context = transfer.fctx; + impl->previous_fiber->impl->guard.unlock(); + impl->previous_fiber.reset(); + impl->entry_point(impl->start_parameter); + UNREACHABLE(); +} + +void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) { + ASSERT(impl->context != nullptr); + impl->context = impl->rewind_context; + impl->rewind_context = nullptr; + u8* tmp = impl->stack_limit; + impl->stack_limit = impl->rewind_stack_limit; + impl->rewind_stack_limit = tmp; + impl->rewind_point(impl->rewind_parameter); + UNREACHABLE(); +} + +void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { + auto* fiber = static_cast<Fiber*>(transfer.data); + fiber->Start(transfer); +} + +void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) { + auto* fiber = static_cast<Fiber*>(transfer.data); + fiber->OnRewind(transfer); +} + +Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) + : impl{std::make_unique<FiberImpl>()} { + impl->entry_point = std::move(entry_point_func); + impl->start_parameter = start_parameter; + impl->stack_limit = impl->stack.data(); + impl->rewind_stack_limit = impl->rewind_stack.data(); + u8* stack_base = impl->stack_limit + default_stack_size; + impl->context = + boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc); +} + +Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {} + +Fiber::~Fiber() { + if (impl->released) { + return; + } + // Make sure the Fiber is not being used + const bool locked = impl->guard.try_lock(); + ASSERT_MSG(locked, "Destroying a fiber that's still running"); + if (locked) { + impl->guard.unlock(); + } +} + +void Fiber::Exit() { + ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber"); + if (!impl->is_thread_fiber) { + return; + } + impl->guard.unlock(); + impl->released = true; +} + +void Fiber::Rewind() { + ASSERT(impl->rewind_point); + ASSERT(impl->rewind_context == nullptr); + u8* stack_base = impl->rewind_stack_limit + default_stack_size; + impl->rewind_context = + boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc); + boost::context::detail::jump_fcontext(impl->rewind_context, this); +} + +void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) { + ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); + ASSERT_MSG(to != nullptr, "Next fiber is null!"); + to->impl->guard.lock(); + to->impl->previous_fiber = from; + auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get()); + ASSERT(from->impl->previous_fiber != nullptr); + from->impl->previous_fiber->impl->context = transfer.fctx; + from->impl->previous_fiber->impl->guard.unlock(); + from->impl->previous_fiber.reset(); +} + +std::shared_ptr<Fiber> Fiber::ThreadToFiber() { + std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; + fiber->impl->guard.lock(); + fiber->impl->is_thread_fiber = true; + return fiber; +} + +#endif +} // namespace Common |