libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
deque.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/deque.h
6 * A bounded Chase-Lev work-stealing deque. Single-owner pushes
7 * and pops on the bottom; multiple thieves CAS-steal from the
8 * top. See:
9 * Chase, D. & Lev, Y. "Dynamic Circular Work-Stealing Deque",
10 * SPAA 2005.
11 *
12 * M5 ships a fixed-capacity variant: callers fall back to a
13 * mutex-protected slow-path queue when push fails with
14 * XTC_E_AGAIN. M5.5 may add CAS-based growth.
15 *
16 * The deque stores `void *` and is generic; the run-queue uses
17 * xtc_task_t* by convention.
18 */
19
20#ifndef XTC_DEQUE_H
21#define XTC_DEQUE_H
22
23#include <stdatomic.h>
24#include <stdint.h>
25
26#include "xtc.h"
27
28/*
29 * Capacity must be a power of two. 256 is generous for a per-loop
30 * run queue; PG-style backends typically have <128 in flight.
31 */
32#define XTC_DEQUE_CAP 256
33#define XTC_DEQUE_MASK (XTC_DEQUE_CAP - 1)
34
35typedef struct xtc_deque {
36 /*
37 * top and bottom are 64-bit indices; we mask them by the
38 * capacity at slot access. Using 64-bit indices avoids any
39 * wrap concern inside the lifetime of a process.
40 *
41 * - `bottom` is owned by the owner thread; only it writes.
42 * - `top` is read by everyone, written via CAS by thieves
43 * (to claim a slot) and by the owner (when popping the
44 * last item to break a tie with a thief).
45 */
46 _Atomic int64_t top;
47 _Atomic int64_t bottom;
48 _Atomic(void *) buf[XTC_DEQUE_CAP];
50
51static inline void
52xtc_deque_init(xtc_deque_t *d)
53{
54 int i;
55 atomic_store_explicit(&d->top, 0, memory_order_relaxed);
56 atomic_store_explicit(&d->bottom, 0, memory_order_relaxed);
57 for (i = 0; i < XTC_DEQUE_CAP; i++)
58 atomic_store_explicit(&d->buf[i], NULL, memory_order_relaxed);
59}
60
61/*
62 * Owner-side push. Returns XTC_OK on success, XTC_E_AGAIN if the
63 * deque is full (caller must use the slow-path overflow queue).
64 */
65static inline int
66xtc_deque_push(xtc_deque_t *d, void *v)
67{
68 int64_t b = atomic_load_explicit(&d->bottom, memory_order_relaxed);
69 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
70 if (b - t >= XTC_DEQUE_CAP) return XTC_E_AGAIN;
71 atomic_store_explicit(&d->buf[b & XTC_DEQUE_MASK], v,
72 memory_order_relaxed);
73 atomic_thread_fence(memory_order_release);
74 atomic_store_explicit(&d->bottom, b + 1, memory_order_relaxed);
75 return XTC_OK;
76}
77
78/*
79 * Owner-side pop (LIFO). Returns NULL if the deque is empty.
80 *
81 * The tricky case is a single-element deque: both the owner and a
82 * thief can race for it. We resolve via CAS on `top`: whoever wins
83 * gets the item; the loser sees empty.
84 */
85static inline void *
86xtc_deque_pop(xtc_deque_t *d)
87{
88 int64_t b = atomic_load_explicit(&d->bottom, memory_order_relaxed) - 1;
89 int64_t t;
90 void *x;
91
92 atomic_store_explicit(&d->bottom, b, memory_order_relaxed);
93 atomic_thread_fence(memory_order_seq_cst);
94 t = atomic_load_explicit(&d->top, memory_order_relaxed);
95
96 if (t > b) {
97 /* Empty. */
98 atomic_store_explicit(&d->bottom, t, memory_order_relaxed);
99 return NULL;
100 }
101
102 x = atomic_load_explicit(&d->buf[b & XTC_DEQUE_MASK],
103 memory_order_relaxed);
104 if (t < b) return x; /* uncontested */
105
106 /* t == b: last element, race with thief. */
107 if (!atomic_compare_exchange_strong_explicit(
108 &d->top, &t, t + 1,
109 memory_order_seq_cst, memory_order_relaxed))
110 x = NULL; /* lost the race */
111 atomic_store_explicit(&d->bottom, b + 1, memory_order_relaxed);
112 return x;
113}
114
115/*
116 * Thief-side steal (FIFO). Returns NULL on empty or on losing the
117 * CAS race with another thief or with the owner's pop.
118 */
119static inline void *
120xtc_deque_steal(xtc_deque_t *d)
121{
122 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
123 int64_t b;
124 void *x;
125
126 atomic_thread_fence(memory_order_seq_cst);
127 b = atomic_load_explicit(&d->bottom, memory_order_acquire);
128
129 if (t >= b) return NULL; /* empty */
130 x = atomic_load_explicit(&d->buf[t & XTC_DEQUE_MASK],
131 memory_order_relaxed);
132 if (!atomic_compare_exchange_strong_explicit(
133 &d->top, &t, t + 1,
134 memory_order_seq_cst, memory_order_relaxed))
135 return NULL; /* lost race */
136 return x;
137}
138
139/*
140 * Approximate length. May return slightly stale values; useful for
141 * stealing heuristics ("which loop is busiest?").
142 */
143static inline int64_t
144xtc_deque_len(const xtc_deque_t *d)
145{
146 int64_t b = atomic_load_explicit(&d->bottom, memory_order_acquire);
147 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
148 return b - t;
149}
150
151#endif /* XTC_DEQUE_H */