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 * Overridable (bounded model checking builds it small -- the
32 * algorithm is capacity-agnostic, indices are unbounded and masked at
33 * access, so a small cap exercises identical logic in a tractable
34 * state space; see test/cbmc/deque_harness.c).
35 */
36#ifndef XTC_DEQUE_CAP
37#define XTC_DEQUE_CAP 256
38#endif
39#define XTC_DEQUE_MASK (XTC_DEQUE_CAP - 1)
40
41typedef struct xtc_deque {
42 /*
43 * top and bottom are 64-bit indices; we mask them by the
44 * capacity at slot access. Using 64-bit indices avoids any
45 * wrap concern inside the lifetime of a process.
46 *
47 * - `bottom` is owned by the owner thread; only it writes.
48 * - `top` is read by everyone, written via CAS by thieves
49 * (to claim a slot) and by the owner (when popping the
50 * last item to break a tie with a thief).
51 */
52 _Atomic int64_t top;
53 _Atomic int64_t bottom;
54 _Atomic(void *) buf[XTC_DEQUE_CAP];
56
57static inline void
58xtc_deque_init(xtc_deque_t *d)
59{
60 int i;
61 atomic_store_explicit(&d->top, 0, memory_order_relaxed);
62 atomic_store_explicit(&d->bottom, 0, memory_order_relaxed);
63 for (i = 0; i < XTC_DEQUE_CAP; i++)
64 atomic_store_explicit(&d->buf[i], NULL, memory_order_relaxed);
65}
66
67/*
68 * Owner-side push. Returns XTC_OK on success, XTC_E_AGAIN if the
69 * deque is full (caller must use the slow-path overflow queue).
70 */
71static inline int
72xtc_deque_push(xtc_deque_t *d, void *v)
73{
74 int64_t b = atomic_load_explicit(&d->bottom, memory_order_relaxed);
75 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
76 if (b - t >= XTC_DEQUE_CAP) return XTC_E_AGAIN;
77 atomic_store_explicit(&d->buf[b & XTC_DEQUE_MASK], v,
78 memory_order_relaxed);
79 atomic_thread_fence(memory_order_release);
80 atomic_store_explicit(&d->bottom, b + 1, memory_order_relaxed);
81 return XTC_OK;
82}
83
84/*
85 * Owner-side pop (LIFO). Returns NULL if the deque is empty.
86 *
87 * The tricky case is a single-element deque: both the owner and a
88 * thief can race for it. We resolve via CAS on `top`: whoever wins
89 * gets the item; the loser sees empty.
90 */
91static inline void *
92xtc_deque_pop(xtc_deque_t *d)
93{
94 int64_t b = atomic_load_explicit(&d->bottom, memory_order_relaxed) - 1;
95 int64_t t;
96 void *x;
97
98 atomic_store_explicit(&d->bottom, b, memory_order_relaxed);
99 atomic_thread_fence(memory_order_seq_cst);
100 t = atomic_load_explicit(&d->top, memory_order_relaxed);
101
102 if (t > b) {
103 /* Empty. */
104 atomic_store_explicit(&d->bottom, t, memory_order_relaxed);
105 return NULL;
106 }
107
108 x = atomic_load_explicit(&d->buf[b & XTC_DEQUE_MASK],
109 memory_order_relaxed);
110 if (t < b) return x; /* uncontested */
111
112 /* t == b: last element, race with thief. */
113 if (!atomic_compare_exchange_strong_explicit(
114 &d->top, &t, t + 1,
115 memory_order_seq_cst, memory_order_relaxed))
116 x = NULL; /* lost the race */
117 atomic_store_explicit(&d->bottom, b + 1, memory_order_relaxed);
118 return x;
119}
120
121/*
122 * Thief-side steal (FIFO). Returns NULL on empty or on losing the
123 * CAS race with another thief or with the owner's pop.
124 */
125static inline void *
126xtc_deque_steal(xtc_deque_t *d)
127{
128 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
129 int64_t b;
130 void *x;
131
132 atomic_thread_fence(memory_order_seq_cst);
133 b = atomic_load_explicit(&d->bottom, memory_order_acquire);
134
135 if (t >= b) return NULL; /* empty */
136 x = atomic_load_explicit(&d->buf[t & XTC_DEQUE_MASK],
137 memory_order_relaxed);
138 if (!atomic_compare_exchange_strong_explicit(
139 &d->top, &t, t + 1,
140 memory_order_seq_cst, memory_order_relaxed))
141 return NULL; /* lost race */
142 return x;
143}
144
145/*
146 * Approximate length. May return slightly stale values; useful for
147 * stealing heuristics ("which loop is busiest?").
148 */
149static inline int64_t
150xtc_deque_len(const xtc_deque_t *d)
151{
152 int64_t b = atomic_load_explicit(&d->bottom, memory_order_acquire);
153 int64_t t = atomic_load_explicit(&d->top, memory_order_acquire);
154 return b - t;
155}
156
157#endif /* XTC_DEQUE_H */