| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| /** | |
| * @file Ring/circular buffer state machine. | |
| */ | |
| #pragma once | |
| #include <cdefs.h> | |
| #include <type_traits> | |
| namespace ds::ring_buffer | |
| { | |
| /** | |
| * A statically-sized, non-atomic ring buffer state machine. The actual | |
| * element storage must be provided externally; all this does is manage the | |
| * cursors. This representation uses an explicit empty flag, rather than | |
| * considering equal cursors to be empty, so as to not need an extra element | |
| * of store Capacity-many items. | |
| * | |
| * The type of the cursors is parametric, but must be unsigned. | |
| * | |
| * The consumer workflow is: | |
| * - use head_get() to test emptiness and, if nonempty, read the index | |
| * of the head element | |
| * - make use of the element in the (external!) storage at that index | |
| * - call head_advance() to discard the head element | |
| * | |
| * The producer workflow is: | |
| * - use tail_next() to retrieve the index for the next element, | |
| * if there is room | |
| * - populate the element in the (external!) storage at that index | |
| * - call tail_advance() to make that element available to the | |
| * consumer | |
| */ | |
| template<typename Debug, std::size_t Capacity, typename IxTy = std::size_t> | |
| class Cursors | |
| { | |
| static_assert(std::is_arithmetic_v<IxTy> && std::is_unsigned_v<IxTy>, | |
| "Ring buffer cursor type must be unsigned arithmetic"); | |
| template<typename... Args> | |
| using Assert = typename Debug::template Assert<Args...>; | |
| public: | |
| using Ix = IxTy; | |
| private: | |
| Ix head; | |
| Ix tail; | |
| bool empty; | |
| Ix advance(Ix v) | |
| { | |
| if constexpr ((Capacity & (Capacity - 1)) == 0) | |
| { | |
| /* For power of two sizes, we can rely on bit masking. */ | |
| return (v - 1) & (Capacity - 1); | |
| } | |
| else | |
| { | |
| /* | |
| * For non-power-of-two, we can use unsigned underflow and | |
| * signed arithmetic right shift to avoid branching. mask is | |
| * all zeros if next is non-negative and all ones otherwise. | |
| */ | |
| Ix next = v - 1; | |
| using SIx = std::make_signed_t<Ix>; | |
| Ix mask = static_cast<SIx>(next) >> (8 * sizeof(SIx) - 1); | |
| return (mask & (Capacity - 1)) | (~mask & next); | |
| } | |
| } | |
| public: | |
| /** | |
| * Reset cursors to an empty state. | |
| */ | |
| void reset() | |
| { | |
| tail = 0; | |
| head = advance(tail); | |
| empty = true; | |
| } | |
| /** | |
| * Is this ring empty? | |
| */ | |
| __always_inline bool is_empty() | |
| { | |
| return empty; | |
| } | |
| /** | |
| * Try to retrieve the head index; returns true if available and | |
| * false otherwise. | |
| */ | |
| bool head_get(Ix &v) | |
| { | |
| if (is_empty()) | |
| { | |
| return false; | |
| } | |
| v = head; | |
| return true; | |
| } | |
| /** | |
| * Retrieve the head index while bypassing the emptiness check. The | |
| * caller must have reason to believe that the ring is not empty. | |
| */ | |
| Ix head_get_unsafe() | |
| { | |
| return head; | |
| } | |
| /** | |
| * Discard the element at the index returned by head_get(). | |
| * | |
| * Do not call this unless there was a matching call to | |
| * head_get() that has returned true. | |
| */ | |
| __always_inline void head_advance() | |
| { | |
| Assert<>(!empty, "Cannot advance head of empty ring buffer!"); | |
| if (head == tail) | |
| { | |
| empty = true; | |
| } | |
| head = advance(head); | |
| } | |
| /** | |
| * Try to retrieve the index of the tail, if nonempty. | |
| */ | |
| bool tail_get(Ix &v) | |
| { | |
| if (is_empty()) | |
| { | |
| return false; | |
| } | |
| v = tail; | |
| return true; | |
| } | |
| /** | |
| * Try to retrieve the index beyond the tail, if there is room. | |
| * Returns true and sets the index if so, or returns false if | |
| * not. | |
| */ | |
| bool tail_next(Ix &v) | |
| { | |
| Ix nt = advance(tail); | |
| if (!empty && head == nt) | |
| { | |
| return false; | |
| } | |
| v = nt; | |
| return true; | |
| } | |
| /** | |
| * Make the next element available. | |
| * | |
| * Do not call this unless there was a matching call to | |
| * tail_next() that returned true. | |
| */ | |
| __always_inline void tail_advance() | |
| { | |
| Assert<>(empty || head != advance(tail), | |
| "Cannot advance tail of full ring buffer!"); | |
| tail = advance(tail); | |
| empty = false; | |
| } | |
| }; | |
| } // namespace ds::ring_buffer |