Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
fiber.c File Reference
#include "serene/rt/fiber.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "serene/rt/context.h"
#include "serene/rt/engine.h"
#include "serene/utils.h"
Include dependency graph for fiber.c:

Go to the source code of this file.

Functions

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 from/to on a fake stack that __sanitizer_start_switch_fiber releases before srn_fiber_swap reads them.
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 to to and never comes back.
void srn_fiber_on_entry (srn_fiber_t *from)
 Call as the first action inside a fresh fiber's entry.
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.
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 away from it and back.
static void srn_fiber_launcher (void *fiber_ptr)
srn_fiber_tsrn_fiber_make (srn_context_t *ctx, srn_scheduler_t *sched, const char *name, srn_fiber_entry_t entry, void *arg, size_t stack_size)
 Create a fiber that will run entry(ctx, arg), registered with sched but NOT scheduled.
srn_fiber_tsrn_fiber_spawn (srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
 Make and schedule a fiber with every default, the engine's scheduler, the configured stack size, and an autogenerated name.
srn_fiber_tsrn_fiber_spawn_copy (srn_context_t *ctx, srn_fiber_entry_t entry, const void *arg, size_t size)
 srn_fiber_spawn with size bytes of *arg copied into ctx first, so the fiber owns its argument and the caller's stack frame can die freely.

Function Documentation

◆ srn_fiber_init_thread()

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 away from it and back.

The thread's stack bounds are not queried here (there is no portable way). Under the sanitizer they are recorded by the first fiber's srn_fiber_on_entry. The saved context is written by the first switch away.

Definition at line 154 of file fiber.c.

154 {
155 // Represent the calling thread as the running fiber. The saved context
156 // (fiber_ctx) stays empty, and the first switch away from the thread fills
157 // it.
158 memset(f, 0, sizeof(*f));
160 (void)snprintf(f->name, sizeof(f->name), "thread");
161#if SRN_ASAN
162 // ASan needs this loop's stack bounds on every switch back to it, and a
163 // worker that only ever resumes stolen fibers never launches a fresh
164 // fiber, so srn_fiber_on_entry would never report them. Run a throwaway
165 // fiber once, its entry records the bounds through the same protocol
166 // every real fiber uses. The probe finishes before the switch back, so
167 // srn_fiber_switch_final already has real bounds to announce.
168 srn_fiber_t probe;
169 memset(&probe, 0, sizeof(probe));
170 probe.state = SRN_FIBER_READY;
171 (void)snprintf(probe.name, sizeof(probe.name), "stack-bounds-probe");
172 probe.stack = srn_fiber_stack_alloc(1); // rounds up to one page plus guard
173 srn_fiber_ctx_make(&probe.fiber_ctx, probe.stack, stack_bounds_probe, f);
174 srn_fiber_switch(f, &probe);
176#endif
177#if SRN_TSAN
178 // The loop fiber stands in for this OS thread, so it takes the thread's own
179 // current TSan fiber rather than a freshly created one.
180 f->tsan_fiber = __tsan_get_current_fiber();
181#endif
182}
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:66
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...
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...
void srn_fiber_stack_free(srn_fiber_stack_t stack)
@ SRN_FIBER_RUNNING
Currently executing.
Definition fiber.h:236
@ SRN_FIBER_READY
On the run queue, eligible to run.
Definition fiber.h:234
char name[SRN_FIBER_NAME_MAX]
Debug name, the caller's choice copied at creation, or the autogenerated f#<id> from the engine wide ...
Definition fiber.h:330
_Atomic srn_fiber_state_t state
The lifecycle state.
Definition fiber.h:273
srn_fiber_stack_t stack
Definition fiber.h:267
srn_fiber_ctx_t fiber_ctx
Saved stack pointer (see srn_fiber_ctx_t).
Definition fiber.h:266
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_launcher()

void srn_fiber_launcher ( void * fiber_ptr)
static

Definition at line 184 of file fiber.c.

184 {
185 PANIC_IF_NULL(fiber_ptr);
186
187 srn_fiber_t *fiber = fiber_ptr;
188 // The resumer is the worker loop that switched us in, on_entry records its
189 // stack bounds, and switch_final hands control back to it when the entry
190 // returns.
192 // The worker loop already set the state to RUNNING before switching in, for
193 // both the first run and every resume, so it is not set again here.
194 // A null result is legal, the result is type erased and "no result" is a
195 // reasonable outcome for an entry run for its effects.
196 fiber->result = fiber->entry(fiber->ctx, fiber->arg);
197 fiber->state = SRN_FIBER_DONE;
199}
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:88
void srn_fiber_on_entry(srn_fiber_t *from)
Call as the first action inside a fresh fiber's entry.
Definition fiber.c:111
@ SRN_FIBER_DONE
Entry returned. The result is final.
Definition fiber.h:240
srn_fiber_t * srn_fiber_worker_loop(void)
The worker's loop of the worker running on the calling os thread.
Definition scheduler.c:1062
srn_fiber_entry_t entry
Definition fiber.h:275
srn_fiber_result_t result
Set when state reaches SRN_FIBER_DONE.
Definition fiber.h:279
srn_context_t * ctx
Definition fiber.h:274
void * arg
Definition fiber.h:276
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_make()

srn_fiber_t * srn_fiber_make ( srn_context_t * ctx,
srn_scheduler_t * sched,
const char * name,
srn_fiber_entry_t entry,
void * arg,
size_t stack_size )
nodiscard

Create a fiber that will run entry(ctx, arg), registered with sched but NOT scheduled.

The fiber stays NEW until srn_fiber_schedule starts it, so a caller can build several fibers and wire them up before any of them runs. Registration at creation means the scheduler reaps every fiber at shutdown, scheduled or not.

name is a debug label copied into the fiber, truncated to SRN_FIBER_NAME_MAX - 1 bytes, or nullptr for an autogenerated unique one. A stack_size of 0 selects the configured per fiber default.

Definition at line 201 of file fiber.c.

204 {
205
206 srn_fiber_t *f = ALLOC(ctx, srn_fiber_t);
207 PANIC_IF_NULL(f);
208 memset((void *)f, 0, sizeof(srn_fiber_t));
209
210 // TODO(lxsameer): Make the fiber stack configurable via cli arg or something.
211 // This is the acquire side of the stack-ring TODO in fiber.h: a pooled stack
212 // would be pulled from the per-thread ring here, falling back to a fresh
213 // mapping only on a miss.
214 // A zero `stack_size` falls back to the configured per fiber default.
215 f->stack =
216 srn_fiber_stack_alloc(stack_size != 0 ? stack_size : ctx->engine->config.fiber.stack_size);
217 f->ctx = ctx;
218 f->state = SRN_FIBER_NEW;
219 f->entry = entry;
220 f->arg = arg;
221
222 if (name != nullptr) {
223 (void)snprintf(f->name, sizeof(f->name), "%s", name);
224 } else {
225 // The autogenerated name makes logs tell fibers apart with no caller
226 // input. Ids come from the engine wide object id counter, so a fiber
227 // never shares its number with another runtime object.
228 (void)snprintf(f->name, sizeof(f->name), "f#%" PRIu64, srn_allocate_object_id(ctx->engine));
229 }
230
231#if SRN_TSAN
232 f->tsan_fiber = __tsan_create_fiber(0);
233#endif
234 srn_fiber_ctx_make(&f->fiber_ctx, f->stack, &srn_fiber_launcher, (void *)f);
235
236 // Register before enqueuing, the scheduler must know about the fiber for its
237 // whole life, independent of which queue (if any) it currently sits on. The
238 // registry is how a SUSPENDED fiber, off every run queue, stays reachable for
239 // cleanup and cancellation.
240 srn_sched_register(sched, f);
241
242 // The fiber stays NEW. srn_fiber_schedule makes it runnable, so a caller
243 // can build several fibers and wire them up before any of them runs.
244 return f;
245}
#define ALLOC(ctx, T)
Definition context.h:84
srn_object_id_t srn_allocate_object_id(srn_engine_t *engine)
Definition engine.c:172
static void srn_fiber_launcher(void *fiber_ptr)
Definition fiber.c:184
@ SRN_FIBER_NEW
Created, stack mapped, never resumed.
Definition fiber.h:232
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:318
srn_fiber_config_t fiber
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
Definition engine.h:62
size_t stack_size
Size of every fiber stack, in bytes.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_on_entry()

void srn_fiber_on_entry ( srn_fiber_t * from)

Call as the first action inside a fresh fiber's entry.

from is the fiber that started this one (the scheduler, or the bootstrap thread fiber). It completes the switch for AddressSanitizer and, since from's stack bounds only become discoverable here, records them into from so a later switch back is tracked. from may be null. A no-op when the sanitizer is not in use.

Definition at line 111 of file fiber.c.

111 {
112#if SRN_ASAN
113 // A nullptr fake-stack, a fresh fiber has no previously saved bookkeeping.
114 // The out-params report the stack this fiber was started from -- this is the
115 // one chance to learn `from`'s bounds (there is no portable way to query a
116 // thread's own stack), and a later switch back to `from` needs them, so
117 // record them. They flow through the sanitizer rather than libc.
118 const void *bottom = nullptr;
119 size_t size = 0;
120 __sanitizer_finish_switch_fiber(nullptr, &bottom, &size);
121
122 if (from != nullptr) {
123 // ASan reports the came-from stack as (bottom, size). The struct keeps the
124 // low and high boundaries, so limit = bottom and start = bottom + size.
125 from->stack.limit = (void *)bottom;
126 from->stack.start = (char *)bottom + size;
127 }
128#else
129 UNUSED(from);
130#endif
131}
void * limit
Low end of usable region.
Definition fiber.h:222
void * start
High end, stack pointer initialises to this address.
Definition fiber.h:220
#define UNUSED(x)
Definition utils.h:45
Here is the caller graph for this function:

◆ srn_fiber_on_reap()

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.

Releases the fiber's ThreadSanitizer handle. A no-op when the sanitizer is not in use.

Definition at line 133 of file fiber.c.

133 {
134#if SRN_TSAN
135 __tsan_destroy_fiber(fiber->tsan_fiber);
136#else
137 UNUSED(fiber);
138#endif
139}
Here is the caller graph for this function:

◆ srn_fiber_spawn()

srn_fiber_t * srn_fiber_spawn ( srn_context_t * ctx,
srn_fiber_entry_t entry,
void * arg )

Make and schedule a fiber with every default, the engine's scheduler, the configured stack size, and an autogenerated name.

The caller keeps ownership of arg and must keep it alive until the fiber is done with it, srn_fiber_spawn_copy lifts that burden.

Definition at line 247 of file fiber.c.

247 {
248 PANIC_IF_NULL(ctx);
249 srn_fiber_t *f = srn_fiber_make(ctx, ctx->engine->scheduler, nullptr, entry, arg, 0);
251 return f;
252}
srn_fiber_t * srn_fiber_make(srn_context_t *ctx, srn_scheduler_t *sched, const char *name, srn_fiber_entry_t entry, void *arg, size_t stack_size)
Create a fiber that will run entry(ctx, arg), registered with sched but NOT scheduled.
Definition fiber.c:201
void srn_fiber_schedule(srn_fiber_t *fiber)
Schedule a NEW fiber, making it eligible to run.
Definition scheduler.c:630
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:75
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_spawn_copy()

srn_fiber_t * srn_fiber_spawn_copy ( srn_context_t * ctx,
srn_fiber_entry_t entry,
const void * arg,
size_t size )

srn_fiber_spawn with size bytes of *arg copied into ctx first, so the fiber owns its argument and the caller's stack frame can die freely.

The copy lives until the context is released, like the fiber itself.

Definition at line 255 of file fiber.c.

255 {
256 PANIC_IF_NULL(ctx);
257 PANIC_IF_NULL(arg);
258 // max_align_t suits any argument type, the caller only hands over bytes.
259 void *copy = srn_allocate(ctx, size, alignof(max_align_t));
260 PANIC_IF_NULL(copy);
261 memcpy(copy, arg, size);
262 return srn_fiber_spawn(ctx, entry, copy);
263}
void * srn_allocate(const srn_context_t *ctx, size_t size, size_t alignment)
Definition context.c:73
srn_fiber_t * srn_fiber_spawn(srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
Make and schedule a fiber with every default, the engine's scheduler, the configured stack size,...
Definition fiber.c:247
Here is the call graph for this function:

◆ srn_fiber_switch()

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 from/to on a fake stack that __sanitizer_start_switch_fiber releases before srn_fiber_swap reads them.

Switch from from to to, both live fibers, telling AddressSanitizer about the stack change.

Not instrumented by either sanitizer, the stack swaps mid-function, which confuses ASan's fake stack and TSan's shadow stack. The explicit annotations keep each sanitizer's fiber tracking correct across the swap instead.

Definition at line 66 of file fiber.c.

66 {
67
68#if SRN_ASAN
69 const size_t size = srn_fiber_stack_size(to->stack);
70 __sanitizer_start_switch_fiber(&from->fake_stack, to->stack.limit, size);
71#endif
72#if SRN_TSAN
73 // Move TSan's notion of the running fiber to `to` before the stack swaps. A
74 // fiber built outside srn_fiber_make / srn_fiber_init_thread has no handle
75 // and is simply not tracked. Only the low-level switch tests build such a
76 // fiber. Every fiber the scheduler runs has one.
77 if (to->tsan_fiber != nullptr) {
78 __tsan_switch_to_fiber(to->tsan_fiber, 0);
79 }
80#endif
82#if SRN_ASAN
83 __sanitizer_finish_switch_fiber(from->fake_stack, nullptr, nullptr);
84#endif
85}
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:611
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_switch_final()

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 to to and never comes back.

The fiber's entry function therefore ends at this call – any code after it is unreachable, which is why this is [[noreturn]]. Nothing is freed here. The spent fiber's stack is left frozen and reclaimed later by its owner (the scheduler reaps a finished fiber and frees its stack via srn_fiber_stack_free).

Definition at line 88 of file fiber.c.

88 {
89#if SRN_ASAN
90 // A nullptr fake-stack tells ASan the current fiber is finished and will not
91 // resume, so it can discard the bookkeeping rather than leak it.
92 const size_t size = srn_fiber_stack_size(to->stack);
93 __sanitizer_start_switch_fiber(nullptr, to->stack.limit, size);
94#endif
95#if SRN_TSAN
96 // The finished fiber's handle is released later, at reap. Here just move
97 // TSan to `to` before the swap, when `to` has a handle (see
98 // srn_fiber_switch).
99 if (to->tsan_fiber != nullptr) {
100 __tsan_switch_to_fiber(to->tsan_fiber, 0);
101 }
102#endif
103 // srn_fiber_swap always writes the outgoing sp somewhere. This fiber is done,
104 // so discard it. Control loads `to` and never comes back.
105 srn_fiber_ctx_t discard;
106 srn_fiber_swap(&discard, &to->fiber_ctx);
108}
The saved context of a suspended fiber is a single word, its stack pointer at the moment it was switc...
Definition fiber.h:198
#define SHOULD_NOT_HAPPEN
Definition utils.h:83
Here is the call graph for this function:
Here is the caller graph for this function: