libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
coro_int.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/coro_int.h
6 * Internal types for the L2 coroutine substrate.
7 */
8
9#ifndef XTC_CORO_INT_H
10#define XTC_CORO_INT_H
11
12#include "coro_select.h" /* XTC_CORO_FCTX: which substrate is live */
13
14#if defined(_WIN32)
15# include <windows.h>
16#elif !defined(XTC_CORO_FCTX)
17# include <ucontext.h>
18#endif
19
20#include "xtc_async.h"
21#include "loop_int.h"
22
23/*
24 * The fiber context attached to an xtc_task_t when it was spawned
25 * via xtc_async. Lives in the per-task arena (currently malloc).
26 */
27struct xtc_coro {
28#if defined(_WIN32)
29 LPVOID fiber; /* the coroutine's own Win32 fiber */
30 LPVOID loop_fiber; /* return-to-loop fiber pointer */
31#elif !defined(XTC_CORO_FCTX)
32 ucontext_t ctx; /* the coroutine's own machine state */
33 ucontext_t loop_ctx; /* return-to-loop context (set on resume) */
34#else
35 /* fcontext substrate (coro_fctx.c): a single saved stack pointer.
36 * On make_fcontext it is the fresh entry point; each yield/await
37 * overwrites it with the coroutine's current resume point. The
38 * scheduler's return point is a per-thread cursor in coro_fctx.c,
39 * not stored here, because coroutines always return to the
40 * scheduler, never directly to one another. */
41 void *fctx;
42#endif
43 void *stack;
44 size_t stack_sz;
45 xtc_coro_fn fn;
46 void *arg;
47 intptr_t result;
48 int done; /* 1 once fn has returned */
49
50 xtc_task_t *self; /* back-pointer to our task */
51 /*
52 * The proc layer's handle for THIS fiber, published once by
53 * __proc_entry from the fiber itself.
54 *
55 * It exists because __current_proc is a thread-local that
56 * __xtc_fiber_ctx_save/restore carries across a yield, and under the
57 * multi-loop executor the restored value can name a DIFFERENT proc
58 * than the fiber now running -- measured on FreeBSD at 48909 wrong
59 * against 511 right, confirmed independently by locating a stack
60 * address (no thread-local involved) inside the registered coro
61 * stack ranges. A primitive that registers per-task state must not
62 * act on the wrong proc, so it reads its identity from the running
63 * fiber, and this is that link. void * to keep the proc layer's
64 * struct out of this header.
65 */
66 void *proc; /* struct xtc_proc * */
67 xtc_task_t *waiter; /* task awaiting this one (or NULL) */
68
69 /*
70 * When non-NULL, this coroutine has just registered itself as
71 * the `waiter` of another and is yielding into the loop with
72 * the intent of staying parked rather than rescheduling. The
73 * step function reads and clears this flag to decide between
74 * RESCHED and PENDING.
75 */
76 struct xtc_coro *_parked_on;
77
78 /*
79 * Sanitizer fiber-switch save token (ASan/TSan/LSan). Holds this
80 * coro's "fake stack" across a park so __sanitizer_start/finish_
81 * switch_fiber can track the user-space stack switch and stop
82 * mis-attributing stack memory. Unused (always NULL) in a
83 * non-sanitized build. See XTC_FIBER_SWITCH_ANNOTATE in
84 * coro_fctx.c / coro_uctx.c.
85 */
86 void *san_fake_stack;
87
88 /*
89 * TSan fiber-IDENTITY token (clang ThreadSanitizer only). TSan
90 * does NOT implement __sanitizer_*_switch_fiber; it needs each
91 * coroutine represented as a TSan "fiber object" (__tsan_create_
92 * fiber at create, __tsan_switch_to_fiber at every switch,
93 * __tsan_destroy_fiber at teardown) so it can carry per-fiber
94 * happens-before across cooperative switches instead of seeing
95 * them as one confused thread. Distinct from san_fake_stack (the
96 * two sanitizer models are mutually exclusive per build). Always
97 * NULL unless built with clang -fsanitize=thread. See
98 * XTC_TSAN_FIBERS in coro_fctx.c / coro_uctx.c.
99 */
100 void *tsan_fiber;
101
102 /* Valgrind stack id from VALGRIND_STACK_REGISTER for this coro's
103 * stack, set whenever `stack` is (coro_common.h). */
104 unsigned vg_stack_id;
105};
106
107/* Shared by loop.c -- the currently-running coroutine on this loop. */
108extern XTC_THREAD_LOCAL struct xtc_coro *__xtc_current_coro;
109
110/* Forward declarations for the dispatch glue. */
111int __xtc_coro_step(xtc_task_t *self, void *user);
112
113/*
114 * Internal spawn with an explicit pin flag. The public xtc_async wraps
115 * this with pinned=1 (the long-standing default: a coro's task is not
116 * work-stealable). pinned=0 places the task on the stealable deque so
117 * it can migrate across loops -- used by the proc layer when
118 * xtc_proc_opts_t.migratable is set. Every coro backend
119 * (coro_fctx/uctx/winfiber) defines it.
120 */
121int __xtc_async_ex(xtc_loop_t *loop, xtc_coro_fn fn, void *arg, int pinned,
122 xtc_task_t **out_task);
123
124/* The task wrapping the currently-running coroutine on this thread,
125 * or NULL when not running inside a coroutine. Lets lower-level
126 * primitives (e.g. xtc_amutex) find the current task to park it. */
127xtc_task_t *__xtc_current_task(void);
128
129/* xtc_loop_fini: unmap the loop's spare fiber stacks (a no-op for the
130 * winfiber substrate, which does not mmap its stacks). */
131void __xtc_coro_stack_spare_fini(xtc_loop_t *loop);
132
133/* __xtc_current_coro read through an out-of-line call on the CURRENT
134 * thread. Use this, not the variable, in code that may run after a
135 * stack switch on a migratable fiber: compilers keep a thread-local's
136 * address live across calls, so a direct read after the fiber resumed
137 * on another carrier sees the previous thread's slot. */
138struct xtc_coro *__xtc_current_coro_get(void);
139
140/*
141 * xtc_await's kill point, at the top of each park round (all three
142 * substrates). The first round has nothing pending unless the kill came
143 * first, and a later round runs only after a wake: if that wake was not
144 * the target's completion and a kill is pending, stop being the target's
145 * waiter (the target's DONE path reads target->waiter on this loop, so
146 * this is single-threaded with it) and deliver. __xtc_fiber_kill_poll
147 * unwinds (does not return), or latches the kill under a mask and
148 * returns, and the await then re-registers and waits on.
149 */
150#define AWAIT_KILL_POLL(me, target) do { \
151 if (__xtc_fiber_kill_poll != NULL) { \
152 if ((target)->waiter == (me)->self) \
153 (target)->waiter = NULL; \
154 __xtc_fiber_kill_poll(); \
155 } \
156} while (0)
157
158#endif /* XTC_CORO_INT_H */