Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
Fiber subsystem implementation overview

This page is the implementation map for the fiber subsystem: the files, the objects, and how a fiber is created, switched, scheduled, and torn down. For the design and its rationale — why stackful, why fixed stacks with a guard page, why a reason-agnostic suspend/ready pair, why a work-stealing deque — see the "Fibers", chapter of the language report. The I/O reactor this subsystem waits on is documented in Reactor implementation overview.

Module map

File Role
serene/rt/fiber.h Public surface: srn_fiber_t (fully public), the state enum, the scheduler/fiber verbs, and the context-switch declarations.
rt/fiber/fiber.c Architecture-neutral fiber mechanics: srn_fiber_make, the launcher, the switch wrappers, srn_fiber_on_entry/on_reap, per-thread init.
rt/fiber/scheduler.c The one scheduler and its workers: run queues (Chase–Lev deques + a shared global queue), srn_sched_run/stop/drain/shutdown, work-stealing, parking, and quiescence.
rt/fiber/arch/ctx_<cpu>.c, arch/switch_<cpu>.S, arch/stack_<cpu>.h Per-architecture context fabrication and the srn_fiber_swap/srn_fiber_trampoline assembly.
rt/fiber/platform/stack_posix.c The stack provider: mmap + guard page, behind srn_fiber_stack_alloc/free.
rt/fiber/platform/thread_posix.c, serene/rt/fiber/thread.h The OS-thread shim (spawn/join, mutex, cond), modelled by intent, not a 1:1 pthread wrapper.
rt/fiber/io.c, serene/rt/fiber/io.h The fiber↔reactor bridge and the fiber-facing IO surface — see below.

The fiber object and its states

A srn_fiber_t is a function plus its own stack. Its saved context is a single word — the stack pointer at the moment it switched away (srn_fiber_ctx_t); the callee-saved registers live on the fiber's own stack, pushed by srn_fiber_swap. A fiber moves through five states:

Scheduling

There is exactly one srn_scheduler_t; it owns the ready work and a registry of every live fiber. It has no event loop. srn_sched_run(sched, nworkers) turns the calling OS thread into worker 0, spawns the rest, and drains work until the pool goes quiescent or a stop is requested.

  • Work stealing. Each worker owns a Chase–Lev deque; find_work looks in its own deque, then the shared global queue, then steals from each peer. Work produced while a fiber runs (a yield, a spawn, a wake) lands on the running worker's own deque; work from outside a worker, or deque overflow, goes to the global queue.
  • Suspend / ready / yield. The reason-agnostic core: srn_fiber_suspend parks the running fiber via a park-commit the worker runs after the fiber is off its stack (race-free); srn_fiber_ready flips a suspended fiber back to READY with a CAS; srn_fiber_yield re-enqueues and picks the next. This is the interface the reactor wakes fibers through.
  • Quiescence. The pool is done when nothing is runnable, every worker is parked, and the reactor has no operations in flight (srn_reactor_idle).
  • Stopping. srn_sched_drain winds down gracefully (fence new IO submissions, let in-flight ops finish); srn_sched_stop halts at the next clean fiber boundary and leaves queued fibers for srn_sched_shutdown to reap.

Terminology is precise throughout (see the report's glossary): a worker is the per-OS-thread state, the worker's loop stands in for the OS thread and picks the next fiber, fibers suspend while OS threads park.

The fiber↔reactor bridge

io.c is the only layer that knows both fibers and the reactor. A blocking-looking call (srn_fiber_read, srn_fiber_accept, …) builds a request on the fiber's own stack and suspends; the submit happens inside the park-commit (reactor_submit_commit), so the fiber is fully parked before the completion can land. srn_reactor_consume, run by the owning worker, copies each completion into the on-stack waiter, readies the fiber, and drops the in-flight count. Each call returns a per-op fallible value (maybe_error null on success). The non-suspending helpers (socket, bind, open, …) share that convention but never touch the reactor. Regular-file reads and writes, which a readiness backend cannot poll, are served by a synchronous syscall on the worker (see Reactor implementation overview).

Portability and sanitizers

Every platform/architecture file carries a whole-file target-macro guard, so the whole source set compiles for a cross target and only the matching files emit symbols (see the report's portability milestone). The switch path is annotated for AddressSanitizer and ThreadSanitizer — srn_fiber_t gains a fake_stack field under ASan and a tsan_fiber handle under TSan, so a build's struct layout depends on its sanitizer flags, and a consumer must match them.