Spicy
fiber.h
1 // Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #include <csetjmp>
6 #include <memory>
7 #include <optional>
8 #include <string>
9 #include <type_traits>
10 #include <utility>
11 #include <vector>
12 
13 #include <hilti/rt/any.h>
14 #include <hilti/rt/autogen/config.h>
15 #include <hilti/rt/configuration.h>
16 #include <hilti/rt/exception.h>
17 #include <hilti/rt/types/reference.h>
18 #include <hilti/rt/util.h>
19 
20 struct Fiber;
21 
22 // Fiber entry point for execution of fiber payload.
23 extern "C" void __fiber_run_trampoline(void* args);
24 
25 // Fiber entry point for stack switch trampoline.
26 extern "C" void __fiber_switch_trampoline(void* args);
27 
28 namespace hilti::rt {
29 
30 namespace detail {
31 class Fiber;
32 } // namespace detail
33 
34 namespace resumable {
36 using Handle = detail::Fiber;
37 } // namespace resumable
38 
39 namespace detail {
40 
42 extern void trackStack();
43 
45 struct FiberContext {
46  FiberContext();
47  ~FiberContext();
48 
49  FiberContext(const FiberContext&) = delete;
50  FiberContext(FiberContext&&) = default;
51 
52  FiberContext& operator=(const FiberContext&) = delete;
53  FiberContext& operator=(FiberContext&&) = default;
54 
56  std::unique_ptr<detail::Fiber> main;
57 
59  std::unique_ptr<detail::Fiber> switch_trampoline;
60 
62  detail::Fiber* current = nullptr;
63 
65  std::unique_ptr<::Fiber> shared_stack;
66 
68  std::vector<std::unique_ptr<Fiber>> cache;
69 };
70 
74 struct StackBuffer {
80  StackBuffer(const ::Fiber* fiber) : _fiber(fiber) {}
81 
83  ~StackBuffer();
84 
90  std::pair<char*, char*> activeRegion() const;
91 
96  std::pair<char*, char*> allocatedRegion() const;
97 
103  size_t activeSize() const;
104 
106  size_t allocatedSize() const { return static_cast<size_t>(allocatedRegion().second - allocatedRegion().first); }
107 
109  size_t liveRemainingSize() const;
110 
112  void save();
113 
118  void restore() const;
119 
120 private:
121  const ::Fiber* _fiber;
122  void* _buffer = nullptr; // allocated memory holding swapped out stack content
123  size_t _buffer_size = 0; // amount currently allocated for `_buffer`
124 };
125 
126 // Render stack region for use in debug output.
127 inline std::ostream& operator<<(std::ostream& out, const StackBuffer& s) {
128  out << fmt("%p-%p:%zu", s.activeRegion().first, s.activeRegion().second, s.activeSize());
129  return out;
130 }
131 
133 class Callback {
134 public:
135  template<typename F>
136  Callback(F f)
137  : _f(std::move(f)), _invoke([](const hilti::rt::any& f, resumable::Handle* h) -> hilti::rt::any {
138  return hilti::rt::any_cast<F>(f)(h);
139  }) {}
140 
141  Callback(const Callback&) = default;
142  Callback(Callback&&) = default;
143 
144  Callback& operator=(const Callback&) = default;
145  Callback& operator=(Callback&&) = default;
146 
147  hilti::rt::any operator()(resumable::Handle* h) const { return _invoke(_f, h); }
148 
149 private:
150  hilti::rt::any _f; //< Type-erased storage for the concrete callback.
151  hilti::rt::any (*_invoke)(const hilti::rt::any& f, resumable::Handle* h); //< Invoke type-erased callback.
152 };
153 
160 class Fiber {
161 public:
163  enum class Type : int64_t {
165  SharedStack,
167  Main,
169  };
170 
171  Fiber(Type type);
172  ~Fiber();
173 
174  Fiber(const Fiber&) = delete;
175  Fiber(Fiber&&) = delete;
176  Fiber& operator=(const Fiber&) = delete;
177  Fiber& operator=(Fiber&&) = delete;
178 
179  void init(Callback f) {
180  _result = {};
181  _exception = nullptr;
182  _function = std::move(f);
183  }
184 
186  auto type() { return _type; }
187 
189  const auto& stackBuffer() const { return _stack_buffer; }
190 
191  void run();
192  void yield();
193  void resume();
194  void abort();
195 
196  bool isMain() const { return _type == Type::Main; }
197 
198  bool isDone() {
199  switch ( _state ) {
200  case State::Running:
201  case State::Yielded: return false;
202 
203  case State::Aborting:
204  case State::Finished:
205  case State::Idle:
206  case State::Init:
207  // All these mean we didn't recently run a function that could have
208  // produced a result still pending.
209  return true;
210  }
211  cannot_be_reached(); // For you, GCC.
212  }
213 
214  auto&& result() { return std::move(_result); }
215  std::exception_ptr exception() const { return _exception; }
216 
218  const char* location() const { return _location; }
219 
224  void setLocation(const char* location = nullptr) { _location = location; }
225 
226  std::string tag() const;
227 
228  static std::unique_ptr<Fiber> create();
229  static void destroy(std::unique_ptr<Fiber> f);
230  static void primeCache();
231  static void reset();
232 
233  struct Statistics {
234  uint64_t total;
235  uint64_t current;
236  uint64_t cached;
237  uint64_t max;
238  uint64_t max_stack_size;
239  uint64_t initialized;
240  };
241 
242  static Statistics statistics();
243 
244 private:
245  friend void ::__fiber_run_trampoline(void* argsp);
246  friend void ::__fiber_switch_trampoline(void* argsp);
247  friend void detail::trackStack();
248 
249  enum class State { Init, Running, Aborting, Yielded, Idle, Finished };
250 
251  void _yield(const char* tag);
252  void _activate(const char* tag);
253 
255  static void _startSwitchFiber(const char* tag, detail::Fiber* to);
256 
258  static void _finishSwitchFiber(const char* tag);
259 
261  static void _executeSwitch(const char* tag, detail::Fiber* from, detail::Fiber* to);
262 
263  Type _type;
264  State _state{State::Init};
265  std::optional<Callback> _function;
266  std::optional<hilti::rt::any> _result;
267  std::exception_ptr _exception;
268 
270  std::unique_ptr<::Fiber> _fiber;
271 
273  Fiber* _caller = nullptr;
274 
276  StackBuffer _stack_buffer;
277 
279  const char* _location = nullptr;
280 
281 #ifdef _WIN32
283  struct {
284  void* stack_base = nullptr;
285  void* stack_limit = nullptr;
286  void* deallocation_stack = nullptr;
287  } _teb;
288 #endif
289 
290 #ifdef HILTI_HAVE_ASAN
292  struct {
293  const void* stack = nullptr;
294  size_t stack_size = 0;
295  void* fake_stack = nullptr;
296  } _asan;
297 #endif
298 
299  // TODO: Usage of these isn't thread-safe. Should become "atomic" and
300  // move into global state.
301  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _total_fibers;
302  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _current_fibers;
303  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _cached_fibers;
304  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _max_fibers;
305  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _max_stack_size;
306  HILTI_JIT_IMPORT_OR_INLINE static uint64_t _initialized; // number of trampolines run
307 };
308 
309 std::ostream& operator<<(std::ostream& out, const Fiber& fiber);
310 
311 extern void yield();
312 
313 } // namespace detail
314 
320 class Resumable {
321 public:
328  template<typename Function>
330  requires(std::is_invocable_v<Function, resumable::Handle*>)
331  : _fiber(detail::Fiber::create()) {
332  _fiber->init(std::move(f));
333  }
334 
335  Resumable() = default;
336  Resumable(const Resumable& r) = delete;
337  Resumable(Resumable&& r) noexcept = default;
338  Resumable& operator=(const Resumable& other) = delete;
339  Resumable& operator=(Resumable&& other) noexcept = default;
340 
341  ~Resumable() {
342  if ( _fiber )
343  try {
344  detail::Fiber::destroy(std::move(_fiber));
345  } catch ( ... ) {
347  }
348  }
349 
351  void run();
352 
354  void resume();
355 
357  void abort();
358 
360  resumable::Handle* handle() { return _fiber.get(); }
361 
366  bool hasResult() const { return _done && _result.has_value(); }
367 
372  template<typename Result>
373  const Result& get() const {
374  assert(static_cast<bool>(_result));
375 
376  if constexpr ( std::is_same_v<Result, void> )
377  return {};
378  else {
379  try {
380  return hilti::rt::any_cast<const Result&>(*_result);
381  } catch ( const hilti::rt::bad_any_cast& ) {
382  throw InvalidArgument("mismatch in result type");
383  }
384  }
385  }
386 
388  explicit operator bool() const { return _done; }
389 
390 private:
391  void yielded();
392 
393  void checkFiber(const char* location) const {
394  if ( ! _fiber )
395  throw std::logic_error(std::string("fiber not set in ") + location);
396  }
397 
398  std::unique_ptr<detail::Fiber> _fiber;
399  bool _done = false;
400  std::optional<hilti::rt::any> _result;
401 };
402 
403 namespace resumable::detail {
404 
406 template<typename T>
407 auto copyArg(T t) {
408  // In general, we can't move references to the heap.
409  static_assert(! std::is_reference_v<T>, "copyArg() does not accept references other than ValueReference<T>.");
410  return t;
411 }
412 
413 // Special case: We don't want to (nor need to) deep-copy value references.
414 // Their payload already resides on the heap, so reuse that.
415 template<typename T>
416 ValueReference<T> copyArg(const ValueReference<T>& t) {
417  return ValueReference<T>(t.asSharedPtr());
418 }
419 
420 // Special case: We don't want to (nor need to) deep-copy value references.
421 // Their payload already resides on the heap, so reuse that.
422 template<typename T>
423 ValueReference<T> copyArg(ValueReference<T>& t) {
424  return ValueReference<T>(t.asSharedPtr());
425 }
426 
427 } // namespace resumable::detail
428 
429 namespace fiber {
430 
435 template<typename Function>
436 auto execute(Function f) {
437  Resumable r(std::move(f));
438  r.run();
439  return r;
440 }
441 
442 } // namespace fiber
443 } // namespace hilti::rt
Definition: function.h:19
Definition: result.h:73
Definition: fiber.h:320
void resume()
Definition: fiber.cc:752
resumable::Handle * handle()
Definition: fiber.h:360
void abort()
Definition: fiber.cc:763
const Result & get() const
Definition: fiber.h:373
Resumable(Function f) requires(std
Definition: fiber.h:329
void run()
Definition: fiber.cc:741
bool hasResult() const
Definition: fiber.h:366
Definition: fiber.h:133
Definition: fiber.h:160
void setLocation(const char *location=nullptr)
Definition: fiber.h:224
const auto & stackBuffer() const
Definition: fiber.h:189
Type
Definition: fiber.h:163
const char * location() const
Definition: fiber.h:218
auto type()
Definition: fiber.h:186
Definition: any.h:7
void location(const char *x)
Definition: logging.h:126
std::string fmt(const char *fmt, const Args &... args)
Definition: fmt.h:17
void cannot_be_reached()
Definition: util.cc:53
Definition: fiber.h:45
std::unique_ptr< detail::Fiber > switch_trampoline
Definition: fiber.h:59
std::vector< std::unique_ptr< Fiber > > cache
Definition: fiber.h:68
std::unique_ptr< detail::Fiber > main
Definition: fiber.h:56
detail::Fiber * current
Definition: fiber.h:62
std::unique_ptr<::Fiber > shared_stack
Definition: fiber.h:65
Definition: fiber.h:74
std::pair< char *, char * > activeRegion() const
Definition: fiber.cc:379
size_t liveRemainingSize() const
Definition: fiber.cc:401
void save()
Definition: fiber.cc:428
void restore() const
Definition: fiber.cc:459
std::pair< char *, char * > allocatedRegion() const
Definition: fiber.cc:396
~StackBuffer()
Definition: fiber.cc:377
size_t activeSize() const
Definition: fiber.cc:426
StackBuffer(const ::Fiber *fiber)
Definition: fiber.h:80
size_t allocatedSize() const
Definition: fiber.h:106