Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
fiber.h
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// TODO(lxsameer): A bocking IO thread poool:
20// The current implementation has a big weakness. Depending on the backend it
21// might to NOT support async operations on regular files (Likes of epoll,
22// kqueue backend; while io_uring and IOCP do). That means that we are facing
23// with a tricky situation.
24//
25// We can never block the reactor since it is single threaded. We need to either
26// offload the blocking IO operation to a threadpool dedicated for such
27// operations or just do the blocking IO in the fibers themselves.
28// Right now we went with the later option since it is much, much simpler to
29// implement, and we didn't want to overengineer. But the better choice seem to
30// be the thread pool option.
31//
32// That being said, it has it's own problems as well. For example:
33// - We need more parked threads that might not be optimal
34// - We can't post completion from the threadpool since SQs/CQs are single
35// producer/consumer and we need to have Multi producer signle user queues.
36// - The current model would be ok for the majority of use cases.
37//
38// So I guess the point of this todo is to investigate the IO threadpool idea,
39// and implement it if makes sense.
40
41// TODO(lxsameer): Bound the fiber count against the kernel's mapping limit.
42// Each fiber stack is its own mmap carrying a PROT_NONE guard band, so every
43// live or parked fiber costs at least two entries against vm.max_map_count. On
44// a stock Linux (~64K) that ceilings the process at about max_map_count / 2
45// fibers. Recycling stacks through the free ring each worker keeps (see the
46// stack ring TODO further down and scheduler.c) reclaims the mappings of
47// finished and cancelled fibers and is the first mitigation. Dropping the
48// guard band, or moving to stack maps once a gc exists, would relax the ceiling
49// further.
50
51/** @file
52 AI Generated (🤦)
53 Fiber subsystem overview
54
55 Stackful, cooperative fibers for the runtime. A fiber is a function plus its
56 own stack, so it can pause at any call depth and resume later. Nothing is
57 preempted. A fiber runs until it explicitly yields, suspends, or finishes.
58
59 Terminology (used throughout the fiber subsystem)
60 fiber a unit of work with its own stack. It can pause and resume.
61 os thread a thread the operating system schedules.
62 worker the state one os thread uses to run fibers. It holds a local
63 queue of ready fibers and a worker's loop. There is one
64 worker per os thread.
65 worker's loop a special fiber that stands in for the os thread. A running
66 fiber switches back to it to hand control over, and it picks
67 the next fiber to run.
68 worker routine the steps an os thread repeats. It takes a fiber, switches
69 into it, deals with how the fiber gave up control, and parks
70 when no fiber is runnable.
71 scheduler there is exactly one. It owns the shared ready queue and the
72 list of live fibers and decides what runs next. It does not
73 run fibers itself, workers do.
74 suspend a fiber steps off every queue and waits to be woken. Its os
75 thread stays free to run other fibers.
76 park an os thread blocks because no fiber is runnable anywhere. A
77 notify wakes it.
78
79 Every fiber stack is the same fixed size (the configured `fiber.stack_size`), one
80 mapping with a guard page that never grows. Uniform size is a deliberate
81 constraint, not an accident. It lets a finished fiber's stack be handed to any
82 later fiber (the stack ring TODO below) without tracking size classes, and it
83 keeps switch and mapping costs predictable.
84
85 Control flow is a hub. A fiber never jumps to another fiber. It switches back
86 to its worker's loop, which picks the next one.
87
88 scheduler ready queue: [F2] -> [F5] -> ...
89 | the os thread takes the head
90 v
91 +-----------> worker's loop --- switch ---> running fiber
92 | |
93 | yield (re-enqueue, run next) ----------------+
94 | suspend (off-queue until srn_fiber_ready) -----+
95 | finish (reap) --------------------------------+
96 +--------------------------------------------------+
97
98 A running fiber gives up control three ways:
99 - yield still runnable, so it moves to the back of the ready queue.
100 - suspend off every queue until some party calls srn_fiber_ready. Fibers,
101 workers, and the reactor may wake it; the reactor is the only
102 waker outside the worker pool (see srn_fiber_ready).
103 - finish its entry returns and the fiber is reaped.
104
105 Signal handling is not part of this subsystem. No scheduler entry point is
106 async signal safe, and by design: a runtime that owns an event loop folds
107 signals into it rather than promising handler safety call by call.
108 `srn_sched_stop` and `srn_sched_drain` take the scheduler mutex and signal
109 its condition variable, so calling them from a signal handler can
110 self-deadlock. A signal-initiated shutdown routes the signal into ordinary
111 thread context first (a dedicated thread in sigwait, or a signalfd
112 registered with the reactor) and calls stop or drain from there.
113
114 The spec book's fibers chapter (docs/spec/fibers.typ) is the long-form
115 reference. Keep this overview in step with it.
116*/
117
118#pragma once
119
120#include <stddef.h>
121#include <stdint.h>
122
123#include "serene/rt/trace.h"
124
125#ifndef __x86_64__
126# error "no fiber context switch for this architecture (need ctx_<arch>.c and switch_<arch>.S)"
127#endif
128
129// Sanitizer detection. AddressSanitizer and ThreadSanitizer each need a little
130// per-fiber bookkeeping (the guarded fields below and the switch path in
131// fiber.c), and a normal build carries none of it. Every translation unit in a
132// build compiles with the same `-fsanitize` flags, so the struct layout stays
133// consistent within that build.
134#if defined(__SANITIZE_ADDRESS__)
135# define SRN_ASAN 1
136#elif defined(__has_feature)
137# if __has_feature(address_sanitizer)
138# define SRN_ASAN 1
139# endif
140#endif
141#ifndef SRN_ASAN
142# define SRN_ASAN 0
143#endif
144
145#if defined(__SANITIZE_THREAD__)
146# define SRN_TSAN 1
147#elif defined(__has_feature)
148# if __has_feature(thread_sanitizer)
149# define SRN_TSAN 1
150# endif
151#endif
152#ifndef SRN_TSAN
153# define SRN_TSAN 0
154#endif
155
156#define FIBER_TRACEPOINT(...) SRN_TRACEPOINT_WITH_GROUP(fiber __VA_OPT__(, ) __VA_ARGS__)
157
158typedef struct srn_engine_t srn_engine_t;
159typedef struct srn_context_t srn_context_t;
161typedef struct srn_scheduler_t srn_scheduler_t;
162typedef struct srn_reactor_t srn_reactor_t;
163typedef size_t srn_worker_id_t;
164
165/**
166 * What a fiber's entry produces, type-erased. It is an alias for `void *`, so
167 * the fiber subsystem carries no dependency on the value model. A caller that
168 * runs fibers over Serene values casts to and from its own type at the
169 * boundary.
170 */
171typedef void *srn_fiber_result_t;
172
173#define srn_fiber_get_scheduler_m(fiber) fiber->ctx->engine->scheduler
174
175// TODO(lxsameer): We need to have a ring with a certain size, which
176// would be like a storage for ready to use stacks. When we're done with a fiber
177// put its stack in the ring and prepare it for another upcoming fiber.
178// it might be even a good idea to allocate a few stacks in advance on start
179// up. This ring should be per thread I think.
180
181/**
182 * Size of the embedded debug name buffer on every fiber, terminator
183 * included. Large enough for the autogenerated `fiber_<id>` spelling of any
184 * 64 bit id.
185 */
186#define SRN_FIBER_NAME_MAX 32U
187
188// -----------------------------------------------------------------------------
189// Saved machine context
190// -----------------------------------------------------------------------------
191/**
192 * The saved context of a suspended fiber is a single word, its stack pointer
193 * at the moment it was switched away from. The callee-saved registers live on
194 * the fiber's own stack, pushed by srn_fiber_swap and popped when it is
195 * resumed.
196 */
200
201// -----------------------------------------------------------------------------
202// Stack
203// -----------------------------------------------------------------------------
204/**
205 * One stack per fiber, mapped with a guard band at the low end so an overflow
206 * faults deterministically instead of corrupting a neighbour. With addresses
207 * increasing to the right, the layout is:
208 *
209 * |... guard band ...|.......... frames ..........|
210 * ^ guard ^ limit ^ start
211 *
212 * `guard` is the mmap base, the lowest page of the region. The band spans
213 * `fiber.guard_pages` pages from the configuration. The stack grows downward,
214 * so frames start at the high end (`start`) and grow toward `limit`; one step
215 * past `limit` lands on the protected guard band and faults.
216 */
217typedef struct srn_fiber_stack_t {
218 /// High end, stack pointer initialises to this address
219 void *start;
220 /// Low end of usable region
221 void *limit;
222 /// Low base of the protected guard band that detects stack overflows
223 void *guard;
225
226// -----------------------------------------------------------------------------
227// Lifecycle
228// -----------------------------------------------------------------------------
229typedef enum srn_fiber_state_e : uint8_t {
230 /// Created, stack mapped, never resumed
232 /// On the run queue, eligible to run
234 /// Currently executing
236 /// Parked off the run queue, awaits srn_fiber_ready
238 /// Entry returned. The result is final
241
242/**
243 * The function a fiber runs. Allocations made through `ctx` land in the
244 * context's block.
245 * TODO(lxsameer): The shape is generic until code generation produces
246 * fibers, at which point it adopts the Serene ABI.
247 * TODO(lxsameer): Should we support two types of entry functions? one
248 * for C calling convention and one for FastC?
249 */
251
252/**
253 * Suspend commit callback. The worker routine runs it on its own side once the
254 * fiber has switched out. It re-checks the awaited condition and only then
255 * registers `self` with whatever will wake it, returning true to stay
256 * suspended, or false to resume `self` at once because the condition already
257 * holds. The order matters, registering before the re-check can leave a live
258 * registration behind a false return, and that stale waker would wake the
259 * fiber's NEXT suspend. It must not switch fibers.
260 */
261typedef bool (*srn_fiber_park_fn)(srn_fiber_t *self, void *arg);
262
264 /// Saved stack pointer (see srn_fiber_ctx_t)
267 /// The lifecycle state. Atomic because a waker may run on a different os
268 /// thread than the worker running the fiber. `srn_fiber_ready` flips
269 /// `SUSPENDED` to `READY` with a CAS while the worker running the fiber reads
270 /// and writes the same field. Making it atomic keeps every access whole, so a
271 /// read can never see an inconsistent value.
275 void *arg;
276 /// Set when state reaches SRN_FIBER_DONE. Type-erased (see
277 /// srn_fiber_result_t), the caller interprets it.
279
280 /// While this fiber is suspending, the commit the worker routine runs once
281 /// the fiber is off the stack, to publish it to its waker (see
282 /// srn_fiber_suspend). Valid only across that one suspend hand-off.
284 void *park_arg;
285
286#if SRN_ASAN
287 /// AddressSanitizer bookkeeping across switches.
288 void *fake_stack;
289#endif
290
291#if SRN_TSAN
292 /// ThreadSanitizer fiber handle. TSan tracks each fiber as its own execution
293 /// context, so the switch path tells it which one is current. Created with
294 /// the fiber and destroyed when it is reaped.
295 void *tsan_fiber;
296#endif
297
298 /// Intrusive link threading this fiber onto one of the scheduler's
299 /// singly-linked lists (the ready run queue, or a wait queue) without a
300 /// separate node allocation, the next pointer lives in the fiber itself. A
301 /// fiber belongs to at most one such list at a time, so one link suffices.
303
304 /// Head of the list of fibers blocked in srn_fiber_wait_for on this fiber.
305 /// They are threaded through their own `link` (a waiter is suspended, so off
306 /// the ready queue, and its `link` is free). When this fiber reaches DONE the
307 /// worker routine wakes them all.
309
310 /// Registry links.
311 ///
312 /// The scheduler keeps every live fiber on one doubly linked list through
313 /// these pointers. It is different from `link`. `link` says where a fiber
314 /// sits in the run order, the registry says the fiber exists at all. A fiber
315 /// joins the list when it is created and leaves when it is reaped, in
316 /// whatever state it is in between -- including SUSPENDED, which sits on no
317 /// queue.
318 ///
319 /// This is how the scheduler accounts for, cleans up, and (later) cancels its
320 /// fibers. Doubly linked so reaping can unlink from the middle in O(1).
323
324 /// Debug name, the caller's choice copied at creation, or autogenerated
325 /// when the caller passed none (see srn_fiber_autoname).
326 /// srn_fiber_init_thread names loop fibers "thread". Embedded so the name
327 /// shares the fiber's lifetime with no separate allocation.
329};
330
331// -----------------------------------------------------------------------------
332// Scheduler
333// -----------------------------------------------------------------------------
334[[nodiscard]] [[gnu::nonnull(1)]]
336
337/**
338 * The one stop tear down of the fiber subsystem, should be called once
339 * `srn_sched_run` has returned. It joins the os threads, then releases the
340 * stack of every fiber still registered (any left unreaped, such as one
341 * suspended with no party to wake it, or one left queued when the run stopped
342 * early), and frees the scheduler's own resources. The fiber structs
343 * themselves live in context blocks and are reclaimed with those blocks, not
344 * here.
345 *
346 * The scheduler is NOT usable after this. A later `srn_sched_run` panics, and
347 * a repeated `srn_sched_shutdown` is a no-op (so the engine can call it
348 * unconditionally even after a caller already did).
349 *
350 * Must be called from outside the pool, never from an os thread that is
351 * running a worker nor from a fiber, and only after `srn_sched_run` has
352 * returned. Calling it on a live pool panics -- stop the pool with
353 * `srn_sched_stop`, let `srn_sched_run` return, then shut down.
354 */
355[[gnu::nonnull(1)]]
357
358/**
359 * Run the scheduler with `nworkers` os threads draining it, returning once the
360 * pool goes quiescent (every os thread parked on an empty queue) or a stop is
361 * requested with `srn_sched_stop`. The calling os thread becomes worker 0, so
362 * it does not return until then. `nworkers` is clamped to at least 1; with 1
363 * it is the calling os thread alone, which keeps execution single-threaded and
364 * cooperatively ordered. The spawned os threads are not joined here --
365 * `srn_sched_shutdown` joins them as part of tearing the subsystem down.
366 */
367[[gnu::nonnull(1)]]
368void srn_sched_run(srn_scheduler_t *sched, size_t nworkers);
369
370/**
371 * Ask a running scheduler to stop. Each worker routine checks the request at
372 * the top of each turn and stops once the fiber it is running yields or
373 * finishes, so a slice in flight is never cut mid-execution. `srn_sched_run`
374 * then returns. Fibers left queued stay unrun and are reclaimed by
375 * `srn_sched_shutdown`. Does not wait. Safe to call from any os thread or a
376 * fiber, but NOT from a signal handler. It takes the scheduler mutex and
377 * signals the condition variable, neither of which is async signal safe.
378 */
379[[gnu::nonnull(1)]]
381
382/**
383 * Ask a running scheduler to wind down gracefully. Unlike `srn_sched_stop`,
384 * the workers keep running every runnable fiber and let in-flight IO complete;
385 * only new IO submissions are fenced, so a fiber's next IO call returns
386 * `-ECANCELED` and the fiber unwinds rather than parking on a fresh op. The
387 * pool then reaches the same quiescence as a natural finish and
388 * `srn_sched_run` returns. An in-flight op that never completes (an idle recv,
389 * a long sleep) stalls the wind-down until it finishes; bounding that needs op
390 * cancellation and is not provided yet. Does not wait. Safe to call from any
391 * os thread or a fiber. A no-op if the scheduler is not running.
392 */
393[[gnu::nonnull(1)]]
395
396/**
397 * Record a fiber in the scheduler's registry of live fibers, where it stays
398 * until it is reaped.
399 */
400[[gnu::nonnull(1, 2)]]
402
403/**
404 * Place a fiber on a scheduler's ready queue, making it eligible to run.
405 */
406[[gnu::nonnull(1, 2)]]
408
409// -----------------------------------------------------------------------------
410// Fibers
411// -----------------------------------------------------------------------------
412/**
413 * Create a fiber that will run entry(ctx, arg), registered with `sched` but
414 * NOT scheduled. The fiber stays NEW until srn_fiber_schedule starts it, so
415 * a caller can build several fibers and wire them up before any of them
416 * runs. Registration at creation means the scheduler reaps every fiber at
417 * shutdown, scheduled or not.
418 *
419 * `name` is a debug label copied into the fiber, truncated to
420 * SRN_FIBER_NAME_MAX - 1 bytes, or nullptr for an autogenerated unique one.
421 * A `stack_size` of 0 selects the configured per fiber default.
422 */
423[[nodiscard]] [[gnu::nonnull(1, 2, 4)]]
425 srn_context_t *ctx, srn_scheduler_t *sched, const char *name, srn_fiber_entry_t entry, void *arg,
426 size_t stack_size
427);
428
429/**
430 * Write the autogenerated debug name for a new fiber into `dst`. Created on
431 * a worker the tag is `f#<worker>:<n>`, the creating worker's id and its
432 * spawn count. Created off the pool it is `f#m:<id>` from the engine wide
433 * object id counter. The worker part is provenance, not placement, a stolen
434 * fiber runs anywhere. srn_fiber_make uses this when given no name.
435 */
436[[gnu::nonnull(1, 2)]]
437void srn_fiber_autoname(srn_engine_t *engine, char *dst, size_t size);
438
439/**
440 * Schedule a NEW fiber, making it eligible to run. A fiber is scheduled
441 * exactly once, scheduling one that is not NEW panics, and waking a
442 * suspended fiber is srn_fiber_ready's job instead.
443 */
444[[gnu::nonnull(1)]]
446
447/**
448 * Make and schedule a fiber with every default, the engine's scheduler, the
449 * configured stack size, and an autogenerated name. The caller keeps
450 * ownership of `arg` and must keep it alive until the fiber is done with
451 * it, srn_fiber_spawn_copy lifts that burden.
452 */
453[[gnu::nonnull(1, 2)]]
455
456/**
457 * srn_fiber_spawn with `size` bytes of `*arg` copied into `ctx` first, so
458 * the fiber owns its argument and the caller's stack frame can die freely.
459 * The copy lives until the context is released, like the fiber itself.
460 */
461[[gnu::nonnull(1, 2, 3)]]
463srn_fiber_spawn_copy(srn_context_t *ctx, srn_fiber_entry_t entry, const void *arg, size_t size);
464
465/**
466 * srn_fiber_spawn_copy for an lvalue, the address and size are taken for
467 * the caller.
468 */
469#define SRN_FIBER_SPAWN_COPY(ctx, entry, value) \
470 srn_fiber_spawn_copy(ctx, entry, &(value), sizeof(value))
471
472/**
473 * Yield cooperatively, re-enqueue the running fiber and run the next ready
474 * one. Acts on the fiber currently running on this thread.
475 */
476void srn_fiber_yield(void);
477
478/**
479 * Park the running fiber until a party calls srn_fiber_ready. The fiber
480 * switches out first. Only then does the scheduler run `commit` (see
481 * srn_fiber_park_fn) to register it and confirm it should stay parked.
482 * Registering only after the park completes is what makes it race free -- a
483 * waker can never observe a half parked fiber. Acts on the fiber currently
484 * running on this thread.
485 */
486[[gnu::nonnull(1)]]
487void srn_fiber_suspend(srn_fiber_park_fn commit, void *arg);
488
489/**
490 * Mark a suspended fiber runnable again, waking it when the event it awaited
491 * occurs. Callable from a fiber, a worker, or the reactor. The reactor is
492 * the only legitimate waker outside the worker pool.
493 */
494// NOTE: Quiescence accounts for runnable fibers and the reactor's inflight ops, nothing else, so a
495// wake from an unrelated os thread can arrive after the run has already ended and be lost, or race
496// the scheduler teardown. A subsystem that wakes fibers from its own threads must be accounted for
497// in quiescence the way the reactor is.
498[[gnu::nonnull(1)]]
499void srn_fiber_ready(srn_fiber_t *fiber);
500
501/**
502 * Block the calling fiber until `target` finishes, then return its result. If the `target` is
503 * already done, returns immediately.
504 */
505[[gnu::nonnull(1)]]
507
508/**
509 * The fiber currently running on this os thread, or null when the calling
510 * thread is not a worker or the worker is running its own loop rather than
511 * a fiber.
512 */
514
515/**
516 * The worker's loop of the worker running on the calling os thread. The fiber
517 * that resumes when the current fiber yields, suspends, or finishes, the
518 * resumer a fiber's launcher hands control back to. Each worker has its own,
519 * so this is valid only on an os thread that is currently running the worker
520 * routine.
521 */
522[[gnu::returns_nonnull]]
524
525// -----------------------------------------------------------------------------
526// Context switch
527// -----------------------------------------------------------------------------
528/**
529 * Save the current execution context into `from`, restore `to`, and resume on
530 * `to`'s stack. Returns, on `from`'s stack, when a fiber later switches back
531 * into `from`.
532 */
533[[gnu::nonnull(1, 2)]]
535
536/**
537 * Initialise a fresh fiber context so the first srn_fiber_swap into it begins
538 * executing fn(arg) on `stack`.
539 *
540 * `fn` is the fiber's entry, a `void (*)(void *)` that runs on the new stack
541 * and receives `arg`. It must NEVER return -- there is no frame beneath it, so
542 * a return traps in the trampoline. Instead it transfers control away with
543 * srn_fiber_switch (to yield) or srn_fiber_switch_final (when finished), and
544 * as its first action it calls srn_fiber_on_entry(). Typical shape:
545 *
546 * static void worker(void *arg) {
547 * srn_fiber_on_entry(&scheduler); // hand the switch to the
548 * sanitizer
549 * ... do the fiber's work using arg ...
550 * srn_fiber_switch_final(&scheduler); // finished, never returns
551 * }
552 */
553[[gnu::nonnull(1, 3)]]
555 srn_fiber_ctx_t *fiber_ctx, srn_fiber_stack_t stack, void (*fn)(void *), void *arg
556);
557
558/**
559 * Switch from `from` to `to`, both live fibers, telling AddressSanitizer about
560 * the stack change. `from` resumes where it left off when something later
561 * switches back into it.
562 */
563[[gnu::nonnull(1, 2)]]
565
566/**
567 * Like srn_fiber_switch, but for a fiber that has finished and must not be
568 * resumed, control transfers to `to` and never comes back. The fiber's entry
569 * function therefore ends at this call -- any code after it is unreachable,
570 * which is why this is [[noreturn]]. Nothing is freed here. The spent fiber's
571 * stack is left frozen and reclaimed later by its owner (the scheduler reaps a
572 * finished fiber and frees its stack via srn_fiber_stack_free).
573 */
574[[noreturn]] [[gnu::nonnull(1)]]
576
577/**
578 * Call as the first action inside a fresh fiber's entry. `from` is the fiber
579 * that started this one (the scheduler, or the bootstrap thread fiber). It
580 * completes the switch for AddressSanitizer and, since `from`'s stack bounds
581 * only become discoverable here, records them into `from` so a later switch
582 * back is tracked. `from` may be null. A no-op when the sanitizer is not in
583 * use.
584 */
586
587/**
588 * Call when a finished fiber is reaped, after it has switched away for the
589 * last time. Releases the fiber's ThreadSanitizer handle. A no-op when the
590 * sanitizer is not in use.
591 */
592[[gnu::nonnull(1)]]
593void srn_fiber_on_reap(srn_fiber_t *fiber);
594
595/**
596 * Represent the calling OS thread as the running fiber ("#0"), so the
597 * scheduler or a test can switch away from it and back. The thread's stack
598 * bounds are not queried here (there is no portable way). Under the sanitizer
599 * they are recorded by the first fiber's srn_fiber_on_entry. The saved context
600 * is written by the first switch away.
601 */
602[[gnu::nonnull(1)]]
604
605// -----------------------------------------------------------------------------
606// Stack provider (rt/fiber/stack_*.c)
607// -----------------------------------------------------------------------------
608/**
609 * Allocate a stack of at least `size` usable bytes, or
610 * SRN_CONFIG_DEFAULT_FIBER_STACK_SIZE when `size` is 0, plus a guard band of
611 * `guard_pages` OS pages below it. `guard_pages` must be at least 1. Platform
612 * specific.
613 */
614[[nodiscard]]
615srn_fiber_stack_t srn_fiber_stack_alloc(size_t size, size_t guard_pages);
616
618
619// -----------------------------------------------------------------------------
620// Helpers
621// -----------------------------------------------------------------------------
623 return (size_t)((char *)s.start - (char *)s.limit);
624}
625
626/**
627 * Return the id of the worker that the calling os thread is running, or
628 * SIZE_MAX when the calling thread is not a worker.
629 */
631
632/**
633 * Whether the scheduler still accepts new IO submissions. True only while
634 * RUNNING; false once `srn_sched_drain` or `srn_sched_stop` has begun winding
635 * the pool down. The IO bridge reads this to fence submissions during a
636 * wind-down, so a fiber's IO call comes back cancelled instead of parking on an
637 * op the wind-down would have to wait out.
638 */
639[[gnu::nonnull(1)]]
641
642/**
643 * Rouse parked workers so the owner of `channel` consumes its completions.
644 * The reactor's notify hook. The wake is currently a broadcast, `channel`
645 * names the worker that must look, but every parked worker is notified and
646 * the ones with nothing to do re-park.
647 */
648void srn_sched_wake_worker(srn_scheduler_t *sched, size_t channel);
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:324
size_t srn_worker_id_t
Definition fiber.h:163
srn_fiber_t * srn_fiber_worker_loop(void)
The worker's loop of the worker running on the calling os thread.
Definition scheduler.c:1101
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:87
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.
void srn_sched_wake_worker(srn_scheduler_t *sched, size_t channel)
Rouse parked workers so the owner of channel consumes its completions.
Definition scheduler.c:1171
void srn_fiber_ready(srn_fiber_t *fiber)
Mark a suspended fiber runnable again, waking it when the event it awaited occurs.
Definition scheduler.c:1083
srn_fiber_result_t srn_fiber_wait_for(srn_fiber_t *target)
Block the calling fiber until target finishes, then return its result.
Definition scheduler.c:1130
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...
Definition fiber.c:258
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:250
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...
static size_t srn_fiber_stack_size(srn_fiber_stack_t s)
Definition fiber.h:622
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_sched_drain(srn_scheduler_t *sched)
Ask a running scheduler to wind down gracefully.
Definition scheduler.c:1002
#define SRN_FIBER_NAME_MAX
Size of the embedded debug name buffer on every fiber, terminator included.
Definition fiber.h:186
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:153
void srn_sched_shutdown(srn_scheduler_t *sched)
The one stop tear down of the fiber subsystem, should be called once srn_sched_run has returned.
Definition scheduler.c:333
bool srn_sched_accepting_submissions(srn_scheduler_t *sched)
Whether the scheduler still accepts new IO submissions.
Definition scheduler.c:1163
void srn_fiber_stack_free(srn_fiber_stack_t stack)
void srn_sched_stop(srn_scheduler_t *sched)
Ask a running scheduler to stop.
Definition scheduler.c:978
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:630
srn_fiber_state_e
Definition fiber.h:229
@ SRN_FIBER_NEW
Created, stack mapped, never resumed.
Definition fiber.h:231
@ SRN_FIBER_RUNNING
Currently executing.
Definition fiber.h:235
@ SRN_FIBER_READY
On the run queue, eligible to run.
Definition fiber.h:233
@ SRN_FIBER_DONE
Entry returned. The result is final.
Definition fiber.h:239
@ SRN_FIBER_SUSPENDED
Parked off the run queue, awaits srn_fiber_ready.
Definition fiber.h:237
srn_fiber_result_t(* srn_fiber_entry_t)(srn_context_t *ctx, void *arg)
The function a fiber runs.
Definition fiber.h:250
srn_scheduler_t * srn_sched_init(srn_engine_t *engine)
Definition scheduler.c:263
srn_fiber_t * srn_fiber_current(void)
The fiber currently running on this os thread, or null when the calling thread is not a worker or the...
Definition scheduler.c:1097
void srn_fiber_switch(srn_fiber_t *from, srn_fiber_t *to)
Switch from from to to, both live fibers, telling AddressSanitizer about the stack change.
Definition fiber.c:65
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:202
void srn_fiber_on_entry(srn_fiber_t *from)
Call as the first action inside a fresh fiber's entry.
Definition fiber.c:110
bool(* srn_fiber_park_fn)(srn_fiber_t *self, void *arg)
Suspend commit callback.
Definition fiber.h:261
void srn_fiber_schedule(srn_fiber_t *fiber)
Schedule a NEW fiber, making it eligible to run.
Definition scheduler.c:638
void * srn_fiber_result_t
What a fiber's entry produces, type-erased.
Definition fiber.h:171
enum srn_fiber_state_e srn_fiber_state_t
void srn_sched_run(srn_scheduler_t *sched, size_t nworkers)
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every...
Definition scheduler.c:875
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:132
void srn_fiber_autoname(srn_engine_t *engine, char *dst, size_t size)
Write the autogenerated debug name for a new fiber into dst.
Definition scheduler.c:1141
void srn_fiber_suspend(srn_fiber_park_fn commit, void *arg)
Park the running fiber until a party calls srn_fiber_ready.
Definition scheduler.c:1063
void srn_fiber_yield(void)
Yield cooperatively, re-enqueue the running fiber and run the next ready one.
Definition scheduler.c:1039
srn_worker_id_t srn_sched_current_worker_id(void)
Return the id of the worker that the calling os thread is running, or SIZE_MAX when the calling threa...
Definition scheduler.c:1157
void * arg
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:54
The saved context of a suspended fiber is a single word, its stack pointer at the moment it was switc...
Definition fiber.h:197
void * sp
Definition fiber.h:198
One stack per fiber, mapped with a guard band at the low end so an overflow faults deterministically ...
Definition fiber.h:217
void * guard
Low base of the protected guard band that detects stack overflows.
Definition fiber.h:223
void * limit
Low end of usable region.
Definition fiber.h:221
void * start
High end, stack pointer initialises to this address.
Definition fiber.h:219
char name[SRN_FIBER_NAME_MAX]
Debug name, the caller's choice copied at creation, or autogenerated when the caller passed none (see...
Definition fiber.h:328
srn_fiber_entry_t entry
Definition fiber.h:274
_Atomic srn_fiber_state_t state
The lifecycle state.
Definition fiber.h:272
srn_fiber_t * link
Intrusive link threading this fiber onto one of the scheduler's singly-linked lists (the ready run qu...
Definition fiber.h:302
srn_fiber_t * waiters
Head of the list of fibers blocked in srn_fiber_wait_for on this fiber.
Definition fiber.h:308
void * park_arg
Definition fiber.h:284
srn_fiber_result_t result
Set when state reaches SRN_FIBER_DONE.
Definition fiber.h:278
srn_fiber_park_fn park_commit
While this fiber is suspending, the commit the worker routine runs once the fiber is off the stack,...
Definition fiber.h:283
srn_context_t * ctx
Definition fiber.h:273
srn_fiber_stack_t stack
Definition fiber.h:266
srn_fiber_t * reg_prev
Registry links.
Definition fiber.h:321
void * arg
Definition fiber.h:275
srn_fiber_t * reg_next
Definition fiber.h:322
srn_fiber_ctx_t fiber_ctx
Saved stack pointer (see srn_fiber_ctx_t).
Definition fiber.h:265
Platform neutral static tracepoints.