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 
50  std::unique_ptr<detail::Fiber> main;
51 
53  std::unique_ptr<detail::Fiber> switch_trampoline;
54 
56  detail::Fiber* current = nullptr;
57 
59  std::unique_ptr<::Fiber> shared_stack;
60 
62  std::vector<std::unique_ptr<Fiber>> cache;
63 };
64 
68 struct StackBuffer {
74  StackBuffer(const ::Fiber* fiber) : _fiber(fiber) {}
75 
77  ~StackBuffer();
78 
84  std::pair<char*, char*> activeRegion() const;
85 
90  std::pair<char*, char*> allocatedRegion() const;
91 
97  size_t activeSize() const;
98 
100  size_t allocatedSize() const { return static_cast<size_t>(allocatedRegion().second - allocatedRegion().first); }
101 
103  size_t liveRemainingSize() const;
104 
106  void save();
107 
112  void restore() const;
113 
114 private:
115  const ::Fiber* _fiber;
116  void* _buffer = nullptr; // allocated memory holding swapped out stack content
117  size_t _buffer_size = 0; // amount currently allocated for `_buffer`
118 };
119 
120 // Render stack region for use in debug output.
121 inline std::ostream& operator<<(std::ostream& out, const StackBuffer& s) {
122  out << fmt("%p-%p:%zu", s.activeRegion().first, s.activeRegion().second, s.activeSize());
123  return out;
124 }
125 
127 class Callback {
128 public:
129  template<typename F>
130  Callback(F f)
131  : _f(std::move(f)), _invoke([](const hilti::rt::any& f, resumable::Handle* h) -> hilti::rt::any {
132  return hilti::rt::any_cast<F>(f)(h);
133  }) {}
134 
135  Callback(const Callback&) = default;
136  Callback(Callback&&) = default;
137 
138  Callback& operator=(const Callback&) = default;
139  Callback& operator=(Callback&&) = default;
140 
141  hilti::rt::any operator()(resumable::Handle* h) const { return _invoke(_f, h); }
142 
143 private:
144  hilti::rt::any _f; //< Type-erased storage for the concrete callback.
145  hilti::rt::any (*_invoke)(const hilti::rt::any& f, resumable::Handle* h); //< Invoke type-erased callback.
146 };
147 
154 class Fiber {
155 public:
157  enum class Type : int64_t {
159  SharedStack,
161  Main,
163  };
164 
165  Fiber(Type type);
166  ~Fiber();
167 
168  Fiber(const Fiber&) = delete;
169  Fiber(Fiber&&) = delete;
170  Fiber& operator=(const Fiber&) = delete;
171  Fiber& operator=(Fiber&&) = delete;
172 
173  void init(Callback f) {
174  _result = {};
175  _exception = nullptr;
176  _function = std::move(f);
177  }
178 
180  auto type() { return _type; }
181 
183  const auto& stackBuffer() const { return _stack_buffer; }
184 
185  void run();
186  void yield();
187  void resume();
188  void abort();
189 
190  bool isMain() const { return _type == Type::Main; }
191 
192  bool isDone() {
193  switch ( _state ) {
194  case State::Running:
195  case State::Yielded: return false;
196 
197  case State::Aborting:
198  case State::Finished:
199  case State::Idle:
200  case State::Init:
201  // All these mean we didn't recently run a function that could have
202  // produced a result still pending.
203  return true;
204  }
205  cannot_be_reached(); // For you, GCC.
206  }
207 
208  auto&& result() { return std::move(_result); }
209  std::exception_ptr exception() const { return _exception; }
210 
212  const char* location() const { return _location; }
213 
218  void setLocation(const char* location = nullptr) { _location = location; }
219 
220  std::string tag() const;
221 
222  static std::unique_ptr<Fiber> create();
223  static void destroy(std::unique_ptr<Fiber> f);
224  static void primeCache();
225  static void reset();
226 
227  struct Statistics {
228  uint64_t total;
229  uint64_t current;
230  uint64_t cached;
231  uint64_t max;
232  uint64_t max_stack_size;
233  uint64_t initialized;
234  };
235 
236  static Statistics statistics();
237 
238 private:
239  friend void ::__fiber_run_trampoline(void* argsp);
240  friend void ::__fiber_switch_trampoline(void* argsp);
241  friend void detail::trackStack();
242 
243  enum class State { Init, Running, Aborting, Yielded, Idle, Finished };
244 
245  void _yield(const char* tag);
246  void _activate(const char* tag);
247 
249  static void _startSwitchFiber(const char* tag, detail::Fiber* to);
250 
252  static void _finishSwitchFiber(const char* tag);
253 
255  static void _executeSwitch(const char* tag, detail::Fiber* from, detail::Fiber* to);
256 
257  Type _type;
258  State _state{State::Init};
259  std::optional<Callback> _function;
260  std::optional<hilti::rt::any> _result;
261  std::exception_ptr _exception;
262 
264  std::unique_ptr<::Fiber> _fiber;
265 
267  Fiber* _caller = nullptr;
268 
270  StackBuffer _stack_buffer;
271 
273  const char* _location = nullptr;
274 
275 #ifdef HILTI_HAVE_ASAN
277  struct {
278  const void* stack = nullptr;
279  size_t stack_size = 0;
280  void* fake_stack = nullptr;
281  } _asan;
282 #endif
283 
284  // TODO: Usage of these isn't thread-safe. Should become "atomic" and
285  // move into global state.
286  inline static uint64_t _total_fibers;
287  inline static uint64_t _current_fibers;
288  inline static uint64_t _cached_fibers;
289  inline static uint64_t _max_fibers;
290  inline static uint64_t _max_stack_size;
291  inline static uint64_t _initialized; // number of trampolines run
292 };
293 
294 std::ostream& operator<<(std::ostream& out, const Fiber& fiber);
295 
296 extern void yield();
297 
298 } // namespace detail
299 
305 class Resumable {
306 public:
313  template<typename Function>
315  requires(std::is_invocable_v<Function, resumable::Handle*>)
316  : _fiber(detail::Fiber::create()) {
317  _fiber->init(std::move(f));
318  }
319 
320  Resumable() = default;
321  Resumable(const Resumable& r) = delete;
322  Resumable(Resumable&& r) noexcept = default;
323  Resumable& operator=(const Resumable& other) = delete;
324  Resumable& operator=(Resumable&& other) noexcept = default;
325 
326  ~Resumable() {
327  if ( _fiber )
328  try {
329  detail::Fiber::destroy(std::move(_fiber));
330  } catch ( ... ) {
332  }
333  }
334 
336  void run();
337 
339  void resume();
340 
342  void abort();
343 
345  resumable::Handle* handle() { return _fiber.get(); }
346 
351  bool hasResult() const { return _done && _result.has_value(); }
352 
357  template<typename Result>
358  const Result& get() const {
359  assert(static_cast<bool>(_result));
360 
361  if constexpr ( std::is_same_v<Result, void> )
362  return {};
363  else {
364  try {
365  return hilti::rt::any_cast<const Result&>(*_result);
366  } catch ( const hilti::rt::bad_any_cast& ) {
367  throw InvalidArgument("mismatch in result type");
368  }
369  }
370  }
371 
373  explicit operator bool() const { return _done; }
374 
375 private:
376  void yielded();
377 
378  void checkFiber(const char* location) const {
379  if ( ! _fiber )
380  throw std::logic_error(std::string("fiber not set in ") + location);
381  }
382 
383  std::unique_ptr<detail::Fiber> _fiber;
384  bool _done = false;
385  std::optional<hilti::rt::any> _result;
386 };
387 
388 namespace resumable::detail {
389 
391 template<typename T>
392 auto copyArg(T t) {
393  // In general, we can't move references to the heap.
394  static_assert(! std::is_reference_v<T>, "copyArg() does not accept references other than ValueReference<T>.");
395  return t;
396 }
397 
398 // Special case: We don't want to (nor need to) deep-copy value references.
399 // Their payload already resides on the heap, so reuse that.
400 template<typename T>
401 ValueReference<T> copyArg(const ValueReference<T>& t) {
402  return ValueReference<T>(t.asSharedPtr());
403 }
404 
405 // Special case: We don't want to (nor need to) deep-copy value references.
406 // Their payload already resides on the heap, so reuse that.
407 template<typename T>
408 ValueReference<T> copyArg(ValueReference<T>& t) {
409  return ValueReference<T>(t.asSharedPtr());
410 }
411 
412 } // namespace resumable::detail
413 
414 namespace fiber {
415 
420 template<typename Function>
421 auto execute(Function f) {
422  Resumable r(std::move(f));
423  r.run();
424  return r;
425 }
426 
427 } // namespace fiber
428 } // namespace hilti::rt
Definition: function.h:18
Definition: result.h:71
Definition: fiber.h:305
void resume()
Definition: fiber.cc:631
resumable::Handle * handle()
Definition: fiber.h:345
void abort()
Definition: fiber.cc:642
const Result & get() const
Definition: fiber.h:358
Resumable(Function f) requires(std
Definition: fiber.h:314
void run()
Definition: fiber.cc:620
bool hasResult() const
Definition: fiber.h:351
Definition: fiber.h:127
Definition: fiber.h:154
void setLocation(const char *location=nullptr)
Definition: fiber.h:218
const auto & stackBuffer() const
Definition: fiber.h:183
Type
Definition: fiber.h:157
const char * location() const
Definition: fiber.h:212
auto type()
Definition: fiber.h:180
Definition: any.h:7
std::string fmt(const char *fmt, const Args &... args)
Definition: fmt.h:13
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:45
Definition: fiber.h:45
std::unique_ptr< detail::Fiber > switch_trampoline
Definition: fiber.h:53
std::vector< std::unique_ptr< Fiber > > cache
Definition: fiber.h:62
std::unique_ptr< detail::Fiber > main
Definition: fiber.h:50
detail::Fiber * current
Definition: fiber.h:56
std::unique_ptr<::Fiber > shared_stack
Definition: fiber.h:59
Definition: fiber.h:68
std::pair< char *, char * > activeRegion() const
Definition: fiber.cc:289
size_t liveRemainingSize() const
Definition: fiber.cc:308
void save()
Definition: fiber.cc:332
void restore() const
Definition: fiber.cc:360
std::pair< char *, char * > allocatedRegion() const
Definition: fiber.cc:303
~StackBuffer()
Definition: fiber.cc:287
size_t activeSize() const
Definition: fiber.cc:330
StackBuffer(const ::Fiber *fiber)
Definition: fiber.h:74
size_t allocatedSize() const
Definition: fiber.h:100