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

The Serene runtime is a static library linked into code produced by a Serene compiler. It provides the runtime facilities that a program needs, such as the in-memory object model (values, symbols, namespaces), the persistent data structures, the cooperative fiber scheduler, a memory manager, the FFI subsystem, and a JIT engine among other things.

While the runtime is meant to be used with Serene, nothing stops you from using it with other compilers or even generic programs (for example, I used the runtime with my wayland compositor).

How to build the runtime

The runtime builds with Meson and a C23 compiler (Clang or GCC). A Nix flake pins every dependency, so the easiest path is:

# Run these from the repository root.
nix build '.#runtime' # build the standalone runtime as a Nix package
# or, to hack on it:
nix develop # a shell with the toolchain + deps
meson setup build runtime # build dir 'build', source dir 'runtime'
meson compile -C build

Without Nix you need a C23 toolchain with the following dependencies:

  • pthreads (on POSIX)
  • LLVM 22 or newer only for the full build; the standalone runtime don't need it

Serene build vs Standalone build

The with-serene feature (enabled by default) controls the Serene language layer: the value model, ABI, namespaces and keywords, and the JIT (and with it the LLVM dependency).

# full runtime (default): generic layer + Serene layer + LLVM
meson setup build runtime
# substrate only: memory manager, fibers, data structures. No value model, no LLVM.
meson setup build runtime -Dwith-serene=disabled

Install

meson setup build runtime --prefix=/usr/local # add -Dwith-serene=disabled for generic layer only
meson install -C build # sudo for a system prefix

This installs the static library, the public headers under $prefix/include/serene/, and a serene.runtime.pc pkg-config file.

Using the runtime in your project

Find the runtime through pkg-config. The serene.runtime.pc file carries the include path, the transitive libraries, and the right -DSRN_WITH_SERENE define so your code sees the same struct layout the library was built with. The public headers use C23 attributes, so compile your code as C23.

A minimal program that runs one fiber (works against a substrate-only build):

#include <stdio.h>
#include <serene/runtime.h>
static int ok; // a fiber result must be non-null
static srn_fiber_result_t say_hello(srn_context_t *ctx, void *arg) {
(void)ctx;
(void)arg;
printf("hello from a fiber\n");
return &ok;
}
int main(void) {
srn_engine_t *engine = srn_engine_make(mm, NULL); // NULL config -> defaults
srn_scheduler_t *sched = engine->scheduler;
(void)srn_fiber_make(ctx, sched, say_hello, NULL, 0);
srn_sched_run(sched, 1);
srn_engine_shutdown(engine); // tears down the scheduler before the context
}
static srn_fiber_result_t say_hello(srn_context_t *ctx, void *arg)
Definition 01_hello.c:34
int main(void)
Definition 01_hello.c:41
static int ok
Definition hello.c:28
srn_context_t * srn_context_make(srn_engine_t *engine)
Make an empty context, by allocating a new memory block.
Definition context.c:39
int srn_context_release(srn_context_t *ctx)
Definition context.c:64
void srn_mm_shutdown(srn_mm_t *mm)
Shut down the memory manager and release the resources.
Definition default.c:374
srn_mm_t * srn_mm_init(const srn_configuration_t *config)
Initialize the memory manager, this function will panic on error.
Definition default.c:322
void srn_engine_shutdown(srn_engine_t *engine)
Definition engine.c:154
srn_engine_t * srn_engine_make(srn_mm_t *mm, const srn_configuration_t *config)
Create the engine over mm, copying config (the runtime's knobs) into it.
Definition engine.c:101
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_result_t
What a fiber's entry produces, type-erased.
Definition fiber.h:157
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:843
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:51
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:75
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:107

Build it with whatever compiler you use:

cc -std=c23 $(pkg-config --cflags serene.runtime) hello.c \
$(pkg-config --libs serene.runtime) -o hello

Ready-to-copy templates for Make, Meson, and CMake live in examples/usage/, and runnable fiber demos in examples/fibers/ (built by default; configure with -Dwith-examples=disabled to skip them).

Browse the API

  • Functions — a flat, alphabetical index of every runtime function. Most of the API is free functions (srn_*), so this is usually the fastest way in.
  • Data Structures — every struct, union, enum, and typedef.
  • Files — browse by header. Public headers live under serene/rt/; the serene, rt, and jit folders expand to the full tree.

Core object model

Persistent data structures

Concurrency: fibers

Asynchronous IO: the reactor

  • Overview: the reactor implementation overview — the architecture, the data flow, and the backend-author contract. Pairs with the "The Reactor" and "Fiber I/O" sections of the language report.
  • reactor.h — the IO reactor: one completion-based engine (an io_uring-style submit/complete contract) running on its own dedicated OS thread. Each worker talks to it over a channel – a submission/completion SPSC ring pair – and the reactor owns the rings. Platform backends realise the contract: epoll on Linux today, with io_uring/kqueue/IOCP planned.
  • fiber/io.h — the fiber-facing IO surface: suspending calls (srn_fiber_read, srn_fiber_write, srn_fiber_sleep (family), srn_fiber_accept, srn_fiber_connect, and the send/recv family) plus non-suspending helpers (srn_fiber_socket, bind, listen, open, ...). A suspending call reads like a blocking one but suspends only the calling fiber; its worker keeps running others, and the fiber resumes with a per-op result – a fallible value carrying either the outcome or an error – once the reactor finishes. A pollable fd (socket, pipe, ...) goes through the reactor; a regular file, which a readiness backend cannot poll, is served by a synchronous syscall on the worker. Suspending calls must run on a fiber that is on a worker.
  • Internals: the backend vtable in rt/reactor/backend.h and the epoll backend in rt/reactor/backend/epoll.c; the reactor core (channels, the reactor thread, the dispatch loop, the timer heap) in rt/reactor/reactor.c; and the fiber-to-reactor bridge (submit-on-park, completion -> wake) in rt/fiber/io.c.
  • Deferred: a thread-pool offload for blocking work. Regular-file IO is currently served by a blocking syscall on the worker; a dedicated pool that decouples that from the worker count is designed but not yet built.

Memory, FFI, and JIT

TODOs

  • Fix the comments to be in markdown
  • Make the compiler warn/errors more strict

License

The runtime library — the sources compiled into libserene.runtime and the public headers under serene/ — is licensed under the GNU LGPL v3, so you can link it into programs under other licenses. The runtime's tests, examples, and build tooling, and the rest of the Serene project, are under the GNU GPL v3. Each file states its license in its header; the full texts are in LICENSE.