Zth (libzth)
context.h
Go to the documentation of this file.
1 #ifndef ZTH_CONTEXT_CONTEXT_H
2 #define ZTH_CONTEXT_CONTEXT_H
3 /*
4  * Zth (libzth), a cooperative userspace multitasking library.
5  * Copyright (C) 2019-2022 Jochem Rutgers
6  *
7  * This Source Code Form is subject to the terms of the Mozilla Public
8  * License, v. 2.0. If a copy of the MPL was not distributed with this
9  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
10  */
11 
20 #ifdef __cplusplus
21 
22 # include "libzth/macros.h"
23 
24 # include "libzth/util.h"
25 # include "libzth/allocator.h"
26 # include "libzth/context.h"
27 # include "libzth/worker.h"
28 
29 # include <setjmp.h>
30 # include <unistd.h>
31 
32 # ifndef ZTH_OS_WINDOWS
33 # include <signal.h>
34 # endif
35 
36 # ifdef ZTH_HAVE_MMAN
37 # include <sys/mman.h>
38 # ifndef MAP_STACK
39 # define MAP_STACK 0
40 # endif
41 # endif
42 
43 # ifdef ZTH_HAVE_VALGRIND
44 # include <valgrind/memcheck.h>
45 # endif
46 
47 # ifdef ZTH_ENABLE_ASAN
48 # include <sanitizer/common_interface_defs.h>
49 # endif
50 
51 namespace zth {
52 namespace impl {
53 
70 template <typename Impl>
71 class ContextBase {
73 protected:
74  constexpr explicit ContextBase(ContextAttr const& attr) noexcept
75  : m_attr(attr)
76 # ifdef ZTH_ENABLE_ASAN
77  , m_alive(true)
78 # endif
79  {}
80 
85  {
86  // Make sure destroy() was called before.
87  zth_assert(m_stack.p == nullptr);
88  }
89 
93  Impl& impl() noexcept
94  {
95  return static_cast<Impl&>(*this);
96  }
97 
101  Impl const& impl() const noexcept
102  {
103  return static_cast<Impl const&>(*this);
104  }
105 
106 public:
110  struct Stack {
111  constexpr Stack(void* p, size_t size) noexcept
112  : p(static_cast<char*>(p))
113  , size(size)
114  {}
115 
116  constexpr explicit Stack(size_t size = 0) noexcept
117  : p()
118  , size(size)
119  {}
120 
121  constexpr operator bool() const noexcept
122  {
123  return p != nullptr && size > 0;
124  }
125 
126  char* p;
127  size_t size;
128  };
129 
133  static int init() noexcept
134  {
135  return 0;
136  }
137 
141  static void deinit() noexcept {}
142 
146  ContextAttr& attr() noexcept
147  {
148  return m_attr;
149  }
150 
154  ContextAttr const& attr() const noexcept
155  {
156  return m_attr;
157  }
158 
162  int create() noexcept
163  {
164  int res = 0;
165 
166  m_stack = Stack(attr().stackSize);
167  m_stackUsable = Stack();
168 
169  if(attr().stackSize == 0)
170  // No stack requested.
171  return 0;
172 
173  // Allocate stack.
174  if((res = impl().initStack(m_stack, m_stackUsable)))
175  return res;
176 
177  // Apply guards.
178  impl().valgrindRegister();
179 
180  if(unlikely((res = impl().stackGuardInit()))) {
181  // Uh, oh...
182  impl().deallocStack(m_stack);
183  return res;
184  }
185 
186  return 0;
187  }
188 
192  void destroy() noexcept
193  {
194  impl().stackGuardDeinit();
195  impl().valgrindDeregister();
196  impl().deinitStack(m_stack);
197  m_stackUsable = m_stack = Stack();
198  }
199 
203  int initStack(Stack& stack, Stack& usable) noexcept
204  {
205  stack.size = impl().calcStackSize(stack.size);
206 
207  size_t const page = impl().pageSize();
208  zth_assert(__builtin_popcount((unsigned int)page) == 1);
209 
210  // Round up to full pages.
211  stack.size = (stack.size + page - 1u) & ~(page - 1u);
212 
213  // Add another page size to allow arbitrary allocation location.
214  stack.size += page;
215 
216  if(unlikely(!(stack.p = (char*)impl().allocStack(stack.size))))
217  return errno;
218 
219  // Compute usable offsets.
220  usable = stack;
221  impl().stackAlign(usable);
222 
223  stack_watermark_init(usable.p, usable.size);
224  return 0;
225  }
226 
230  void deinitStack(Stack& stack) noexcept
231  {
232  impl().deallocStack(stack);
233  }
234 
238  static size_t pageSize() noexcept
239  {
240  static size_t pz =
241 # ifdef ZTH_HAVE_MMAN
242  (size_t)sysconf(_SC_PAGESIZE);
243 # else
244  sizeof(void*);
245 # endif
246  return pz;
247  }
248 
252  Stack const& stack() const noexcept
253  {
254  return m_stack;
255  }
256 
260  Stack const& stackUsable() const noexcept
261  {
262  return m_stackUsable;
263  }
264 
268  size_t calcStackSize(size_t size) noexcept
269  {
270 # ifndef ZTH_OS_WINDOWS
271  size += MINSIGSTKSZ;
272 # endif
273 
274 # ifdef ZTH_HAVE_MMAN
276  // Both ends of the stack are guarded using mprotect().
277  size += impl().pageSize() * 2;
278 # endif
279 
280  return size;
281  }
282 
290  void* allocStack(size_t size) noexcept
291  {
292  void* p = nullptr;
293 
294 # ifdef ZTH_HAVE_MMAN
295  // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-cstyle-cast)
296  p =
297  mmap(nullptr, size, PROT_READ | PROT_WRITE,
298  MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
299 
300  if(unlikely(p == MAP_FAILED))
301  return nullptr;
302 # else
303  if(unlikely((p = (void*)allocate_noexcept<char>(size)) == nullptr)) {
304  errno = ENOMEM;
305  return nullptr;
306  }
307 # endif
308 
309  return p;
310  }
311 
315  void deallocStack(Stack& stack) noexcept
316  {
317  if(!stack.p)
318  return;
319 
320 # ifdef ZTH_HAVE_MMAN
321  munmap(stack.p, stack.size);
322 # else
323  deallocate(static_cast<char*>(stack.p), stack.size);
324 # endif
325 
326  stack.p = nullptr;
327  }
328 
332  void stackAlign(Stack& stack) noexcept
333  {
334  if(!stack.p || !stack.size)
335  // Nothing to align.
336  return;
337 
338  size_t const ps = impl().pageSize();
339 
340  // Align to page size.
341  uintptr_t stack_old = (uintptr_t)stack.p;
342  // calcStackSize() should have taken care of this.
343  zth_assert(stack.size > ps);
344 
345  // The stack should not wrap.
346  zth_assert(stack_old + stack.size > stack_old);
347 
348  uintptr_t stack_new = ((uintptr_t)(stack_old + ps - 1u) & ~(ps - 1u));
349  stack.size = (stack.size - (stack_new - stack_old)) & ~(ps - 1u);
350 
351 # ifdef ZTH_HAVE_MMAN
353  // Do not use the pages at both sides.
354  zth_assert(stack.size > ps * 2u);
355  stack_new += ps;
356  stack.size -= ps * 2u;
357  }
358 # endif
359 
360  zth_assert(stack.size > 0);
361  stack.p = (char*)stack_new;
362  }
363 
367  void valgrindRegister() noexcept
368  {
369 # ifdef ZTH_USE_VALGRIND
370  if(!m_stackUsable.p)
371  return;
372 
373  m_valgrind_stack_id = VALGRIND_STACK_REGISTER(
374  m_stackUsable.p, m_stackUsable.p + m_stackUsable.size - 1u);
375 
376  if(RUNNING_ON_VALGRIND)
377  zth_dbg(context, "[%s] Stack of context %p has Valgrind id %u",
378  currentWorker().id_str(), this, m_valgrind_stack_id);
379 # endif
380  }
381 
385  void valgrindDeregister() noexcept
386  {
387 # ifdef ZTH_USE_VALGRIND
388  VALGRIND_STACK_DEREGISTER(m_valgrind_stack_id);
389  m_valgrind_stack_id = 0;
390 # endif
391  }
392 
396  int stackGuardInit() noexcept
397  {
398 # ifdef ZTH_HAVE_MMAN
400  if(!m_stackUsable.p)
401  return 0;
402 
403  size_t const ps = impl().pageSize();
404  // Guard both ends of the stack.
405  if(unlikely(mprotect(m_stackUsable.p - ps, ps, PROT_NONE)))
406  return errno;
407  if(unlikely(mprotect(m_stackUsable.p + m_stackUsable.size, ps, PROT_NONE)))
408  return errno;
409  }
410 # endif
411  return 0;
412  }
413 
417  void stackGuardDeinit() noexcept
418  {
419  // Automatically released when mprotected area is munmapped.
420  }
421 
427  __attribute__((noinline)) static bool stackGrowsDown(void const* reference)
428  {
429  int i = 42;
430  return reinterpret_cast<uintptr_t>(&i) < reinterpret_cast<uintptr_t>(reference);
431  }
432 
438  void stackGuard() noexcept
439  {
440  impl().stackGuard(stackUsable());
441  }
442 
446  void stackGuard(Stack const& UNUSED_PAR(stack)) noexcept {}
447 
452  void* stackGuard(void* UNUSED_PAR(p)) noexcept
453  {
454  return nullptr;
455  }
456 
460  void context_switch(Context& to) noexcept;
461 
465  static void** sp(Stack const& stack) noexcept;
466 
470  static void stack_push(void**& sp, void* p) noexcept;
471 
475  static void set_sp(jmp_buf& env, void** sp) noexcept;
476 
480  static void set_pc(jmp_buf& env, void* sp) noexcept;
481 
492 
496  void context_push_regs() noexcept {}
497 
501  void context_pop_regs() noexcept {}
502 
506  void context_prepare_jmp(Impl& UNUSED_PAR(to), jmp_buf& UNUSED_PAR(env)) noexcept {}
507 
511  void die() noexcept
512  {
513 # ifdef ZTH_ENABLE_ASAN
514  m_alive = false;
515 # endif
516  }
517 
521  bool alive() const noexcept
522  {
523 # ifdef ZTH_ENABLE_ASAN
524  return m_alive;
525 # else
526  return true;
527 # endif
528  }
529 
531  void* UNUSED_PAR(stack), size_t UNUSED_PAR(size), void* (*f)(void*) noexcept,
532  void* arg) noexcept
533  {
534  // cppcheck-suppress CastIntegerToAddressAtReturn
535  return f(arg);
536  }
537 
538 private:
540  ContextAttr m_attr;
542  Stack m_stack;
544  Stack m_stackUsable;
545 # ifdef ZTH_USE_VALGRIND
547  unsigned int m_valgrind_stack_id;
548 # endif
549 # ifdef ZTH_ENABLE_ASAN
551  bool m_alive;
552 # endif
553 };
554 
555 template <typename Impl>
556 static Impl* current_context() noexcept;
557 
558 } // namespace impl
559 } // namespace zth
560 
561 # if defined(ZTH_ARCH_ARM)
562 # include "libzth/context/arch_arm.h"
563 # else
565 # endif
566 
567 extern "C" void context_entry(zth::Context* context) noexcept __attribute__((noreturn, used));
568 
569 # if defined(ZTH_CONTEXT_SIGALTSTACK)
571 # elif defined(ZTH_CONTEXT_SJLJ)
572 # include "libzth/context/sjlj.h"
573 # elif defined(ZTH_CONTEXT_UCONTEXT)
574 # include "libzth/context/ucontext.h"
575 # elif defined(ZTH_CONTEXT_WINFIBER)
576 # include "libzth/context/winfiber.h"
577 # else
578 # error Unknown context switching approach.
579 # endif
580 
581 #endif // __cplusplus
582 #endif // ZTH_CONTEXT_CONTEXT_H
Base class of the Context.
Definition: context.h:71
static void stack_push(void **&sp, void *p) noexcept
Push data into the stack.
void stackGuardDeinit() noexcept
Release the guards around the memory.
Definition: context.h:417
static size_t pageSize() noexcept
Get system's page size.
Definition: context.h:238
static bool stackGrowsDown(void const *reference)
Checks if the stack grows down or up.
Definition: context.h:427
void context_pop_regs() noexcept
Post-sjlj context restoring.
Definition: context.h:501
Stack const & stack() const noexcept
Return the stack address.
Definition: context.h:252
static void deinit() noexcept
Final system cleanup.
Definition: context.h:141
int initStack(Stack &stack, Stack &usable) noexcept
Allocate and initialize stack.
Definition: context.h:203
void * stack_switch(void *stack, size_t size, void *(*f)(void *) noexcept, void *arg) noexcept
Definition: context.h:530
int stackGuardInit() noexcept
Initialize guards around the stack memory.
Definition: context.h:396
void die() noexcept
Flag fiber as died after it returned from context_entry().
Definition: context.h:511
void stackGuard() noexcept
Configure the guard for the current fiber.
Definition: context.h:438
static void set_sp(jmp_buf &env, void **sp) noexcept
Set the stack pointer in a jmp_buf.
Impl & impl() noexcept
Return Impl this.
Definition: context.h:93
static void context_trampoline_from_jmp_buf()
Entry point to jump to from a (sig)jmp_buf.
void context_prepare_jmp(Impl &to, jmp_buf &env) noexcept
Pre-sjlj jump.
Definition: context.h:506
static void set_pc(jmp_buf &env, void *sp) noexcept
Set the program counter in a jmp_buf.
ContextAttr const & attr() const noexcept
Return the context attributes, requested by the user.
Definition: context.h:154
int create() noexcept
Create context.
Definition: context.h:162
void * allocStack(size_t size) noexcept
Allocate requested size of stack memory.
Definition: context.h:290
void deallocStack(Stack &stack) noexcept
Frees the previously allocated stack.
Definition: context.h:315
void * stackGuard(void *p) noexcept
Configure the guard for the address.
Definition: context.h:452
void valgrindRegister() noexcept
Register the current stack to valgrind.
Definition: context.h:367
static void ** sp(Stack const &stack) noexcept
Get the initial stack pointer for the given stack.
Impl const & impl() const noexcept
Return Impl this.
Definition: context.h:101
void context_switch(Context &to) noexcept
Perform a context switch.
ContextAttr & attr() noexcept
Return the context attributes, requested by the user.
Definition: context.h:146
void stackGuard(Stack const &stack) noexcept
Configure the guard for the given stack.
Definition: context.h:446
void destroy() noexcept
Destroy and cleanup context.
Definition: context.h:192
Stack const & stackUsable() const noexcept
Return the start of the actual usable stack.
Definition: context.h:260
void deinitStack(Stack &stack) noexcept
Deinit and free stack.
Definition: context.h:230
void stackAlign(Stack &stack) noexcept
Compute and modify the stack alignment and size, within the allocated space.
Definition: context.h:332
void valgrindDeregister() noexcept
Deregister the current stack from valgrind.
Definition: context.h:385
bool alive() const noexcept
Check if fiber is still running.
Definition: context.h:521
size_t calcStackSize(size_t size) noexcept
Compute the stack size, given the requested user size and current configuration.
Definition: context.h:268
constexpr ContextBase(ContextAttr const &attr) noexcept
Definition: context.h:74
void context_push_regs() noexcept
Pre-sjlj context saving.
Definition: context.h:496
static int init() noexcept
One-time system initialization.
Definition: context.h:133
#define MAP_STACK
Definition: context.h:39
Worker & currentWorker() noexcept
Return the (thread-local) singleton Worker instance.
Definition: worker.h:388
void stack_watermark_init(void *stack, size_t size) noexcept
Initialize the memory region for stack high water marking.
Definition: context.cpp:245
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition: util.h:210
#define UNUSED_PAR(name)
Definition: macros.h:79
Definition: allocator.h:23
void context_entry(Context *context)
static bool const EnableStackGuard
When true, enable stack guards.
Definition: config.h:135
Stack information.
Definition: context.h:110
constexpr Stack(size_t size=0) noexcept
Definition: context.h:116
constexpr Stack(void *p, size_t size) noexcept
Definition: context.h:111
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition: util.h:236
#define ZTH_CLASS_NOCOPY(Class)
Definition: util.h:254
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition: util.h:56