-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathfiber.cpp
240 lines (203 loc) · 5.63 KB
/
fiber.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
////////////////////////////////////////////////////////////////////////////////
// Distributed under the Boost Software License, Version 1.0. //
// (See accompanying file LICENSE or copy at //
// https://www.boost.org/LICENSE_1_0.txt) //
////////////////////////////////////////////////////////////////////////////////
#include "jobs/fiber/fiber.h"
#include <atomic>
#include <cassert>
#include <cstddef>
#include <exception>
#include <memory>
#include "core/error_handling.h"
#include "core/static_buffer.h"
#include "jobs/context.h"
#include "jobs/job.h"
#include "log/log.h"
#if defined(__clang__)
#define NO_OPT __attribute__((noinline, optnone))
#elif defined(__GNUC__)
#define NO_OPT __attribute__((noinline, optimize("O0")))
#endif
extern "C"
{
// these will be defined with arch specific assembler
extern void save_context(iris::Context *);
extern void restore_context(iris::Context *);
extern void change_stack(void *stack);
}
namespace iris
{
struct Fiber::implementation
{
std::unique_ptr<StaticBuffer> stack_buffer;
std::byte *stack;
Context context;
Context suspended_context;
// all these functions are noinline and noopt
// we require a very strict ordering of instructions in order to save and
// restore contexts and we don't want the compiler to alter that
/**
* Starts a Fiber. Once completed will restore the previously saved context.
*
* @param fiber
* Fiber to start.
*/
NO_OPT static void do_start(Fiber *fiber)
{
try
{
// swap to new stack and execute job
change_stack(fiber->impl_->stack);
fiber->job_();
}
catch (...)
{
// store any exceptions so it can possibly be rethrown later
if (fiber->exception_ == nullptr)
{
fiber->exception_ = std::current_exception();
}
}
*this_fiber() = fiber->parent_fiber_;
restore_context(&fiber->impl_->context);
}
/**
* Suspend the Fiber.
*
* @param fiber
* Fiber to suspend.
*/
NO_OPT static void do_suspend(Fiber *fiber)
{
// no code between these lines
// restoring this saved context will cause execution to continue from
// *after* the following line
save_context(&fiber->impl_->suspended_context);
restore_context(&fiber->impl_->context);
// the above call will not return
// we get here when the suspended context is restored
}
/**
* Resume the Fiber.
*
* @param fiber
* Fiber to resume.
*
* @returns
* Always returns 0 - this is to prevent tail-call optimisation which
* can mess up the assembly calls.
*/
NO_OPT static int do_resume(Fiber *fiber)
{
// store context so we can resume from here once our job has finished
save_context(&fiber->impl_->context);
restore_context(&fiber->impl_->suspended_context);
return 0;
}
};
Fiber::Fiber(Job job)
: Fiber(job, nullptr)
{
}
Fiber::Fiber(Job job, Counter *counter)
: job_(nullptr)
, counter_(counter)
, parent_fiber_(nullptr)
, exception_(nullptr)
, safe_(true)
, impl_(std::make_unique<implementation>())
{
job_ = job;
impl_->stack_buffer = std::make_unique<StaticBuffer>(10u);
// stack grows from high -> low memory so move our pointer down, not all the
// way as we need some space to copy the previous stack frame
impl_->stack = *impl_->stack_buffer + (StaticBuffer::page_size() * 9);
}
Fiber::~Fiber() = default;
void Fiber::start()
{
// bookkeeping
parent_fiber_ = *this_fiber();
*this_fiber() = this;
// save our context and kick off the job, we will return from here when the
// job is done (but possible on a different thread)
save_context(&impl_->context);
implementation::do_start(this);
if (!safe_)
{
// we are no longer suspending if we are here i.e. it is now safe for
// another thread to pick us up
safe_ = true;
}
else
{
// update counter if another fiber was waiting on us
if (counter_ != nullptr)
{
(*counter_)--;
}
}
}
void Fiber::suspend()
{
*this_fiber() = parent_fiber_;
implementation::do_suspend(this);
// if we get here then we have been resumed, so rethrow any stored
// exception
if (exception_)
{
std::rethrow_exception(exception_);
}
}
void Fiber::resume()
{
// bookkeeping
parent_fiber_ = *this_fiber();
*this_fiber() = this;
implementation::do_resume(this);
if (!safe_)
{
// we are no longer suspending if we are here i.e. it is now safe for
// another thread to pick us up
safe_ = true;
}
else
{
// update counter if another fiber was waiting on us
if (counter_ != nullptr)
{
(*counter_)--;
}
}
}
bool Fiber::is_safe() const
{
return safe_;
}
void Fiber::set_unsafe()
{
safe_ = false;
}
bool Fiber::is_being_waited_on() const
{
return counter_ != nullptr;
}
std::exception_ptr Fiber::exception() const
{
return exception_;
}
void Fiber::thread_to_fiber()
{
expect(*this_fiber() == nullptr, "thread already a fiber");
// thread will clean this up when it ends
*this_fiber() = new Fiber(nullptr);
}
Fiber **Fiber::this_fiber()
{
// this allows us to get a pointer to the fiber being executed in the
// current thread
thread_local Fiber *current_fiber = nullptr;
return ¤t_fiber;
}
}