Spicy
fiber.h
1 // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #include <csetjmp>
6 #include <functional>
7 #include <iostream>
8 #include <memory>
9 #include <optional>
10 #include <string>
11 #include <type_traits>
12 #include <utility>
13 #include <vector>
14 
15 #include <hilti/rt/any.h>
16 #include <hilti/rt/exception.h>
17 #include <hilti/rt/lambda.h>
18 #include <hilti/rt/types/reference.h>
19 #include <hilti/rt/util.h>
20 
21 struct Fiber;
22 
23 // Fiber entry point for execution of fiber payload.
24 extern "C" void __fiber_run_trampoline(void* args);
25 
26 // Fiber entry point for stack switch trampoline.
27 extern "C" void __fiber_switch_trampoline(void* args);
28 
29 namespace hilti::rt {
30 
31 namespace detail {
32 class Fiber;
33 } // namespace detail
34 
35 namespace resumable {
37 using Handle = detail::Fiber;
38 } // namespace resumable
39 
40 namespace detail {
41 
43 struct FiberContext {
44  FiberContext();
45  ~FiberContext();
46 
48  std::unique_ptr<detail::Fiber> main;
49 
51  std::unique_ptr<detail::Fiber> switch_trampoline;
52 
54  detail::Fiber* current = nullptr;
55 
57  std::unique_ptr<::Fiber> shared_stack;
58 
60  std::vector<std::unique_ptr<Fiber>> cache;
61 };
62 
66 struct StackBuffer {
72  StackBuffer(const ::Fiber* fiber) : _fiber(fiber) {}
73 
75  ~StackBuffer();
76 
82  std::pair<char*, char*> activeRegion() const;
83 
88  std::pair<char*, char*> allocatedRegion() const;
89 
95  size_t activeSize() const { return static_cast<size_t>(activeRegion().second - activeRegion().first); }
96 
98  size_t allocatedSize() const { return static_cast<size_t>(allocatedRegion().second - allocatedRegion().first); }
99 
101  size_t liveRemainingSize() const;
102 
104  void save();
105 
110  void restore() const;
111 
112 private:
113  const ::Fiber* _fiber;
114  void* _buffer = nullptr; // allocated memory holding swapped out stack content
115 };
116 
117 // Render stack region for use in debug output.
118 inline std::ostream& operator<<(std::ostream& out, const StackBuffer& s) {
119  out << fmt("%p-%p:%zu", s.activeRegion().first, s.activeRegion().second, s.activeSize());
120  return out;
121 }
122 
123 
130 class Fiber {
131 public:
133  enum class Type : int64_t {
134  IndividualStack,
135  SharedStack,
137  Main,
138  SwitchTrampoline,
139  };
140 
141  Fiber(Type type);
142  ~Fiber();
143 
144  Fiber(const Fiber&) = delete;
145  Fiber(Fiber&&) = delete;
146  Fiber& operator=(const Fiber&) = delete;
147  Fiber& operator=(Fiber&&) = delete;
148 
149  void init(Lambda<hilti::rt::any(resumable::Handle*)> f) {
150  _result = {};
151  _exception = nullptr;
152  _function = std::move(f);
153  }
154 
156  auto type() { return _type; }
157 
159  const auto& stackBuffer() const { return _stack_buffer; }
160 
161  void run();
162  void yield();
163  void resume();
164  void abort();
165 
166  bool isMain() const { return _type == Type::Main; }
167 
168  bool isDone() {
169  switch ( _state ) {
170  case State::Running:
171  case State::Yielded: return false;
172 
173  case State::Aborting:
174  case State::Finished:
175  case State::Idle:
176  case State::Init:
177  // All these mean we didn't recently run a function that could have
178  // produced a result still pending.
179  return true;
180  }
181  cannot_be_reached(); // For you, GCC.
182  }
183 
184  auto&& result() { return std::move(_result); }
185  std::exception_ptr exception() const { return _exception; }
186 
187  std::string tag() const;
188 
189  static std::unique_ptr<Fiber> create();
190  static void destroy(std::unique_ptr<Fiber> f);
191  static void primeCache();
192  static void reset();
193 
194  struct Statistics {
195  uint64_t total;
196  uint64_t current;
197  uint64_t cached;
198  uint64_t max;
199  uint64_t initialized;
200  };
201 
202  static Statistics statistics();
203 
204 private:
205  friend void ::__fiber_run_trampoline(void* argsp);
206  friend void ::__fiber_switch_trampoline(void* argsp);
207 
208  enum class State { Init, Running, Aborting, Yielded, Idle, Finished };
209 
210  void _yield(const char* tag);
211  void _activate(const char* tag);
212 
214  static void _startSwitchFiber(const char* tag, detail::Fiber* to);
215 
217  static void _finishSwitchFiber(const char* tag);
218 
220  static void _executeSwitch(const char* tag, detail::Fiber* from, detail::Fiber* to);
221 
222  Type _type;
223  State _state{State::Init};
224  std::optional<Lambda<hilti::rt::any(resumable::Handle*)>> _function;
225  std::optional<hilti::rt::any> _result;
226  std::exception_ptr _exception;
227 
229  std::unique_ptr<::Fiber> _fiber;
230 
232  Fiber* _caller = nullptr;
233 
235  StackBuffer _stack_buffer;
236 
237 #ifdef HILTI_HAVE_SANITIZER
238 
239  struct {
240  const void* stack = nullptr;
241  size_t stack_size = 0;
242  void* fake_stack = nullptr;
243  } _asan;
244 #endif
245 
246  // TODO: Usage of these isn't thread-safe. Should become "atomic" and
247  // move into global state.
248  inline static uint64_t _total_fibers;
249  inline static uint64_t _current_fibers;
250  inline static uint64_t _cached_fibers;
251  inline static uint64_t _max_fibers;
252  inline static uint64_t _initialized; // number of trampolines run
253 };
254 
255 std::ostream& operator<<(std::ostream& out, const Fiber& fiber);
256 
257 extern void yield();
258 
265 extern void checkStack();
266 
267 } // namespace detail
268 
274 class Resumable {
275 public:
282  template<typename Function, typename = std::enable_if_t<std::is_invocable<Function, resumable::Handle*>::value>>
283  Resumable(Function f) : _fiber(detail::Fiber::create()) {
284  _fiber->init(std::move(f));
285  }
286 
287  Resumable() = default;
288  Resumable(const Resumable& r) = delete;
289  Resumable(Resumable&& r) noexcept = default;
290  Resumable& operator=(const Resumable& other) = delete;
291  Resumable& operator=(Resumable&& other) noexcept = default;
292 
293  ~Resumable() {
294  if ( _fiber )
295  detail::Fiber::destroy(std::move(_fiber));
296  }
297 
299  void run();
300 
302  void resume();
303 
305  void abort();
306 
308  resumable::Handle* handle() { return _fiber.get(); }
309 
314  bool hasResult() const { return _done && _result.has_value(); }
315 
320  template<typename Result>
321  const Result& get() const {
322  assert(static_cast<bool>(_result));
323 
324  if constexpr ( std::is_same<Result, void>::value )
325  return {};
326  else {
327  try {
328  return hilti::rt::any_cast<const Result&>(*_result);
329  } catch ( const hilti::rt::bad_any_cast& ) {
330  throw InvalidArgument("mismatch in result type");
331  }
332  }
333  }
334 
336  explicit operator bool() const { return _done; }
337 
338 private:
339  void yielded();
340 
341  void checkFiber(const char* location) const {
342  if ( ! _fiber )
343  throw std::logic_error(std::string("fiber not set in ") + location);
344  }
345 
346  std::unique_ptr<detail::Fiber> _fiber;
347  bool _done = false;
348  std::optional<hilti::rt::any> _result;
349 };
350 
351 namespace resumable::detail {
352 
354 template<typename T>
355 auto copyArg(T t) {
356  // In general, we can't move references to the heap.
357  static_assert(! std::is_reference<T>::value, "copyArg() does not accept references other than ValueReference<T>.");
358  return t;
359 }
360 
361 // Special case: We don't want to (nor need to) deep-copy value references.
362 // Their payload already resides on the heap, so reuse that.
363 template<typename T>
364 const ValueReference<T> copyArg(const ValueReference<T>& t) {
365  return ValueReference<T>(t.asSharedPtr());
366 }
367 
368 // Special case: We don't want to (nor need to) deep-copy value references.
369 // Their payload already resides on the heap, so reuse that.
370 template<typename T>
372  return ValueReference<T>(t.asSharedPtr());
373 }
374 
375 } // namespace resumable::detail
376 
377 namespace fiber {
378 
383 template<typename Function>
384 auto execute(Function f) {
385  Resumable r(std::move(f));
386  r.run();
387  return r;
388 }
389 
390 } // namespace fiber
391 } // namespace hilti::rt
void init()
Definition: init.cc:20
std::shared_ptr< T > asSharedPtr() const
Definition: reference.h:121
std::unique_ptr<::Fiber > shared_stack
Definition: fiber.h:57
size_t activeSize() const
Definition: fiber.h:95
Definition: any.h:7
StackBuffer(const ::Fiber *fiber)
Definition: fiber.h:72
Type
Definition: fiber.h:133
Definition: fiber.h:43
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:52
Definition: function.h:44
void run()
Definition: fiber.cc:547
const auto & stackBuffer() const
Definition: fiber.h:159
std::unique_ptr< detail::Fiber > main
Definition: fiber.h:48
auto type()
Definition: fiber.h:156
std::pair< char *, char * > activeRegion() const
Definition: fiber.cc:248
size_t allocatedSize() const
Definition: fiber.h:98
Definition: reference.h:47
std::unique_ptr< detail::Fiber > switch_trampoline
Definition: fiber.h:51
std::vector< std::unique_ptr< Fiber > > cache
Definition: fiber.h:60
Definition: fiber.h:274
resumable::Handle * handle()
Definition: fiber.h:308
Definition: location.h:93
Definition: lambda.h:77
bool hasResult() const
Definition: fiber.h:314
Definition: fiber.h:66
Definition: result.h:67
std::string fmt(const char *fmt, const Args &... args)
Definition: fmt.h:13
Resumable(Function f)
Definition: fiber.h:283
Definition: fiber.h:130