libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
coro_common.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_common.h
6 * Shared, substrate-agnostic memory-management helpers for the
7 * mmap-based coroutine substrates (coro_fctx.c, coro_uctx.c).
8 * Exactly one substrate is compiled per build (the others are empty
9 * TUs), so the `static` definitions here are instantiated once, into
10 * whichever substrate includes this header. This consolidates code
11 * that was previously duplicated byte-for-byte between fctx and uctx:
12 * - the per-thread fiber-stack pool (recycle freed stacks so a
13 * spawn skips mmap+mprotect),
14 * - the S1 stack-reclaim lever (madvise the unused stack tail on
15 * park).
16 *
17 * The winfiber substrate owns its stacks via CreateFiberEx (no mmap,
18 * no madvise), so it does NOT include this header. The sanitizer
19 * fiber-switch annotations remain per-substrate (their switch call
20 * sites are substrate-specific), so they are deliberately NOT here.
21 *
22 * Include AFTER coro_int.h (for struct xtc_coro) and the substrate's
23 * own headers (needs sysconf, madvise, atomics, XTC_THREAD_LOCAL).
24 */
25
26#ifndef XTC_CORO_COMMON_H
27#define XTC_CORO_COMMON_H
28
29/* ---- Fiber-stack pool (per-thread) --------------------------------
30 *
31 * Every fresh fiber stack costs an mmap + an mprotect(PROT_NONE) for its
32 * guard page, and mprotect write-locks the process-wide address-space
33 * lock (mmap_lock) -- so many threads spawning fibers concurrently
34 * serialize in the kernel, capping spawn throughput far below the core
35 * count (EC2 192-core finding: spawn flat ~7 M/s while the scheduler
36 * scales to 249 M/s). A per-thread free list recycles freed stacks --
37 * the guard page is already installed, so a reused stack skips BOTH the
38 * mmap and the mprotect, and the list is thread-local so the hot path
39 * takes no lock. Bounded so idle threads do not hoard memory.
40 *
41 * Stacks are pooled only at the current __xtc_stack_size; a stack freed
42 * at a different size (after xtc_set_stack_size) is munmap'd, not
43 * pooled, so the pool never hands back a wrong-sized stack. */
44#define XTC_STACK_POOL_MAX 64
45struct stack_pool {
46 void *slots[XTC_STACK_POOL_MAX];
47 size_t size; /* stack_sz these were allocated at */
48 int n;
49};
50static XTC_THREAD_LOCAL struct stack_pool __stack_pool;
51
52/* Pop a cached stack of `stack_sz` (guard already installed), or NULL. */
53static void *
54__stack_pool_get(size_t stack_sz)
55{
56 struct stack_pool *p = &__stack_pool;
57 if (p->n > 0 && p->size == stack_sz)
58 return p->slots[--p->n];
59 return NULL;
60}
61
62/* Return a stack to the pool if it fits (same size, room left); else the
63 * caller munmaps. Returns 1 if pooled, 0 if the caller must free it. */
64static int
65__stack_pool_put(void *base, size_t stack_sz)
66{
67 struct stack_pool *p = &__stack_pool;
68 if (p->n == 0) p->size = stack_sz; /* first entry sets the pool size */
69 if (p->size != stack_sz || p->n == XTC_STACK_POOL_MAX)
70 return 0;
71 p->slots[p->n++] = base;
72 return 1;
73}
74
75/* ---- Lever S1: stack-memory reclamation on park -------------------
76 *
77 * OFF by default. When enabled, a parking fiber returns the unused
78 * tail of its stack (everything below its current SP, beyond a live
79 * margin, page-aligned, above the guard) to the OS with
80 * madvise(MADV_DONTNEED); it faults back zero-filled on resume. See
81 * xtc_async.h. */
82#if defined(MADV_DONTNEED)
83static _Atomic int g_reclaim_on = 0;
84static _Atomic size_t g_reclaim_keep = 0; /* live margin below SP */
85static _Atomic uint64_t g_reclaim_count = 0; /* madvise calls made */
86
87int
88xtc_stack_reclaim_enable(size_t keep_bytes)
89{
90 long pg = sysconf(_SC_PAGESIZE);
91 size_t page = (pg > 0) ? (size_t)pg : 4096u;
92 if (keep_bytes == 0)
93 keep_bytes = page; /* default live margin: one page */
94 atomic_store(&g_reclaim_keep, keep_bytes);
95 atomic_store(&g_reclaim_on, 1);
96 return XTC_OK;
97}
98
99void
100xtc_stack_reclaim_disable(void)
101{
102 atomic_store(&g_reclaim_on, 0);
103}
104
105int
106xtc_stack_reclaim_enabled(void)
107{
108 return atomic_load(&g_reclaim_on);
109}
110
111uint64_t
112xtc_stack_reclaim_count(void)
113{
114 return atomic_load(&g_reclaim_count);
115}
116
117/*
118 * Reclaim the unused tail of the current fiber's stack. Called from a
119 * PARK path with `sp` = the fiber's current stack pointer (a stack
120 * address captured by the caller just before it jumps to the
121 * scheduler). Stacks grow DOWN, so the unused region is
122 * [stack_base + guard, sp - keep), page-aligned inward. No-op unless
123 * the reclaimable span exceeds one page (avoids fault churn on shallow
124 * fibers).
125 */
126static void
127coro_stack_shrink(struct xtc_coro *c, void *sp)
128{
129 long pgl;
130 size_t page, keep;
131 uintptr_t lo, hi, guard, base;
132
133 if (c == NULL || c->stack == NULL || !atomic_load(&g_reclaim_on))
134 return;
135 pgl = sysconf(_SC_PAGESIZE);
136 page = (pgl > 0) ? (size_t)pgl : 4096u;
137 guard = page; /* one guard page at the base */
138 keep = atomic_load(&g_reclaim_keep);
139
140 base = (uintptr_t)c->stack + guard; /* first usable byte */
141 hi = (uintptr_t)sp;
142 if (hi <= base + keep)
143 return; /* SP too close to the base */
144 /* Reclaimable tail: [base, hi - keep), rounded to whole pages so we
145 * never touch a partially-live page. */
146 hi = (hi - keep) & ~(uintptr_t)(page - 1); /* round DOWN */
147 lo = (base + page - 1) & ~(uintptr_t)(page - 1); /* round UP */
148 if (hi <= lo || hi - lo < page)
149 return; /* nothing worth a syscall */
150 if (madvise((void *)lo, (size_t)(hi - lo), MADV_DONTNEED) == 0)
151 (void)atomic_fetch_add(&g_reclaim_count, 1);
152}
153#else /* no MADV_DONTNEED: reclaim is a no-op */
154int xtc_stack_reclaim_enable(size_t k) { (void)k; return XTC_E_NOSYS; }
155void xtc_stack_reclaim_disable(void) { }
156int xtc_stack_reclaim_enabled(void) { return 0; }
157uint64_t xtc_stack_reclaim_count(void) { return 0; }
158#define coro_stack_shrink(c, sp) ((void)(c), (void)(sp))
159#endif
160
161#endif /* XTC_CORO_COMMON_H */