Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
stack_posix.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <lxsameer@lxsameer.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#include <stdint.h>
20
22#include "serene/rt/fiber.h"
24#include "serene/utils.h"
25
26// POSIX stack backend, mmap-based allocation with a guard page. On a target without mmap (Windows)
27// this file compiles to nothing and a sibling backend should supply srn_fiber_stack_alloc/free
28// instead. The selection is by target macro, so the whole runtime source set can be handed to a
29// cross compiler and only the matching backend emits symbols.
30#if defined(__unix__) || defined(__APPLE__)
31
32# include <sys/mman.h>
33
34# ifdef __APPLE__
35# define STACK_FLAGS MAP_PRIVATE | MAP_ANONYMOUS
36# else
37# define STACK_FLAGS MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_STACK
38# endif
39
40// TODO(lxsameer): move from fixed stacks to growable stacks once a precise GC
41// and stack maps exist. A growable stack sizes itself to the live call depth
42// and grows on demand, which removes both the overflow gap and the worst case
43// reservation each fiber pays now. Growing a running stack means relocating
44// it, which needs a stack map to find and rewrite every live pointer, so it
45// depends on the GC landing first.
46
47// NOLINTBEGIN(hicpp-sign*, performance-no-int*)
48srn_fiber_stack_t srn_fiber_stack_alloc(size_t size, size_t guard_pages) {
49 const size_t os_page_size = srn_mm_get_os_page_size();
50
51 // The guard band catches most stack overflows but not all of them. A stack
52 // overflows one page at a time when it grows through ordinary calls, or when
53 // the compiler probes each page (-fstack-clash-protection, which the runtime
54 // builds with). Either way the growing stack steps onto the band and faults.
55 // The gap is a single function whose frame is larger than the whole band and
56 // was built without page probing. Such a frame can jump past the band in one
57 // move and write into the next mapping without faulting. A wider band makes
58 // that jump less likely, but only page probing in the offending code can rule
59 // it out, and foreign code is not always built that way.
60 const size_t guard_size = guard_pages * os_page_size;
61 const size_t desired_size = size ? size : SRN_CONFIG_DEFAULT_FIBER_STACK_SIZE;
62
63 // Reject sizes the page rounding below would wrap. A wrapped size would
64 // silently grant a tiny stack instead of failing.
66 desired_size > SIZE_MAX - guard_size - os_page_size, "Requested fiber stack size is too large"
67 );
68
69 // Round upto a multiple of PAGE size
70 const size_t usable = (desired_size + os_page_size - 1) & ~(os_page_size - 1);
71 // Add the guard band below the usable region
72 const size_t total = usable + guard_size;
73
74 // Since stack grows downward, the starting point where mmap returns will be the end of the stack
75 // for us. We could of used MAP_GROWSDOWN to change the behaviour, but that causes the stack to
76 // grow, but we have a fix stack.
77 void *end = mmap(
78 nullptr, total, PROT_READ | PROT_WRITE, STACK_FLAGS,
79 // according to the man page, for anonymous mode fd should be -1
80 -1,
81 // according to the man page, for anonymous mode offset should be 0
82 0
83 );
84
85 PANIC_IF(end == MAP_FAILED, "Failed to allocate the stack for the fiber subsystem");
86
87 if (mprotect(end, guard_size, PROT_NONE) == -1) {
88 munmap(end, total);
89 PANIC("Failed to allocate the guard band for the fiber subsystem");
90 }
91
92 const uintptr_t start = (uintptr_t)end + (uintptr_t)total;
93 const uintptr_t limit = (uintptr_t)end + (uintptr_t)guard_size;
94
95 srn_fiber_stack_t stack = {.start = (void *)start, .limit = (void *)limit, .guard = end};
96 return stack;
97}
98
100 munmap(stack.guard, (uintptr_t)stack.start - (uintptr_t)stack.guard);
101}
102// NOLINTEND(hicpp-sign*, performance-no-int*)
103
104#endif // POSIX stack backend
static atomic_int total
Definition 05_parallel.c:40
The single place that holds every runtime knob.
#define SRN_CONFIG_DEFAULT_FIBER_STACK_SIZE
Size of every fiber stack, in bytes.
size_t srn_mm_get_os_page_size(void)
Retutrns the OS page size.
Definition default.c:312
AI Generated (🤦) Fiber subsystem overview.
srn_fiber_stack_t srn_fiber_stack_alloc(size_t size, size_t guard_pages)
Allocate a stack of at least size usable bytes, or SRN_CONFIG_DEFAULT_FIBER_STACK_SIZE when size is 0...
void srn_fiber_stack_free(srn_fiber_stack_t stack)
One stack per fiber, mapped with a guard band at the low end so an overflow faults deterministically ...
Definition fiber.h:207
void * guard
Low base of the protected guard band that detects stack overflows.
Definition fiber.h:213
void * start
High end, stack pointer initialises to this address.
Definition fiber.h:209
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define PANIC(msg)
Definition utils.h:53