summaryrefslogtreecommitdiff
path: root/src/common/fiber.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/fiber.cpp')
-rw-r--r--src/common/fiber.cpp226
1 files changed, 226 insertions, 0 deletions
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