Serene Runtime
1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
spsc_ring.c
Go to the documentation of this file.
1
/* -*- C -*-
2
* Serene programming language
3
* Copyright (C) 2019-2026 Sameer Rahmani <
[email protected]
>
4
*
5
* This library is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU Lesser General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* (at your option) any later version.
9
*
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU Lesser General Public License for more details.
14
*
15
* You should have received a copy of the GNU Lesser General Public License
16
* along with this library. If not, see <https://www.gnu.org/licenses/>.
17
*/
18
19
#include "
serene/rt/impl/spsc_ring.h
"
20
21
#include "
serene/rt/context.h
"
22
#include "
serene/utils.h
"
23
24
#define MIN_MAG 2
25
#define MAX_MAG MAX_POW2_EXP_FOR_UNSIGNED(SRN_SPSC_RING_CAP_TYPE)
26
27
srn_spsc_ring_t
*
srn_spsc_ring_make
(
srn_context_t
*ctx,
size_t
magnitude,
size_t
elem_size) {
28
PANIC_IF_NULL
(ctx);
29
30
// Capacity is `2 ^ magnitude`, so it is always a power of two and a slot
31
// index can be masked rather than divided. The magnitude must stay in range:
32
// below MIN_MAG the ring is too small to be useful, and above MAX_MAG the
33
// shift no longer fits `SRN_SPSC_RING_CAP_TYPE`. An out-of-range request
34
// fails as a value carrying an error rather than aborting.
35
if
((magnitude <
MIN_MAG
) || (magnitude >
MAX_MAG
)) {
36
return
FAIL
(ctx,
srn_spsc_ring_t
,
WRONG_RANGE
,
"Magnitude is not in the supported range."
);
37
}
38
39
srn_spsc_ring_t
*r =
ALLOC
(ctx,
srn_spsc_ring_t
);
40
41
// A fresh ring is empty -- head and tail both start at zero -- and carries no
42
// error. `mask` is `cap - 1`, the low bits that select a slot, and `buf`
43
// holds `cap` slots of `elem_size` bytes each.
44
atomic_init(&r->
head
, 0);
45
atomic_init(&r->
tail
, 0);
46
47
r->maybe_error =
nullptr
;
48
r->
cap
= ((
SRN_SPSC_RING_CAP_TYPE
)1U << magnitude);
49
r->
mask
= r->
cap
- 1;
50
r->
elem_size
= elem_size;
51
// The buffer multiply must not wrap, a wrapped size would allocate a tiny
52
// buffer while push/pop still index up to `cap` slots.
53
PANIC_IF
(elem_size == 0,
"'elem_size' must be nonzero"
);
54
PANIC_IF
(r->
cap
> SIZE_MAX / elem_size,
"ring buffer size overflows"
);
55
r->
buf
=
ALLOCN
(ctx,
unsigned
char
, (r->
cap
* elem_size));
56
PANIC_IF_NULL
(r->
buf
);
57
58
return
r;
59
}
60
61
bool
srn_spsc_ring_push
(
srn_spsc_ring_t
*r,
const
void
*elem) {
62
// The producer alone advances `tail`, so reading it needs no synchronisation.
63
// `head` belongs to the consumer; the acquire load makes the slots it has
64
// freed visible here.
65
const
size_t
t = atomic_load_explicit(&r->
tail
, memory_order_relaxed);
66
const
size_t
h = atomic_load_explicit(&r->
head
, memory_order_acquire);
67
68
// `t - h` is the live count, correct across the unsigned wrap of the
69
// counters. At `cap` the ring is full; refuse rather than overwrite a slot
70
// the consumer has not read yet.
71
if
(t - h >= r->
cap
) {
72
return
false
;
73
}
74
75
// Write the element, then publish it by advancing `tail` with release. A
76
// consumer that later acquires this `tail` value is guaranteed to see the
77
// bytes written just above, never a half-filled slot.
78
memcpy(r->
buf
+ ((t & r->
mask
) * r->
elem_size
), elem, r->
elem_size
);
79
atomic_store_explicit(&r->
tail
, t + 1, memory_order_release);
80
return
true
;
81
}
82
83
bool
srn_spsc_ring_pop
(
srn_spsc_ring_t
*r,
void
*out) {
84
// Mirror of push. The consumer alone advances `head` (relaxed), and reads the
85
// producer's `tail` with acquire so it sees every element published so far.
86
const
size_t
h = atomic_load_explicit(&r->
head
, memory_order_relaxed);
87
const
size_t
t = atomic_load_explicit(&r->
tail
, memory_order_acquire);
88
89
// Equal indices mean nothing new has been published since the last pop:
90
// empty.
91
if
(h == t) {
92
return
false
;
93
}
94
95
// Copy the element out, then free the slot by advancing `head` with release.
96
// The release orders this read before a producer's acquire of `head`, so the
97
// producer never reuses the slot while it is still being read here.
98
memcpy(out, r->
buf
+ ((h & r->
mask
) * r->
elem_size
), r->
elem_size
);
99
atomic_store_explicit(&r->
head
, h + 1, memory_order_release);
100
return
true
;
101
}
102
103
bool
srn_spsc_ring_empty
(
const
srn_spsc_ring_t
*r) {
104
// Exact for the consumer, the only party that moves `head`. For any other
105
// observer it is a hint, since the producer may publish right after the load.
106
return
atomic_load_explicit(&r->
head
, memory_order_acquire) ==
107
atomic_load_explicit(&r->
tail
, memory_order_acquire);
108
}
context.h
ALLOCN
#define ALLOCN(ctx, T, N)
Definition
context.h:85
ALLOC
#define ALLOC(ctx, T)
Definition
context.h:84
FAIL
#define FAIL(ctx, T, err, msg)
Definition
errors.h:149
WRONG_RANGE
@ WRONG_RANGE
Definition
errors.h:84
MAX_MAG
#define MAX_MAG
Definition
spsc_ring.c:25
srn_spsc_ring_push
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition
spsc_ring.c:61
MIN_MAG
#define MIN_MAG
Definition
spsc_ring.c:24
srn_spsc_ring_empty
bool srn_spsc_ring_empty(const srn_spsc_ring_t *r)
Whether the ring currently holds no elements.
Definition
spsc_ring.c:103
srn_spsc_ring_make
srn_spsc_ring_t * srn_spsc_ring_make(srn_context_t *ctx, size_t magnitude, size_t elem_size)
Set up a ring by allocating the buf to be the size of cap * elem_size.
Definition
spsc_ring.c:27
srn_spsc_ring_pop
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
Definition
spsc_ring.c:83
spsc_ring.h
A mutable single producer, single consumer ring of fixed size elements.
SRN_SPSC_RING_CAP_TYPE
#define SRN_SPSC_RING_CAP_TYPE
Definition
spsc_ring.h:41
srn_context_t
Definition
context.h:47
srn_spsc_ring_t
Definition
spsc_ring.h:45
srn_spsc_ring_t::elem_size
size_t elem_size
Definition
spsc_ring.h:56
srn_spsc_ring_t::cap
SRN_SPSC_RING_CAP_TYPE cap
Definition
spsc_ring.h:54
srn_spsc_ring_t::mask
SRN_SPSC_RING_CAP_TYPE mask
Definition
spsc_ring.h:55
srn_spsc_ring_t::tail
_Atomic size_t tail
Producer index. Only the producer advances it.
Definition
spsc_ring.h:52
srn_spsc_ring_t::head
_Atomic size_t head
Consumer index. Only the consumer advances it.
Definition
spsc_ring.h:49
srn_spsc_ring_t::buf
unsigned char * buf
cap * elem_size bytes.
Definition
spsc_ring.h:60
utils.h
PANIC_IF_NULL
#define PANIC_IF_NULL(ptr)
Definition
utils.h:66
PANIC_IF
#define PANIC_IF(cond, msg)
Definition
utils.h:59
rt
impl
spsc_ring.c
Serene Runtime is free software, licensed under the
GNU LGPL v3
License.