libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_tnt.h
1/*-
2 * Copyright (c) 2026, The XTC Project -- All rights reserved.
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/xtc_tnt.h
6 * tnt -- a Tina-faithful, stackless Isolate layer (L4) built on
7 * top of the libxtc concurrency runtime. See the Tina reference at
8 * github.com/pmbanugo/tina.
9 *
10 * The decisive architectural choice is to map
11 * a Shard to one long-lived libxtc proc per xtc_exec loop, NOT an
12 * Isolate to a proc. The shard proc owns a dense typed arena of
13 * stackless Isolate structs and runs Tina's dispatch loop:
14 *
15 * drain inbox
16 * -> collect reactor completions
17 * -> for each ready type, for each dispatchable slot (budgeted):
18 * call handler -> interpret the returned transition/effect
19 * (commit staged I/O, enqueue sends, set state).
20 *
21 * Handlers NEVER block; they return a transition. Isolates are
22 * stackless arena structs; only the shard is a fiber.
23 *
24 * This header is the entire public surface. All ambient ctx_*
25 * style calls (here named xtc_tnt_*) are valid only during an active
26 * handler invocation -- they resolve the current shard + turn
27 * frame from thread-local state, exactly as Tina's TinaContext.
28 */
29
30#ifndef XTC_TNT_H
31#define XTC_TNT_H
32
33#include <stddef.h>
34#include <stdint.h>
35
36#ifdef __cplusplus
37extern "C" {
38#endif
39
40/* ---- Handles ------------------------------------------------------
41 *
42 * A 64-bit generational handle, bit-identical to Tina's layout:
43 *
44 * +----------+------------+-------------+--------------+
45 * | shard_id | type_id | slot_index | generation |
46 * | (8 bit) | (8 bit) | (20 bit) | (28 bit) |
47 * +----------+------------+-------------+--------------+
48 *
49 * The generation is the key to safety: when an Isolate is torn down,
50 * its slot's generation counter increments, so any handle still
51 * referencing the old generation is recognisably stale. A send to a
52 * stale handle returns XTC_TNT_SEND_STALE_HANDLE rather than delivering to
53 * whatever new Isolate now occupies the slot.
54 */
55typedef uint64_t xtc_tnt_handle_t;
56
57#define XTC_TNT_HANDLE_NONE ((xtc_tnt_handle_t)0)
58
59#define XTC_TNT_SHARD_BITS 8
60#define XTC_TNT_TYPE_BITS 8
61#define XTC_TNT_SLOT_BITS 20
62#define XTC_TNT_GEN_BITS 28
63
64#define XTC_TNT_SLOT_MAX ((1u << XTC_TNT_SLOT_BITS) - 1u)
65#define XTC_TNT_GEN_MASK ((1u << XTC_TNT_GEN_BITS) - 1u)
66
67static inline xtc_tnt_handle_t
68xtc_tnt_handle_make(uint8_t shard, uint8_t type, uint32_t slot, uint32_t gen)
69{
70 return ((xtc_tnt_handle_t)shard << (XTC_TNT_TYPE_BITS + XTC_TNT_SLOT_BITS +
71 XTC_TNT_GEN_BITS)) |
72 ((xtc_tnt_handle_t)type << (XTC_TNT_SLOT_BITS + XTC_TNT_GEN_BITS)) |
73 ((xtc_tnt_handle_t)(slot & XTC_TNT_SLOT_MAX) << XTC_TNT_GEN_BITS) |
74 ((xtc_tnt_handle_t)(gen & XTC_TNT_GEN_MASK));
75}
76
77static inline uint8_t
78xtc_tnt_handle_shard(xtc_tnt_handle_t h)
79{
80 return (uint8_t)(h >> (XTC_TNT_TYPE_BITS + XTC_TNT_SLOT_BITS + XTC_TNT_GEN_BITS));
81}
82
83static inline uint8_t
84xtc_tnt_handle_type(xtc_tnt_handle_t h)
85{
86 return (uint8_t)((h >> (XTC_TNT_SLOT_BITS + XTC_TNT_GEN_BITS)) & 0xffu);
87}
88
89static inline uint32_t
90xtc_tnt_handle_slot(xtc_tnt_handle_t h)
91{
92 return (uint32_t)((h >> XTC_TNT_GEN_BITS) & XTC_TNT_SLOT_MAX);
93}
94
95static inline uint32_t
96xtc_tnt_handle_gen(xtc_tnt_handle_t h)
97{
98 return (uint32_t)(h & XTC_TNT_GEN_MASK);
99}
100
101/* ---- Transitions --------------------------------------------------
102 *
103 * A handler returns a transition -- a small value describing what the
104 * Isolate wants next, not how to schedule it. The scheduler (the
105 * shard) interprets it. This is the core abstraction that lets the
106 * effect interpreter be swapped for deterministic simulation later:
107 * the Isolate code is identical in production and in DST.
108 */
109typedef enum xtc_tnt_transition_kind {
110 XTC_TNT_DONE = 0, /* clean exit -- deallocate me */
111 XTC_TNT_YIELD, /* run me again next tick */
112 XTC_TNT_WAIT_MESSAGE, /* park until my mailbox has a message */
113 XTC_TNT_WAIT_IO, /* staged I/O via xtc_tnt_submit_io -- park on it */
114 XTC_TNT_CRASH /* voluntary failure -- "let it crash" */
115} xtc_tnt_transition_kind_t;
116
117/* Fault reasons mirror Tina's Isolate_Fault_Reason. */
118typedef enum xtc_tnt_fault_reason {
119 XTC_TNT_FAULT_NONE = 0,
120 XTC_TNT_FAULT_SPAWN_FAILED,
121 XTC_TNT_FAULT_UNIMPLEMENTED_TRANSITION,
122 XTC_TNT_FAULT_INIT_FAILED,
123 XTC_TNT_FAULT_CONTRACT_VIOLATION
124} xtc_tnt_fault_reason_t;
125
126typedef struct xtc_tnt_transition {
127 xtc_tnt_transition_kind_t kind;
128 xtc_tnt_fault_reason_t fault_reason;
130
131#define XTC_TNT_TRANSITION_DONE ((xtc_tnt_transition_t){ XTC_TNT_DONE, XTC_TNT_FAULT_NONE })
132#define XTC_TNT_TRANSITION_YIELD ((xtc_tnt_transition_t){ XTC_TNT_YIELD, XTC_TNT_FAULT_NONE })
133#define XTC_TNT_TRANSITION_WAIT_MESSAGE ((xtc_tnt_transition_t){ XTC_TNT_WAIT_MESSAGE, XTC_TNT_FAULT_NONE })
134#define XTC_TNT_TRANSITION_WAIT_IO ((xtc_tnt_transition_t){ XTC_TNT_WAIT_IO, XTC_TNT_FAULT_NONE })
135
136static inline xtc_tnt_transition_t
137xtc_tnt_transition_to_crash(xtc_tnt_fault_reason_t reason)
138{
139 xtc_tnt_transition_t t = { XTC_TNT_CRASH, reason };
140 return t;
141}
142
143/* ---- Messages -----------------------------------------------------
144 *
145 * Every message is a fixed-size envelope with a tag discriminant and a
146 * raw_union body: a user payload or an I/O completion. Switch on
147 * message->tag to determine which.
148 */
149
150/* Tag constants -- system tags are < XTC_TNT_USER_TAG_BASE; user tags
151 * must be >= XTC_TNT_USER_TAG_BASE (matching Tina's 0x0040 base). */
152#define XTC_TNT_USER_TAG_BASE 0x0040u
153
154/* I/O completion tags (delivered by the effect interpreter / reactor,
155 * never user-sendable). Values match Tina's IO_TAG_* constants. */
156#define XTC_TNT_IO_TAG_ACCEPT_COMPLETE 0x0012u
157#define XTC_TNT_IO_TAG_CONNECT_COMPLETE 0x0013u
158#define XTC_TNT_IO_TAG_SEND_COMPLETE 0x0014u
159#define XTC_TNT_IO_TAG_RECV_COMPLETE 0x0015u
160#define XTC_TNT_IO_TAG_CLOSE_COMPLETE 0x0018u
161
162/* System tags. */
163#define XTC_TNT_TAG_TIMER 0x0002u
164#define XTC_TNT_TAG_SHUTDOWN 0x0003u
165
166#define XTC_TNT_MAX_PAYLOAD_SIZE 96
167#define XTC_TNT_MAX_INIT_ARGS_SIZE 64
168
169typedef struct xtc_tnt_message {
170 uint16_t tag;
171 union {
172 /* User / system message body. payload is placed after an
173 * 8-byte aligned prefix (source + size + pad) so that, with
174 * the union itself 8-aligned, payload starts on an 8-byte
175 * boundary -- a u32/u64 in the payload loads aligned. */
176 struct {
177 xtc_tnt_handle_t source; /* offset 0 */
178 uint16_t payload_size; /* offset 8 */
179 uint8_t _pad[6]; /* pad to 16 */
180 uint8_t payload[XTC_TNT_MAX_PAYLOAD_SIZE]; /* offset 16 */
181 } user;
182 /* I/O completion body. */
183 struct {
184 int fd; /* which fd completed (or new fd) */
185 int32_t result; /* bytes transferred, or -errno */
186 void *buffer; /* reactor buffer (recv only) */
187 uint32_t buffer_len;
188 } io;
189 } body;
191
192/* ---- Isolate type descriptor --------------------------------------
193 *
194 * Three artifacts per Isolate type (Tina's IsolateTypeDescriptor): the
195 * struct stride, an init_fn (called once on spawn, returns the initial
196 * transition), and a handler_fn (called on every message / completion,
197 * returns the next transition). Both functions receive a rawptr to
198 * the Isolate's slot because the scheduler operates on heterogeneous
199 * typed arenas; use xtc_tnt_self_as to cast.
200 */
201typedef xtc_tnt_transition_t (*xtc_tnt_init_fn)(void *self, const void *args,
202 size_t args_size);
203typedef xtc_tnt_transition_t (*xtc_tnt_handler_fn)(void *self, xtc_tnt_message_t *msg);
204
205typedef struct xtc_tnt_type {
206 uint8_t id; /* 0..255 -- index in spec.types */
207 const char *name; /* for diagnostics */
208 uint32_t slot_count; /* arena capacity for this type */
209 size_t stride; /* sizeof(the Isolate struct) */
210 size_t working_memory_size; /* per-slot working arena bytes */
211 uint32_t mailbox_capacity; /* bounded mailbox depth */
212 uint32_t budget_weight; /* max slots dispatched per tick */
213 xtc_tnt_init_fn init_fn;
214 xtc_tnt_handler_fn handler_fn;
216
217/* ---- System spec --------------------------------------------------
218 *
219 * Tina's SystemSpec, trimmed to this slice. At boot xtc_tnt_start carves
220 * every arena from boot-time slab caches (one per type per shard) and
221 * never mallocs on the hot path again (disciplinary, per the doc).
222 */
223typedef struct xtc_tnt_spec {
224 const char *name;
225 const xtc_tnt_type_t *types; /* descriptor table */
226 int n_types;
227 int shard_count; /* number of shards (loops) */
228 uint32_t scratch_size; /* per-shard turn scratch arena bytes */
229 uint32_t recv_buf_size;/* per-recv reactor buffer bytes */
230 /* Optional: a type id to auto-spawn one instance of on shard 0
231 * once the runtime is up (Tina's boot spec spawns a root). Set to
232 * a negative value (default after memset) to disable; the canonical
233 * use is a "driver" or "root supervisor" isolate. -1 = none. */
234 int boot_type;
236
237/* ---- Send results -------------------------------------------------
238 *
239 * Tina's Send_Result. The sender decides what to do -- the layer
240 * never auto-retries or grows the mailbox.
241 */
242typedef enum xtc_tnt_send_result {
243 XTC_TNT_SEND_OK = 0, /* enqueued in the target mailbox */
244 XTC_TNT_SEND_MAILBOX_FULL, /* target mailbox at capacity -- DROPPED */
245 XTC_TNT_SEND_POOL_EXHAUSTED, /* no free envelope -- DROPPED */
246 XTC_TNT_SEND_STALE_HANDLE /* target dead / generation mismatch */
247} xtc_tnt_send_result_t;
248
249/* Spawn outcome. */
250typedef enum xtc_tnt_spawn_error {
251 XTC_TNT_SPAWN_OK = 0,
252 XTC_TNT_SPAWN_ARENA_FULL,
253 XTC_TNT_SPAWN_TYPE_NOT_ALLOCATED,
254 XTC_TNT_SPAWN_INIT_FAILED
255} xtc_tnt_spawn_error_t;
256
257/* I/O submit results. */
258typedef enum xtc_tnt_io_result {
259 XTC_TNT_IO_OK = 0,
260 XTC_TNT_IO_TOO_MANY, /* turn frame staging slots exhausted */
261 XTC_TNT_IO_BAD_FD
262} xtc_tnt_io_result_t;
263
264/* ---- Ambient ctx_* API (valid only during a handler) -------------- */
265
266/* Messaging. tag must be >= XTC_TNT_USER_TAG_BASE. Payload max 96 bytes.
267 * Drop-on-full with sender feedback, matching Tina's .mailbox_full. */
268xtc_tnt_send_result_t xtc_tnt_send(xtc_tnt_handle_t to, uint16_t tag,
269 const void *payload, size_t payload_size);
270
271/* Spawn a new Isolate of type_id on the CURRENT shard. The child's
272 * init_fn runs immediately (not deferred). Returns the new handle in
273 * *out_handle on success. */
274xtc_tnt_spawn_error_t xtc_tnt_spawn(uint8_t type_id, const void *args,
275 size_t args_size, xtc_tnt_handle_t *out_handle);
276
277/* I/O effects. These stage an operation into the current turn frame;
278 * the shard commits it into xtc_aio / the net layer only if the
279 * handler returns XTC_TNT_WAIT_IO. This is Tina's stage-then-commit. */
280
281/* Stage a recv on fd into a reactor buffer; on completion the Isolate
282 * receives a XTC_TNT_IO_TAG_RECV_COMPLETE message. */
283xtc_tnt_io_result_t xtc_tnt_submit_recv(int fd);
284
285/* Stage a send of buffer[0..len) on fd; buffer must live inside the
286 * Isolate struct (it is read at commit time, after the handler
287 * returns). On completion: XTC_TNT_IO_TAG_SEND_COMPLETE. */
288xtc_tnt_io_result_t xtc_tnt_io_send(int fd, const void *buffer, size_t len);
289
290/* Stage a close of fd; on completion: XTC_TNT_IO_TAG_CLOSE_COMPLETE. */
291xtc_tnt_io_result_t xtc_tnt_submit_close(int fd);
292
293/* Register a one-shot timer; after duration_ns the Isolate receives a
294 * message with the given tag. */
295void xtc_tnt_register_timer(uint64_t duration_ns, uint16_t tag);
296
297/* The current Isolate's identity. */
298xtc_tnt_handle_t xtc_tnt_self(void);
299
300/* The current shard's 0-based id. */
301uint8_t xtc_tnt_shard_id(void);
302
303/* The shard-wide scratch arena, reset before every handler call. For
304 * per-turn temporaries only; do not retain across handler returns.
305 * Returns a bump pointer of `size` bytes, or NULL if exhausted. */
306void *xtc_tnt_scratch_arena(size_t size);
307
308/* ---- Ergonomic helpers -------------------------------------------- */
309
310/* Debug-checked cast from the rawptr self to a typed Isolate pointer.
311 * In a debug build this validates the stride matches sizeof(T). */
312#define xtc_tnt_self_as(T, self_raw) ((T *)(self_raw))
313
314/* Cast a message payload to a typed pointer. */
315#define xtc_tnt_payload_as(T, msg) ((T *)((msg)->body.user.payload))
316
317/* ---- Boot ---------------------------------------------------------
318 *
319 * Validate the spec, carve all arenas from boot-time slab caches, pin
320 * one shard proc per loop, and enter the run loop. Blocks until the
321 * system stops (xtc_tnt_stop from any thread, or all shards idle). Returns
322 * XTC_OK-style 0 on clean shutdown, negative on a boot failure.
323 */
324int xtc_tnt_start(const xtc_tnt_spec_t *spec);
325
326/* Request the running system to stop (kicks every shard). Safe to
327 * call from a signal handler or another thread. */
328void xtc_tnt_stop(void);
329
330/* Spawn an Isolate from OUTSIDE the shard fibers (e.g. from main before
331 * xtc_tnt_start, or from a non-isolate listener). Routes the spawn onto
332 * the target shard and runs the init synchronously on that shard's next
333 * tick. shard is 0..shard_count-1. Thread-safe. */
334xtc_tnt_spawn_error_t xtc_tnt_spawn_on(uint8_t shard, uint8_t type_id,
335 const void *args, size_t args_size);
336
337#ifdef __cplusplus
338}
339#endif
340
341#endif /* XTC_TNT_H */