CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
ring_buffer.h
Go to the documentation of this file.
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4/**
5 * @file
6 * Ring/circular buffer state machine.
7 */
8
9#pragma once
10
11#include <cdefs.h>
12#include <type_traits>
13
14namespace ds::ring_buffer
15{
16 /**
17 * A statically-sized, non-atomic ring buffer state machine. The actual
18 * element storage must be provided externally; all this does is manage the
19 * cursors. This representation uses an explicit empty flag, rather than
20 * considering equal cursors to be empty, so as to not need an extra element
21 * of store Capacity-many items.
22 *
23 * The type of the cursors is parametric, but must be unsigned.
24 *
25 * The consumer workflow is:
26 * - use head_get() to test emptiness and, if nonempty, read the index
27 * of the head element
28 * - make use of the element in the (external!) storage at that index
29 * - call head_advance() to discard the head element
30 *
31 * The producer workflow is:
32 * - use tail_next() to retrieve the index for the next element,
33 * if there is room
34 * - populate the element in the (external!) storage at that index
35 * - call tail_advance() to make that element available to the
36 * consumer
37 */
38 template<typename Debug, std::size_t Capacity, typename IxTy = std::size_t>
39 class Cursors
40 {
41 static_assert(std::is_arithmetic_v<IxTy> && std::is_unsigned_v<IxTy>,
42 "Ring buffer cursor type must be unsigned arithmetic");
43
44 template<typename... Args>
45 using Assert = typename Debug::template Assert<Args...>;
46
47 public:
48 using Ix = IxTy;
49
50 private:
51 Ix head;
52 Ix tail;
53 bool empty;
54
55 Ix advance(Ix v)
56 {
57 if constexpr ((Capacity & (Capacity - 1)) == 0)
58 {
59 /* For power of two sizes, we can rely on bit masking. */
60 return (v - 1) & (Capacity - 1);
61 }
62 else
63 {
64 /*
65 * For non-power-of-two, we can use unsigned underflow and
66 * signed arithmetic right shift to avoid branching. mask is
67 * all zeros if next is non-negative and all ones otherwise.
68 */
69 Ix next = v - 1;
70 using SIx = std::make_signed_t<Ix>;
71 Ix mask = static_cast<SIx>(next) >> (8 * sizeof(SIx) - 1);
72 return (mask & (Capacity - 1)) | (~mask & next);
73 }
74 }
75
76 public:
77 /**
78 * Reset cursors to an empty state.
79 */
80 void reset()
81 {
82 tail = 0;
83 head = advance(tail);
84 empty = true;
85 }
86
87 /**
88 * Is this ring empty?
89 */
90 __always_inline bool is_empty()
91 {
92 return empty;
93 }
94
95 /**
96 * Try to retrieve the head index; returns true if available and
97 * false otherwise.
98 */
99 bool head_get(Ix &v)
100 {
101 if (is_empty())
102 {
103 return false;
104 }
105
106 v = head;
107 return true;
108 }
109
110 /**
111 * Retrieve the head index while bypassing the emptiness check. The
112 * caller must have reason to believe that the ring is not empty.
113 */
115 {
116 return head;
117 }
118
119 /**
120 * Discard the element at the index returned by head_get().
121 *
122 * Do not call this unless there was a matching call to
123 * head_get() that has returned true.
124 */
125 __always_inline void head_advance()
126 {
127 Assert<>(!empty, "Cannot advance head of empty ring buffer!");
128 if (head == tail)
129 {
130 empty = true;
131 }
132 head = advance(head);
133 }
134
135 /**
136 * Try to retrieve the index of the tail, if nonempty.
137 */
138 bool tail_get(Ix &v)
139 {
140 if (is_empty())
141 {
142 return false;
143 }
144
145 v = tail;
146 return true;
147 }
148
149 /**
150 * Try to retrieve the index beyond the tail, if there is room.
151 * Returns true and sets the index if so, or returns false if
152 * not.
153 */
154
155 bool tail_next(Ix &v)
156 {
157 Ix nt = advance(tail);
158 if (!empty && head == nt)
159 {
160 return false;
161 }
162
163 v = nt;
164 return true;
165 }
166
167 /**
168 * Make the next element available.
169 *
170 * Do not call this unless there was a matching call to
171 * tail_next() that returned true.
172 */
173 __always_inline void tail_advance()
174 {
175 Assert<>(empty || head != advance(tail),
176 "Cannot advance tail of full ring buffer!");
177 tail = advance(tail);
178 empty = false;
179 }
180 };
181} // namespace ds::ring_buffer
A statically-sized, non-atomic ring buffer state machine.
Definition ring_buffer.h:40
void reset()
Reset cursors to an empty state.
Definition ring_buffer.h:80
void head_advance()
Discard the element at the index returned by head_get().
bool is_empty()
Is this ring empty?
Definition ring_buffer.h:90
bool head_get(Ix &v)
Try to retrieve the head index; returns true if available and false otherwise.
Definition ring_buffer.h:99
bool tail_get(Ix &v)
Try to retrieve the index of the tail, if nonempty.
bool tail_next(Ix &v)
Try to retrieve the index beyond the tail, if there is room.
void tail_advance()
Make the next element available.
Ix head_get_unsafe()
Retrieve the head index while bypassing the emptiness check.