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 if(unlikely((res = impl().stackGuardInit()))) {
155 // Uh, oh...
156 impl().deallocStack(m_stack);
157 return res;
158 }
159
160 return 0;
161 }
162
166 void destroy() noexcept
167 {
168 impl().stackGuardDeinit();
169 impl().valgrindDeregister();
170 impl().deinitStack(m_stack);
171 m_stackUsable = m_stack = Stack();
172 }
173
177 int initStack(Stack& stack, Stack& usable) noexcept
178 {
179 stack.size = impl().calcStackSize(stack.size);
180
181 size_t const page = impl().pageSize();
182 zth_assert(__builtin_popcount((unsigned int)page) == 1);
183
184 // Round up to full pages.
185 stack.size = (stack.size + page - 1U) & ~(page - 1U);
186
187 // Add another page size to allow arbitrary allocation location.
188 stack.size += page;
189
190 if(unlikely(!(stack.p = static_cast<char*>(impl().allocStack(stack.size)))))
191 return errno;
192
193 // Compute usable offsets.
194 usable = stack;
195 impl().stackAlign(usable);
196
197 stack_watermark_init(usable.p, usable.size);
198 return 0;
199 }
200
204 void deinitStack(Stack& stack) noexcept
205 {
206 impl().deallocStack(stack);
207 }
208
212 static size_t pageSize() noexcept
213 {
214 static size_t pz =
215# ifdef ZTH_HAVE_MMAN
216 (size_t)sysconf(_SC_PAGESIZE);
217# else
218 sizeof(void*);
219# endif
220 return pz;
221 }
222
226 Stack const& stack() const noexcept
227 {
228 return m_stack;
229 }
230
234 Stack const& stackUsable() const noexcept
235 {
236 return m_stackUsable;
237 }
238
242 size_t calcStackSize(size_t size) noexcept
243 {
244# ifndef ZTH_OS_WINDOWS
245 size += MINSIGSTKSZ;
246# endif
247
248# ifdef ZTH_HAVE_MMAN
250 // Both ends of the stack are guarded using mprotect().
251 size += impl().pageSize() * 2;
252# endif
253
254 return size;
255 }
256
264 void* allocStack(size_t size) noexcept
265 {
266 void* p = nullptr;
267
268# ifdef ZTH_HAVE_MMAN
269 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-cstyle-cast)
270 p =
271 mmap(nullptr, size, PROT_READ | PROT_WRITE,
272 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
273
274 if(unlikely(p == MAP_FAILED))
275 return nullptr;
276# else
277 if(unlikely((p = static_cast<void*>(allocate_noexcept<char>(size))) == nullptr)) {
278 errno = ENOMEM;
279 return nullptr;
280 }
281# endif
282
283 return p;
284 }
285
289 void deallocStack(Stack& stack) noexcept
290 {
291 if(!stack.p)
292 return;
293
294# ifdef ZTH_HAVE_MMAN
295 munmap(stack.p, stack.size);
296# else
297 deallocate(static_cast<char*>(stack.p), stack.size);
298# endif
299
300 stack.p = nullptr;
301 }
302
306 void stackAlign(Stack& stack) noexcept
307 {
308 if(!stack.p || !stack.size)
309 // Nothing to align.
310 return;
311
312 size_t const ps = impl().pageSize();
313
314 // Align to page size.
315 uintptr_t stack_old = (uintptr_t)stack.p;
316 // calcStackSize() should have taken care of this.
317 zth_assert(stack.size > ps);
318
319 // The stack should not wrap.
320 zth_assert(stack_old + stack.size > stack_old);
321
322 uintptr_t stack_new = ((uintptr_t)(stack_old + ps - 1U) & ~(ps - 1U));
323 stack.size = (stack.size - (stack_new - stack_old)) & ~(ps - 1U);
324
325# ifdef ZTH_HAVE_MMAN
327 // Do not use the pages at both sides.
328 zth_assert(stack.size > ps * 2U);
329 stack_new += ps;
330 stack.size -= ps * 2U;
331 }
332# endif
333
334 zth_assert(stack.size > 0);
335 stack.p = reinterpret_cast<char*>(stack_new); // NOLINT
336 }
337
341 void valgrindRegister() noexcept
342 {
343# ifdef ZTH_USE_VALGRIND
344 if(!m_stackUsable.p)
345 return;
346
347 m_valgrind_stack_id = VALGRIND_STACK_REGISTER(
348 m_stackUsable.p, m_stackUsable.p + m_stackUsable.size - 1U);
349
350 if(RUNNING_ON_VALGRIND)
351 zth_dbg(context, "[%s] Stack of context %p has Valgrind id %u",
352 currentWorker().id_str(), this, m_valgrind_stack_id);
353# endif
354 }
355
359 void valgrindDeregister() noexcept
360 {
361# ifdef ZTH_USE_VALGRIND
362 VALGRIND_STACK_DEREGISTER(m_valgrind_stack_id);
363 m_valgrind_stack_id = 0;
364# endif
365 }
366
370 int stackGuardInit() noexcept
371 {
372# ifdef ZTH_HAVE_MMAN
374 if(!m_stackUsable.p)
375 return 0;
376
377 size_t const ps = impl().pageSize();
378 // Guard both ends of the stack.
379 if(unlikely(mprotect(m_stackUsable.p - ps, ps, PROT_NONE)))
380 return errno;
381 if(unlikely(mprotect(m_stackUsable.p + m_stackUsable.size, ps, PROT_NONE)))
382 return errno;
383 }
384# endif
385 return 0;
386 }
387
391 void stackGuardDeinit() noexcept
392 {
393 // Automatically released when mprotected area is munmapped.
394 }
395
401 __attribute__((noinline)) static bool stackGrowsDown(void const* reference)
402 {
403 int i = 42;
404 return reinterpret_cast<uintptr_t>(&i) < reinterpret_cast<uintptr_t>(reference);
405 }
406
412 void stackGuard() noexcept
413 {
414 impl().stackGuard(stackUsable());
415 }
416
420 void stackGuard(Stack const& UNUSED_PAR(stack)) noexcept {}
421
426 // cppcheck-suppress constParameterPointer
427 void* stackGuard(void* UNUSED_PAR(p)) noexcept
428 {
429 return nullptr;
430 }
431
435 void context_switch(Context& to) noexcept;
436
440 static void** sp(Stack const& stack) noexcept;
441
445 static void stack_push(void**& sp, void* p) noexcept;
446
450 static void set_sp(jmp_buf& env, void** sp) noexcept;
451
455 static void set_pc(jmp_buf& env, void* sp) noexcept;
456
466 static void context_trampoline_from_jmp_buf() noexcept;
467
471 void context_push_regs() noexcept {}
472
476 void context_pop_regs() noexcept {}
477
481 // cppcheck-suppress constParameterReference
482 void context_prepare_jmp(Impl& UNUSED_PAR(to), jmp_buf& UNUSED_PAR(env)) noexcept {}
483
487 void die() noexcept
488 {
489# ifdef ZTH_ENABLE_ASAN
490 m_alive = false;
491# endif
492 }
493
497 bool alive() const noexcept
498 {
499# ifdef ZTH_ENABLE_ASAN
500 return m_alive;
501# else
502 return true;
503# endif
504 }
505
507 void* UNUSED_PAR(stack), size_t UNUSED_PAR(size), void* (*f)(void*) noexcept,
508 void* arg) noexcept
509 {
510 // cppcheck-suppress CastIntegerToAddressAtReturn
511 return f(arg);
512 }
513
514private:
516 ContextAttr m_attr;
518 Stack m_stack;
520 Stack m_stackUsable;
521# ifdef ZTH_USE_VALGRIND
523 unsigned int m_valgrind_stack_id;
524# endif
525# ifdef ZTH_ENABLE_ASAN
527 bool m_alive;
528# endif
529};
530
531template <typename Impl>
532static Impl* current_context() noexcept;
533
534} // namespace impl
535} // namespace zth
536
537# if defined(ZTH_ARCH_ARM)
539# else
541# endif
542
543extern "C" void context_entry(zth::Context* context) noexcept __attribute__((noreturn, used));
544
545# if defined(ZTH_CONTEXT_SIGALTSTACK)
547# elif defined(ZTH_CONTEXT_SJLJ)
548# include "libzth/context/sjlj.h"
549# elif defined(ZTH_CONTEXT_UCONTEXT)
551# elif defined(ZTH_CONTEXT_WINFIBER)
553# else
554# error Unknown context switching approach.
555# endif
556
557#endif // __cplusplus
558#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:391
static size_t pageSize() noexcept
Get system's page size.
Definition context.h:212
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:401
void context_pop_regs() noexcept
Post-sjlj context restoring.
Definition context.h:476
Stack const & stack() const noexcept
Return the stack address.
Definition context.h:226
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:177
void * stack_switch(void *stack, size_t size, void *(*f)(void *) noexcept, void *arg) noexcept
Definition context.h:506
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:427
int stackGuardInit() noexcept
Initialize guards around the stack memory.
Definition context.h:370
void die() noexcept
Flag fiber as died after it returned from context_entry().
Definition context.h:487
void stackGuard() noexcept
Configure the guard for the current fiber.
Definition context.h:412
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:482
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:289
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:341
Stack const & stackUsable() const noexcept
Return the start of the actual usable stack.
Definition context.h:234
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:420
void destroy() noexcept
Destroy and cleanup context.
Definition context.h:166
void deinitStack(Stack &stack) noexcept
Deinit and free stack.
Definition context.h:204
void stackAlign(Stack &stack) noexcept
Compute and modify the stack alignment and size, within the allocated space.
Definition context.h:306
void valgrindDeregister() noexcept
Deregister the current stack from valgrind.
Definition context.h:359
void * allocStack(size_t size) noexcept
Allocate requested size of stack memory.
Definition context.h:264
bool alive() const noexcept
Check if fiber is still running.
Definition context.h:497
size_t calcStackSize(size_t size) noexcept
Compute the stack size, given the requested user size and current configuration.
Definition context.h:242
constexpr ContextBase(ContextAttr const &attr) noexcept
Definition context.h:71
void context_push_regs() noexcept
Pre-sjlj context saving.
Definition context.h:471
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
Worker & currentWorker() noexcept
Return the (thread-local) singleton Worker instance.
Definition worker.h:407
void stack_watermark_init(void *stack, size_t size) noexcept
Initialize the memory region for stack high water marking.
Definition context.cpp:228
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:194
#define UNUSED_PAR(name)
Definition macros.h:78
void context_entry(Context *context)
static bool const EnableStackGuard
When true, enable stack guards.
Definition config.h:142
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