Serene Runtime 1.0.0
C runtime for the Serene programming language
Loading...
Searching...
No Matches
fiber.c File Reference
#include "serene/rt/fiber.h"
#include "serene/rt/context.h"
#include "serene/utils.h"
#include <string.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, srn_fiber_entry_t entry, void *arg, size_t stack_size)
 Create a fiber that will run entry(ctx, arg).
 

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 137 of file fiber.c.

137 {
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}
@ SRN_FIBER_RUNNING
Currently executing.
Definition fiber.h:186
_Atomic srn_fiber_state_t state
The lifecycle state.
Definition fiber.h:217
const char * name
For debugging purposes.
Definition fiber.h:270
Here is the caller graph for this function:

◆ srn_fiber_launcher()

static void srn_fiber_launcher ( void * fiber_ptr)
static

Definition at line 152 of file fiber.c.

152 {
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}
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
void srn_fiber_on_entry(srn_fiber_t *from)
Call as the first action inside a fresh fiber's entry.
Definition fiber.c:107
@ SRN_FIBER_DONE
Entry returned. The result is final.
Definition fiber.h:190
void * srn_fiber_result_t
Definition fiber.h:117
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
srn_fiber_entry_t entry
Definition fiber.h:219
srn_fiber_result_t result
Set when state reaches SRN_FIBER_DONE.
Definition fiber.h:223
srn_context_t * ctx
Definition fiber.h:218
void * arg
Definition fiber.h:220
#define PANIC_IF_NULL(ptr)
Definition utils.h:64
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,
srn_fiber_entry_t entry,
void * arg,
size_t stack_size )
nodiscard

Create a fiber that will run entry(ctx, arg).

The fiber allocates into its block and never releases it. A stack_size of 0 selects SRN_FIBER_DEFAULT_STACK_SIZE.

Definition at line 169 of file fiber.c.

170 {
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
187 srn_fiber_ctx_make(&f->fiber_ctx, f->stack, &srn_fiber_launcher, (void *)f);
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
static void srn_fiber_launcher(void *fiber_ptr)
Definition fiber.c:152
@ SRN_FIBER_NEW
Created, stack mapped, never resumed.
Definition fiber.h:182
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
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
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 107 of file fiber.c.

107 {
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}
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_stack_t stack
Definition fiber.h:211
#define UNUSED(x)
Definition utils.h:43
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 129 of file fiber.c.

129 {
130#if SRN_TSAN
131 __tsan_destroy_fiber(fiber->tsan_fiber);
132#else
133 UNUSED(fiber);
134#endif
135}
Here is the caller 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 62 of file fiber.c.

63 {
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}
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_ctx_t fiber_ctx
Saved stack pointer (see srn_fiber_ctx_t)
Definition fiber.h:210
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 85 of file fiber.c.

85 {
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}
The saved context of a suspended fiber is a single word: its stack pointer at the moment it was switc...
Definition fiber.h:147
#define SHOULD_NOT_HAPPEN
Definition utils.h:77
Here is the call graph for this function:
Here is the caller graph for this function: