Zth (libzth)
Loading...
Searching...
No Matches
context.h
Go to the documentation of this file.
1#ifndef ZTH_CONTEXT_CONTEXT_H
2#define ZTH_CONTEXT_CONTEXT_H
3/*
4 * SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 */
8
17#ifdef __cplusplus
18
19# include "libzth/macros.h"
20
21# include "libzth/allocator.h"
22# include "libzth/context.h"
23# include "libzth/util.h"
24# include "libzth/worker.h"
25
26# include <setjmp.h>
27# include <unistd.h>
28
29# ifndef ZTH_OS_WINDOWS
30# include <signal.h>
31# endif
32
33# ifdef ZTH_HAVE_MMAN
34# include <sys/mman.h>
35# ifndef MAP_STACK
36# define MAP_STACK 0
37# endif
38# endif
39
40# ifdef ZTH_HAVE_VALGRIND
41# include <valgrind/memcheck.h>
42# endif
43
44# ifdef ZTH_ENABLE_ASAN
45# include <sanitizer/common_interface_defs.h>
46# endif
47
48namespace zth {
49namespace impl {
50
67template <typename Impl>
70protected:
71 constexpr explicit ContextBase(ContextAttr const& attr) noexcept
72 : m_attr(attr)
73# ifdef ZTH_ENABLE_ASAN
74 , m_alive(true)
75# endif
76 {}
77
82 {
83 // Make sure destroy() was called before.
84 zth_assert(m_stack.p == nullptr);
85 }
86
90 Impl& impl() noexcept
91 {
92 return static_cast<Impl&>(*this);
93 }
94
98 Impl const& impl() const noexcept
99 {
100 return static_cast<Impl const&>(*this);
101 }
102
103public:
107 static int init() noexcept
108 {
109 return 0;
110 }
111
115 static void deinit() noexcept {}
116
120 ContextAttr& attr() noexcept
121 {
122 return m_attr;
123 }
124
128 ContextAttr const& attr() const noexcept
129 {
130 return m_attr;
131 }
132
136 int create() noexcept
137 {
138 int res = 0;
139
140 m_stack = Stack(attr().stackSize);
141 m_stackUsable = Stack();
142
143 if(attr().stackSize == 0)
144 // No stack requested.
145 return 0;
146
147 // Allocate stack.
148 if((res = impl().initStack(m_stack, m_stackUsable)))
149 return res;
150
151 // Apply guards.
152 impl().valgrindRegister();
153
154 // cppcheck-suppress knownConditionTrueFalse
155 if(unlikely((res = impl().stackGuardInit()))) {
156 // Uh, oh...
157 impl().deallocStack(m_stack);
158 return res;
159 }
160
161 return 0;
162 }
163
167 void destroy() noexcept
168 {
169 impl().stackGuardDeinit();
170 impl().valgrindDeregister();
171 impl().deinitStack(m_stack);
172 m_stackUsable = m_stack = Stack();
173 }
174
178 int initStack(Stack& stack, Stack& usable) noexcept
179 {
180 stack.size = impl().calcStackSize(stack.size);
181
182 size_t const page = impl().pageSize();
183 zth_assert(__builtin_popcount((unsigned int)page) == 1);
184
185 // Round up to full pages.
186 stack.size = (stack.size + page - 1U) & ~(page - 1U);
187
188 // Add another page size to allow arbitrary allocation location.
189 stack.size += page;
190
191 if(unlikely(!(stack.p = static_cast<char*>(impl().allocStack(stack.size)))))
192 return errno;
193
194 // Compute usable offsets.
195 usable = stack;
196 impl().stackAlign(usable);
197
198 stack_watermark_init(usable.p, usable.size);
199 return 0;
200 }
201
205 void deinitStack(Stack& stack) noexcept
206 {
207 impl().deallocStack(stack);
208 }
209
213 static size_t pageSize() noexcept
214 {
215 static size_t pz =
216# ifdef ZTH_HAVE_MMAN
217 (size_t)sysconf(_SC_PAGESIZE);
218# else
219 sizeof(void*);
220# endif
221 return pz;
222 }
223
227 Stack const& stack() const noexcept
228 {
229 return m_stack;
230 }
231
235 Stack const& stackUsable() const noexcept
236 {
237 return m_stackUsable;
238 }
239
243 size_t calcStackSize(size_t size) noexcept
244 {
245# ifndef ZTH_OS_WINDOWS
246 size += MINSIGSTKSZ;
247# endif
248
249# ifdef ZTH_HAVE_MMAN
251 // Both ends of the stack are guarded using mprotect().
252 size += impl().pageSize() * 2;
253# endif
254
255 return size;
256 }
257
265 void* allocStack(size_t size) noexcept
266 {
267 void* p = nullptr;
268
269# ifdef ZTH_HAVE_MMAN
270 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-cstyle-cast)
271 p =
272 mmap(nullptr, size, PROT_READ | PROT_WRITE,
273 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
274
275 if(unlikely(p == MAP_FAILED))
276 return nullptr;
277# else
278 if(unlikely((p = static_cast<void*>(allocate_noexcept<char>(size))) == nullptr)) {
279 errno = ENOMEM;
280 return nullptr;
281 }
282# endif
283
284 return p;
285 }
286
290 void deallocStack(Stack& stack) noexcept
291 {
292 if(!stack.p)
293 return;
294
295# ifdef ZTH_HAVE_MMAN
296 munmap(stack.p, stack.size);
297# else
298 deallocate(static_cast<char*>(stack.p), stack.size);
299# endif
300
301 stack.p = nullptr;
302 }
303
307 void stackAlign(Stack& stack) noexcept
308 {
309 if(!stack.p || !stack.size)
310 // Nothing to align.
311 return;
312
313 size_t const ps = impl().pageSize();
314
315 // Align to page size.
316 uintptr_t stack_old = (uintptr_t)stack.p;
317 // calcStackSize() should have taken care of this.
318 zth_assert(stack.size > ps);
319
320 // The stack should not wrap.
321 zth_assert(stack_old + stack.size > stack_old);
322
323 uintptr_t stack_new = ((uintptr_t)(stack_old + ps - 1U) & ~(ps - 1U));
324 stack.size = (stack.size - (stack_new - stack_old)) & ~(ps - 1U);
325
326# ifdef ZTH_HAVE_MMAN
328 // Do not use the pages at both sides.
329 zth_assert(stack.size > ps * 2U);
330 stack_new += ps;
331 stack.size -= ps * 2U;
332 }
333# endif
334
335 zth_assert(stack.size > 0);
336 stack.p = reinterpret_cast<char*>(stack_new); // NOLINT
337 }
338
342 void valgrindRegister() noexcept
343 {
344# ifdef ZTH_USE_VALGRIND
345 if(!m_stackUsable.p)
346 return;
347
348 m_valgrind_stack_id = VALGRIND_STACK_REGISTER(
349 m_stackUsable.p, m_stackUsable.p + m_stackUsable.size - 1U);
350
351 if(RUNNING_ON_VALGRIND)
352 zth_dbg(context, "[%s] Stack of context %p has Valgrind id %u",
353 currentWorker().id_str(), this, m_valgrind_stack_id);
354# endif
355 }
356
360 void valgrindDeregister() noexcept
361 {
362# ifdef ZTH_USE_VALGRIND
363 VALGRIND_STACK_DEREGISTER(m_valgrind_stack_id);
364 m_valgrind_stack_id = 0;
365# endif
366 }
367
371 int stackGuardInit() noexcept
372 {
373# ifdef ZTH_HAVE_MMAN
375 if(!m_stackUsable.p)
376 return 0;
377
378 size_t const ps = impl().pageSize();
379 // Guard both ends of the stack.
380 if(unlikely(mprotect(m_stackUsable.p - ps, ps, PROT_NONE)))
381 return errno;
382 if(unlikely(mprotect(m_stackUsable.p + m_stackUsable.size, ps, PROT_NONE)))
383 return errno;
384 }
385# endif
386 return 0;
387 }
388
392 void stackGuardDeinit() noexcept
393 {
394 // Automatically released when mprotected area is munmapped.
395 }
396
402 __attribute__((noinline)) static bool stackGrowsDown(void const* reference)
403 {
404 int i = 42;
405 return reinterpret_cast<uintptr_t>(&i) < reinterpret_cast<uintptr_t>(reference);
406 }
407
413 void stackGuard() noexcept
414 {
415 impl().stackGuard(stackUsable());
416 }
417
421 void stackGuard(Stack const& UNUSED_PAR(stack)) noexcept {}
422
427 // cppcheck-suppress constParameterPointer
428 void* stackGuard(void* UNUSED_PAR(p)) noexcept
429 {
430 return nullptr;
431 }
432
436 void context_switch(Context& to) noexcept;
437
441 static void** sp(Stack const& stack) noexcept;
442
446 static void stack_push(void**& sp, void* p) noexcept;
447
451 static void set_sp(jmp_buf& env, void** sp) noexcept;
452
456 static void set_pc(jmp_buf& env, void* sp) noexcept;
457
467 static void context_trampoline_from_jmp_buf() noexcept;
468
472 void context_push_regs() noexcept {}
473
477 void context_pop_regs() noexcept {}
478
482 // cppcheck-suppress constParameterReference
483 void context_prepare_jmp(Impl& UNUSED_PAR(to), jmp_buf& UNUSED_PAR(env)) noexcept {}
484
488 void die() noexcept
489 {
490# ifdef ZTH_ENABLE_ASAN
491 m_alive = false;
492# endif
493 }
494
498 bool alive() const noexcept
499 {
500# ifdef ZTH_ENABLE_ASAN
501 return m_alive;
502# else
503 return true;
504# endif
505 }
506
508 void* UNUSED_PAR(stack), size_t UNUSED_PAR(size), void* (*f)(void*) noexcept,
509 void* arg) noexcept
510 {
511 // cppcheck-suppress CastIntegerToAddressAtReturn
512 return f(arg);
513 }
514
515private:
517 ContextAttr m_attr;
519 Stack m_stack;
521 Stack m_stackUsable;
522# ifdef ZTH_USE_VALGRIND
524 unsigned int m_valgrind_stack_id;
525# endif
526# ifdef ZTH_ENABLE_ASAN
528 bool m_alive;
529# endif
530};
531
532template <typename Impl>
533static Impl* current_context() noexcept;
534
535} // namespace impl
536} // namespace zth
537
538# if defined(ZTH_ARCH_ARM)
540# else
542# endif
543
544extern "C" void context_entry(zth::Context* context) noexcept __attribute__((noreturn, used));
545
546# if defined(ZTH_CONTEXT_SIGALTSTACK)
548# elif defined(ZTH_CONTEXT_SJLJ)
549# include "libzth/context/sjlj.h"
550# elif defined(ZTH_CONTEXT_UCONTEXT)
552# elif defined(ZTH_CONTEXT_WINFIBER)
554# else
555# error Unknown context switching approach.
556# endif
557
558#endif // __cplusplus
559#endif // ZTH_CONTEXT_CONTEXT_H
Base class of the Context.
Definition context.h:68
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:392
static size_t pageSize() noexcept
Get system's page size.
Definition context.h:213
Impl & impl() noexcept
Return Impl this.
Definition context.h:90
static bool stackGrowsDown(void const *reference)
Checks if the stack grows down or up.
Definition context.h:402
void context_pop_regs() noexcept
Post-sjlj context restoring.
Definition context.h:477
Stack const & stack() const noexcept
Return the stack address.
Definition context.h:227
static void deinit() noexcept
Final system cleanup.
Definition context.h:115
int initStack(Stack &stack, Stack &usable) noexcept
Allocate and initialize stack.
Definition context.h:178
void * stack_switch(void *stack, size_t size, void *(*f)(void *) noexcept, void *arg) noexcept
Definition context.h:507
static void ** sp(Stack const &stack) noexcept
Get the initial stack pointer for the given stack.
void * stackGuard(void *p) noexcept
Configure the guard for the address.
Definition context.h:428
int stackGuardInit() noexcept
Initialize guards around the stack memory.
Definition context.h:371
void die() noexcept
Flag fiber as died after it returned from context_entry().
Definition context.h:488
void stackGuard() noexcept
Configure the guard for the current fiber.
Definition context.h:413
static void set_sp(jmp_buf &env, void **sp) noexcept
Set the stack pointer in a jmp_buf.
void context_prepare_jmp(Impl &to, jmp_buf &env) noexcept
Pre-sjlj jump.
Definition context.h:483
static void set_pc(jmp_buf &env, void *sp) noexcept
Set the program counter in a jmp_buf.
int create() noexcept
Create context.
Definition context.h:136
void deallocStack(Stack &stack) noexcept
Frees the previously allocated stack.
Definition context.h:290
ContextAttr & attr() noexcept
Return the context attributes, requested by the user.
Definition context.h:120
Impl const & impl() const noexcept
Return Impl this.
Definition context.h:98
static void context_trampoline_from_jmp_buf() noexcept
Entry point to jump to from a (sig)jmp_buf.
void valgrindRegister() noexcept
Register the current stack to valgrind.
Definition context.h:342
Stack const & stackUsable() const noexcept
Return the start of the actual usable stack.
Definition context.h:235
void context_switch(Context &to) noexcept
Perform a context switch.
void stackGuard(Stack const &stack) noexcept
Configure the guard for the given stack.
Definition context.h:421
void destroy() noexcept
Destroy and cleanup context.
Definition context.h:167
void deinitStack(Stack &stack) noexcept
Deinit and free stack.
Definition context.h:205
void stackAlign(Stack &stack) noexcept
Compute and modify the stack alignment and size, within the allocated space.
Definition context.h:307
void valgrindDeregister() noexcept
Deregister the current stack from valgrind.
Definition context.h:360
void * allocStack(size_t size) noexcept
Allocate requested size of stack memory.
Definition context.h:265
bool alive() const noexcept
Check if fiber is still running.
Definition context.h:498
size_t calcStackSize(size_t size) noexcept
Compute the stack size, given the requested user size and current configuration.
Definition context.h:243
constexpr ContextBase(ContextAttr const &attr) noexcept
Definition context.h:71
void context_push_regs() noexcept
Pre-sjlj context saving.
Definition context.h:472
static int init() noexcept
One-time system initialization.
Definition context.h:107
ContextAttr const & attr() const noexcept
Return the context attributes, requested by the user.
Definition context.h:128
#define MAP_STACK
Definition context.h:36
void context_entry(zth::Context *context) noexcept
Entry point of the fiber.
Definition context.cpp:164
Worker & currentWorker() noexcept
Return the (thread-local) singleton Worker instance.
Definition worker.h:417
void stack_watermark_init(void *stack, size_t size) noexcept
Initialize the memory region for stack high water marking.
Definition context.cpp:233
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:194
#define UNUSED_PAR(name)
Definition macros.h:79
static bool const EnableStackGuard
When true, enable stack guards.
Definition config.h:179
Stack information.
Definition context.h:89
char * p
Definition context.h:105
size_t size
Definition context.h:106
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:217
#define ZTH_CLASS_NOCOPY(Class)
Definition util.h:234
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:60