Serene Runtime 1.0.0
C runtime for the Serene programming language
Loading...
Searching...
No Matches
fiber.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
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 "serene/rt/fiber.h"
20
21#include "serene/rt/context.h"
22#include "serene/utils.h"
23
24#include <string.h>
25
26#if SRN_ASAN
27/// Declared directly rather than via <sanitizer/common_interface_defs.h> to
28/// avoid a hard dependency on the sanitizer headers. These tell ASan when
29/// execution moves between stacks, so it does not mistake a fiber switch for
30/// corruption.
31// NOLINTBEGIN(bugprone-*, cert-dcl*)
32extern void __sanitizer_start_switch_fiber(void **fake_save, const void *bottom, size_t size);
33extern void __sanitizer_finish_switch_fiber(void *fake_save, const void **bottom_old,
34 size_t *size_old);
35// NOLINTEND(bugprone-*, cert-dcl*)
36#endif
37
38#if SRN_TSAN
39/// Declared directly rather than via <sanitizer/tsan_interface.h> to avoid a
40/// hard dependency on the sanitizer headers. TSan models each fiber as its own
41/// execution context. Switching tells it which one is now running, so once
42/// fibers migrate between threads it does not read one fiber's stack accesses as
43/// another's.
44// NOLINTBEGIN(bugprone-*, cert-dcl*)
45extern void *__tsan_get_current_fiber(void);
46extern void *__tsan_create_fiber(unsigned flags);
47extern void __tsan_destroy_fiber(void *fiber);
48extern void __tsan_switch_to_fiber(void *fiber, unsigned flags);
49// NOLINTEND(bugprone-*, cert-dcl*)
50#endif
51
52// -----------------------------------------------------------------------------
53// Fiber management
54// -----------------------------------------------------------------------------
55
56/// Compiled without AddressSanitizer instrumentation: in stack-use-after-return
57/// mode ASan would place `from`/`to` on a fake stack that
58/// __sanitizer_start_switch_fiber releases before srn_fiber_swap reads them.
59/// Not instrumented by either sanitizer: the stack swaps mid-function, which
60/// confuses ASan's fake stack and TSan's shadow stack. The explicit annotations
61/// keep each sanitizer's fiber tracking correct across the swap instead.
62[[gnu::no_sanitize_address]] [[gnu::no_sanitize_thread]] void srn_fiber_switch(srn_fiber_t *from,
63 srn_fiber_t *to) {
64
65#if SRN_ASAN
66 const size_t size = srn_fiber_stack_size(to->stack);
67 __sanitizer_start_switch_fiber(&from->fake_stack, to->stack.limit, size);
68#endif
69#if SRN_TSAN
70 // Move TSan's notion of the running fiber to `to` before the stack swaps. A
71 // fiber built outside srn_fiber_make / srn_fiber_init_thread has no handle
72 // and is simply not tracked. Only the low-level switch tests build such a
73 // fiber. Every fiber the scheduler runs has one.
74 if (to->tsan_fiber != nullptr) {
75 __tsan_switch_to_fiber(to->tsan_fiber, 0);
76 }
77#endif
79#if SRN_ASAN
80 __sanitizer_finish_switch_fiber(from->fake_stack, nullptr, nullptr);
81#endif
82}
83
84[[gnu::no_sanitize_address]] [[gnu::no_sanitize_thread]] void
86#if SRN_ASAN
87 // A nullptr fake-stack tells ASan the current fiber is finished and will not
88 // resume, so it can discard the bookkeeping rather than leak it.
89 const size_t size = srn_fiber_stack_size(to->stack);
90 __sanitizer_start_switch_fiber(nullptr, to->stack.limit, size);
91#endif
92#if SRN_TSAN
93 // The finished fiber's handle is released later, at reap. Here just move
94 // TSan to `to` before the swap, when `to` has a handle (see
95 // srn_fiber_switch).
96 if (to->tsan_fiber != nullptr) {
97 __tsan_switch_to_fiber(to->tsan_fiber, 0);
98 }
99#endif
100 // srn_fiber_swap always writes the outgoing sp somewhere. This fiber is done,
101 // so discard it. Control loads `to` and never comes back.
102 srn_fiber_ctx_t discard;
103 srn_fiber_swap(&discard, &to->fiber_ctx);
105}
106
107[[gnu::no_sanitize_address]] void srn_fiber_on_entry(srn_fiber_t *from) {
108#if SRN_ASAN
109 // A nullptr fake-stack: a fresh fiber has no previously saved bookkeeping.
110 // The out-params report the stack this fiber was started from -- this is the
111 // one chance to learn `from`'s bounds (there is no portable way to query a
112 // thread's own stack), and a later switch back to `from` needs them, so
113 // record them. They flow through the sanitizer rather than libc.
114 const void *bottom = nullptr;
115 size_t size = 0;
116 __sanitizer_finish_switch_fiber(nullptr, &bottom, &size);
117
118 if (from != nullptr) {
119 // ASan reports the came-from stack as (bottom, size). The struct keeps the
120 // low and high boundaries, so limit = bottom and start = bottom + size.
121 from->stack.limit = (void *)bottom;
122 from->stack.start = (char *)bottom + size;
123 }
124#else
125 UNUSED(from);
126#endif
127}
128
130#if SRN_TSAN
131 __tsan_destroy_fiber(fiber->tsan_fiber);
132#else
133 UNUSED(fiber);
134#endif
135}
136
138 // Represent the calling thread as the running fiber. Its stack bounds are not
139 // queried here (there is no portable way). Under the sanitizer the first
140 // fiber's srn_fiber_on_entry records them. The saved context (fiber_ctx)
141 // stays empty, and the first switch away from the thread fills it.
142 memset(f, 0, sizeof(*f));
144 f->name = "thread";
145#if SRN_TSAN
146 // The loop fiber stands in for this OS thread, so it takes the thread's own
147 // current TSan fiber rather than a freshly created one.
148 f->tsan_fiber = __tsan_get_current_fiber();
149#endif
150}
151
152static void srn_fiber_launcher(void *fiber_ptr) {
153 PANIC_IF_NULL(fiber_ptr);
154
155 srn_fiber_t *fiber = fiber_ptr;
156 // The resumer is the worker loop that switched us in: on_entry records its
157 // stack bounds, and switch_final hands control back to it when the entry
158 // returns.
160 // The worker loop already set the state to RUNNING before switching in, for
161 // both the first run and every resume, so it is not set again here.
162 srn_fiber_result_t v = fiber->entry(fiber->ctx, fiber->arg);
163 PANIC_IF_NULL(v);
164 fiber->result = v;
165 fiber->state = SRN_FIBER_DONE;
167}
168
170 void *arg, size_t stack_size) {
171
172 srn_fiber_t *f = ALLOC(ctx, srn_fiber_t);
173 memset((void *)f, 0, sizeof(srn_fiber_t));
174
175 // TODO(lxsameer): Make the fiber stack configurable via cli arg or something.
176 // This is the acquire side of the stack-ring TODO in fiber.h: a pooled stack
177 // would be pulled from the per-thread ring here, falling back to a fresh
178 // mapping only on a miss.
179 f->stack = srn_fiber_stack_alloc(stack_size);
180 f->ctx = ctx;
181 f->state = SRN_FIBER_NEW;
182 f->entry = entry;
183 f->arg = arg;
184#if SRN_TSAN
185 f->tsan_fiber = __tsan_create_fiber(0);
186#endif
188
189 // Register before enqueuing: the scheduler must know about the fiber for its
190 // whole life, independent of which queue (if any) it currently sits on. The
191 // registry is how a SUSPENDED fiber, off every run queue, stays reachable for
192 // cleanup and cancellation.
193 srn_sched_register(sched, f);
194
195 // TODO(lxsameer): revisit whether make should auto-enqueue (spawn semantics)
196 // or stay NEW and require an explicit srn_fiber_ready to start. For now
197 // make == spawn: the fiber is runnable the moment it is created.
198 srn_sched_enqueue(sched, f);
199 return f;
200}
#define ALLOC(ctx, T)
Definition context.h:82
void srn_fiber_ctx_make(srn_fiber_ctx_t *fiber_ctx, srn_fiber_stack_t stack, void(*fn)(void *), void *arg)
Initialise a fresh fiber context so the first srn_fiber_swap into it begins executing fn(arg) on stac...
Definition ctx_x86_64.c:29
void srn_fiber_switch_final(srn_fiber_t *to)
Like srn_fiber_switch, but for a fiber that has finished and must not be resumed: control transfers t...
Definition fiber.c:85
srn_fiber_t * srn_fiber_make(srn_context_t *ctx, srn_scheduler_t *sched, srn_fiber_entry_t entry, void *arg, size_t stack_size)
Create a fiber that will run entry(ctx, arg).
Definition fiber.c:169
static void srn_fiber_launcher(void *fiber_ptr)
Definition fiber.c:152
void srn_fiber_init_thread(srn_fiber_t *f)
Represent the calling OS thread as the running fiber ("#0"), so the scheduler or a test can switch aw...
Definition fiber.c:137
void srn_fiber_switch(srn_fiber_t *from, srn_fiber_t *to)
Compiled without AddressSanitizer instrumentation: in stack-use-after-return mode ASan would place fr...
Definition fiber.c:62
void srn_fiber_on_entry(srn_fiber_t *from)
Call as the first action inside a fresh fiber's entry.
Definition fiber.c:107
void srn_fiber_on_reap(srn_fiber_t *fiber)
Call when a finished fiber is reaped, after it has switched away for the last time.
Definition fiber.c:129
AI Generated (🤦) Fiber subsystem overview.
void srn_fiber_swap(srn_fiber_ctx_t *from, srn_fiber_ctx_t *to)
Save the current execution context into from, restore to, and resume on to's stack.
static size_t srn_fiber_stack_size(srn_fiber_stack_t s)
Definition fiber.h:437
@ SRN_FIBER_NEW
Created, stack mapped, never resumed.
Definition fiber.h:182
@ SRN_FIBER_RUNNING
Currently executing.
Definition fiber.h:186
@ SRN_FIBER_DONE
Entry returned. The result is final.
Definition fiber.h:190
srn_fiber_result_t(* srn_fiber_entry_t)(srn_context_t *ctx, void *arg)
The function a fiber runs.
Definition fiber.h:199
void * srn_fiber_result_t
Definition fiber.h:117
void srn_sched_register(srn_scheduler_t *sched, srn_fiber_t *fiber)
Record a fiber in the scheduler's registry of live fibers, where it stays until it is reaped.
Definition scheduler.c:305
srn_fiber_t * srn_fiber_worker_loop(void)
The worker's loop of the worker running on the calling os thread.
Definition scheduler.c:934
void srn_sched_enqueue(srn_scheduler_t *sched, srn_fiber_t *fiber)
Place a fiber on a scheduler's ready queue, making it eligible to run.
Definition scheduler.c:593
srn_fiber_stack_t srn_fiber_stack_alloc(size_t size)
Allocate a stack of at least size usable bytes plus a guard page, or SRN_FIBER_DEFAULT_STACK_SIZE whe...
Definition stack_posix.c:34
The saved context of a suspended fiber is a single word: its stack pointer at the moment it was switc...
Definition fiber.h:147
void * limit
Low end of usable region.
Definition fiber.h:172
void * start
High end, stack pointer initialises to this address.
Definition fiber.h:170
srn_fiber_entry_t entry
Definition fiber.h:219
_Atomic srn_fiber_state_t state
The lifecycle state.
Definition fiber.h:217
srn_fiber_result_t result
Set when state reaches SRN_FIBER_DONE.
Definition fiber.h:223
srn_context_t * ctx
Definition fiber.h:218
srn_fiber_stack_t stack
Definition fiber.h:211
void * arg
Definition fiber.h:220
const char * name
For debugging purposes.
Definition fiber.h:270
srn_fiber_ctx_t fiber_ctx
Saved stack pointer (see srn_fiber_ctx_t)
Definition fiber.h:210
#define PANIC_IF_NULL(ptr)
Definition utils.h:64
#define SHOULD_NOT_HAPPEN
Definition utils.h:77
#define UNUSED(x)
Definition utils.h:43