blob: 8202befb7e916d2e45117ba1f54530b050ceacd0 [file] [log] [blame]
/*
* Copyright (c) 2017 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authors: Rekai Gonzalez-Alberquilla
*/
#ifndef __BASE_CIRCULARQUEUE_HH__
#define __BASE_CIRCULARQUEUE_HH__
#include <vector>
/** Circular queue.
* Circular queue implemented on top of a standard vector. Instead of using
* a sentinel entry, we use a boolean to distinguish the case in which the
* queue is full or empty, because we are computer people, and we like
* using powers of 2 for sizes, and that way modules are much faster.
* Thus, a circular queue is represented by the 4-tuple
* (Capacity, IsEmpty?, Head, Tail, Round)
* Where:
* - Capacity is the size of the underlying vector.
* - IsEmpty? can be T or F :).
* - Head is the index in the vector of the first element of the queue.
* - Tail is the index in the vector of the last element of the queue.
* - Round is the counter of how many times the Tail has wrapped around.
* A queue is empty when
* Head == Tail + 1 mod Capacity && IsEmpty?.
* Conversely, a queue if full when
* Head == Tail + 1 mod Capacity && !IsEmpty?.
* Comments may show depictions of the underlying vector in the following
* format: '|' delimit the 'cells' of the underlying vector. '-' represents
* an element of the vector that is out-of-bounds of the circular queue,
* while 'o' represents and element that is inside the bounds. The
* characters '[' and ']' are added to mark the entries that hold the head
* and tail of the circular queue respectively.
* E.g.:
* - Empty queues of capacity 4:
* (4,T,1,0,_): |-]|[-|-|-| (4,T,3,2): |-|-|-]|[-|
* - Full queues of capacity 4:
* (4,F,1,0,_): |o]|[o|o|o| (4,F,3,2): |o|o|o]|[o|
* - Queues of capacity 4 with 2 elements:
* (4,F,0,1,_): |[o|o]|-|-| (4,F,3,0): |o]|-|-|[o|
*
* The Round number is only relevant for checking validity of indices,
* therefore it will be omitted or shown as '_'
*/
template <typename T>
class circularQueue : public std::vector<T>
{
protected:
using Base = std::vector<T>;
using typename Base::reference;
using typename Base::const_reference;
uint32_t _size;
uint32_t _head;
uint32_t _tail;
uint32_t _empty;
/** Counter for how many times the tail wraps around.
* Some parts of the code rely on getting the past the end iterator, and
* expect to use it after inserting on the tail. To support this without
* ambiguity, we need the round number to guarantee that it did not become
* a before-the-beginning iterator.
*/
int32_t _round;
/** Iterator to the circular queue.
* iterator implementation to provide the circular-ness that the
* standard std::vector<T>::iterator does not implement.
* Iterators to a queue are represented by a pair of a character and the
* round counter. For the character, '*' denotes the element pointed to by
* the iterator if it is valid. 'x' denotes the element pointed to by the
* iterator when it is BTB or PTE.
* E.g.:
* - Iterator to the head of a queue of capacity 4 with 2 elems.
* (4,F,0,1,R): |[(*,R)|o]|-|-| (4,F,3,0): |o]|-|-|[(*,R)|
* - Iterator to the tail of a queue of capacity 4 with 2 elems.
* (4,F,0,1,R): |[o|(*,R)]|-|-| (4,F,3,0): |(*,R)]|-|-|[o|
* - Iterator to the end of a queue of capacity 4 with 2 elems.
* (4,F,0,1,R): |[o|o]|(x,R)|-| (4,F,3,0): |o]|(x,R)|-|[o|
*/
public:
struct iterator {
circularQueue* _cq;
size_t _idx;
int32_t _round;
public:
iterator(circularQueue* cq, const uint32_t& idx, const int32_t& round)
: _cq(cq), _idx(idx), _round(round) {}
/** Iterator. */
/** @{ */
/** Traits */
/** @{ */
using value_type = T;
using difference_type = std::ptrdiff_t;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using iterator_category = std::random_access_iterator_tag;
/** @} */
/** CopyConstructible */
/** @{ */
iterator(const iterator& it)
: _cq(it._cq), _idx(it._idx), _round(it._round) {}
/** @} */
/** Copy Assignable. */
/** @{ */
iterator&
operator=(const iterator& it)
{
_cq = it._cq;
_idx = it._idx;
_round = it._round;
return *this;
}
/** @} */
/** Destructible.
* Clean-up after ourselves.
*/
/** @{ */
~iterator() { _cq = nullptr; _idx = 0; _round = 0; }
/** @} */
private:
public:
/** Test dereferenceability.
* An iterator is dereferenceable if it is pointing to a non-null
* circular queue, it is not the past-the-end iterator and the
* index is a valid index to that queue. PTE test is required to
* distinguish between:
* - An iterator to the first element of a full queue
* (4,F,1,0): |o]|[*|o|o|
* - The end() iterator of a full queue
* (4,F,1,0): |o]|*[o|o|o|
* Sometimes, though, users will get the PTE iterator and expect it
* to work after growing the buffer on the tail, so we have to
* check if the iterator is still PTE.
*/
bool
dereferenceable() const
{
return _cq != nullptr && _cq->isValidIdx(_idx, _round);
}
/** @} */
public:
/** InputIterator. */
/** @{ */
/** EqualityComparable. */
/** @{ */
/** Equality operator.
* Two iterators must point to the same, possibly null, circular
* queue and the same element on it, including PTE, to be equal.
* In case the clients the the PTE iterator and then grow on the back
* and expect it to work, we have to check if the PTE is still PTE
*/
bool operator==(const iterator& that) const {
return _cq == that._cq && _idx == that._idx &&
_round == that._round;
}
/** @} */
/** Inequality operator.
* Conversely, Two iterators are different if they both point to
* different circular queues or they point to different elements.
*/
bool operator!=(const iterator& that) {
return !(*this == that);
}
public:
/** Dereference operator. */
/** @{ */
reference operator*()
{
/* this has to be dereferenceable. */
assert (dereferenceable());
return (*_cq)[_idx];
}
const_reference operator*() const
{
/* this has to be dereferenceable. */
assert (dereferenceable());
return (*_cq)[_idx];
}
/** @} */
/** Dereference operator.
* Rely on operator* to check for dereferenceability.
*/
/** @{ */
pointer operator->()
{
assert (dereferenceable());
return &((*_cq)[_idx]);
}
const_pointer operator->() const
{
assert (dereferenceable());
return &((*_cq)[_idx]);
}
/** @} */
/** Pre-increment operator. */
iterator& operator++()
{
/* this has to be dereferenceable. */
assert (dereferenceable());
_cq->increase(_idx);
if (_idx == 0)
++_round;
return *this;
}
/** Post-increment operator. */
iterator
operator++(int)
{
iterator t = *this;
++*this;
return t;
}
/** @} */
/** ForwardIterator
* The multipass guarantee is provided by the reliance on _idx.
*/
/** @{ */
/** DefaultConstructible. */
/** @{ */
iterator() : _cq(nullptr), _idx(0) { }
/** @} */
/** Trait reference type
* iterator satisfies OutputIterator, therefore reference
* must be T& */
static_assert(std::is_same<reference, T&>::value,
"reference type is not assignable as required");
/** @} */
/** BidirectionalIterator requirements. */
/** @{ */
private:
/** Test decrementability.
* An iterator to a non-null circular queue is not-decrementable
* if it is pointing to the head element, unless the queue is full
* and we are talking about the past-the-end iterator. In that case,
* the iterator round equals the cq round unless the head is at the
* zero position and the round is one more than the cq round.
*/
bool
decrementable() const
{
return _cq && !(_idx == _cq->head() &&
(_cq->empty() ||
(_idx == 0 && _round != _cq->_round + 1) ||
(_idx !=0 && _round != _cq->_round)));
}
public:
/** Pre-decrement operator. */
iterator& operator--()
{
/* this has to be decrementable. */
assert(decrementable());
if (_idx == 0)
--_round;
_cq->decrease(_idx);
return *this;
}
/** Post-decrement operator. */
iterator operator--(int ) { iterator t = *this; --*this; return t; }
/** @} */
/** RandomAccessIterator requirements.*/
/** @{ */
iterator& operator+=(const difference_type& t)
{
assert(_cq);
_round += (t + _idx) / _cq->size();
_idx = _cq->add(_idx, t);
return *this;
}
iterator& operator-=(const difference_type& t)
{
assert(_cq);
/* C does not do euclidean division, so we have to adjust */
if (t >= 0)
_round += (-t + _idx) / _cq->size();
else
_round += (-t + _idx - _cq->size() + 1) / _cq->size();
_idx = _cq->sub(_idx, t);
return *this;
}
/** Addition operator. */
/** @{ */
iterator operator+(const difference_type& t)
{
iterator ret(*this);
return ret += t;
}
friend iterator operator+(const difference_type& t, iterator& it)
{
iterator ret = it;
return ret += t;
}
/** @} */
/** Substraction operator. */
/** @{ */
iterator operator-(const difference_type& t)
{
iterator ret(*this);
return ret -= t;
}
friend iterator operator-(const difference_type& t, iterator& it)
{
iterator ret = it;
return ret -= t;
}
/** @} */
/** Difference operator.
* that + ret == this
*/
difference_type operator-(const iterator& that) {
/* If a is already at the end, we can safely return 0. */
return _cq->sub(this->_idx, that._idx);
}
/** Index operator.
* The use of * tests for dereferenceability.
*/
template<typename Idx>
typename std::enable_if<std::is_integral<Idx>::value,reference>::type
operator[](const Idx& index) { return *(*this + index); }
/** Comparisons. */
/** @{ */
bool
operator<(const iterator& that) const
{
assert(_cq && that._cq == _cq);
return (this->_round < that._round) ||
(_round == that._round && _idx < that._idx);
}
bool operator>=(const iterator& that) const { return !(*this < that); }
bool operator<=(const iterator& that) const { return !(that < *this); }
/** @} */
/** @} */
/** OutputIterator has no extra requirements.*/
const size_t& idx() const { return _idx; }
};
/** Circular operations. */
/** @{ */
/** Modular addition for size a power of 2. */
static uint32_t
add(const uint32_t& op1, const uint32_t& op2, const uint32_t& size)
{
return (op1 + op2) & (size - 1);
}
/** General modular addition. */
static uint32_t
addM(const uint32_t& op1, const uint32_t& op2, const uint32_t& size)
{
return (op1 + op2) % size;
}
/** Modular substraction for size a power of 2. */
static uint32_t
sub(const uint32_t& op1, const uint32_t& op2, const uint32_t& size)
{
return (op1 - op2) & (size - 1);
}
/** General modular substraction. */
static uint32_t
subM(const uint32_t& op1, const uint32_t& op2, const uint32_t& size)
{
auto ret = (op1 - op2) % size;
return ret >= 0 ? ret : ret + size;
}
template <typename NumT>
static int
increase(NumT& v, const uint32_t& size)
{
static_assert(std::is_integral<NumT>::value,
"Instantiating increase for non-integral type");
if (++v == size) {
v = 0;
return 1;
}
return 0;
}
template <typename NumT>
static int
increase(NumT& v, size_t delta, const uint32_t& size)
{
static_assert(std::is_integral<NumT>::value,
"Instantiating increase for non-integral type");
int w = (v + delta) / size;
v = addM(v, delta, size);
return w;
}
template <typename NumT>
static void
decrease(NumT& v, const uint32_t& size)
{
static_assert(std::is_integral<NumT>::value,
"Instantiating decrease for non-integral type");
v = (v ? v : size) - 1;
}
/** @} */
public:
explicit circularQueue(uint32_t size)
: _size(size), _head(1), _tail(0), _empty(true), _round(0)
{
Base::resize(size);
}
/** Test if the index is in the range of valid elements. */
bool isValidIdx(const size_t& idx) const {
/* An index is invalid if:
* - The queue is empty.
* (6,T,3,2): |-|-|-]|[-|-|*|
* - head is small than tail and:
* - It is greater than both head and tail.
* (6,F,1,3): |-|[o|o|o]|-|*|
* - It is less than both head and tail.
* (6,F,1,3): |*|[o|o|o]|-|-|
* - It is greater than the tail and not than the head.
* (6,F,4,1): |o|o]|-|*|[o|o|
*/
return !(_empty || (
(_head < _tail) && (
(_head < idx && _tail < idx) ||
(_head > idx && _tail > idx)
)) || (_tail < idx && idx < _head));
}
/** Test if the index is in the range of valid elements.
* The round counter is used to disambiguate aliasing.
*/
bool isValidIdx(const size_t& idx, const int32_t& round) const {
/* An index is valid if:
* (6,T,3,2,R): |-|-|-]|[-|-|*|
* - The queue is not empty.
* - round == R and
* - index <= tail (if index > tail, that would be PTE)
* - Either:
* - head <= index
* (6,F,1,3,R): |-|[o|(*,r)|o]|-|-|
* - head > tail
* (6,F,5,3,R): |o|o|(*,r)|o]|-|[o|
* The remaining case means the the iterator is BTB:
* (6,F,3,4,R): |-|-|(*,r)|[o|o]|-|
* - round + 1 == R and:
* - index > tail. If index <= tail, that would be BTB:
* (6,F,2,3,r): | -|- |[(*,r)|o]|-|-| => ... =>
* (6,F,0,1,r+1): |[o|o]| (x,r)|- |-|-| => ... =>
* (6,F,0,3,r+1): |[o|o | (x,r)|o]|-|-|
* - index >= head. If index < head, that would be BTB:
* (6,F,5,2,R): |o|o]|-|-|(*,r)|[o|
* - head > tail. If head <= tail, that would be BTB:
* (6,F,3,4,R): |[o|o]|(*,r)|-|-|-|
* Other values of the round meand that the index is PTE or BTB
*/
return (!_empty && (
(round == _round && idx <= _tail && (
_head <= idx || _head > _tail)) ||
(round + 1 == _round &&
idx > _tail &&
idx >= _head &&
_head > _tail)
));
}
void clear();
reference front() { return (*this)[_head]; }
reference back() { return (*this)[_tail]; }
const uint32_t& size() const { return _size; }
const uint32_t& head() const { return _head; }
const uint32_t& tail() const { return _tail; }
template <typename NumT>
void increase (NumT& v) const { increase(v, _size); }
template <typename NumT>
void decrease (NumT& v) const { decrease(v, _size); }
uint32_t add(const uint32_t& s1, const uint32_t s2) const {
return _size & (_size - 1)
? addM(s1, s2, _size)
: add(s1, s2, _size);
}
uint32_t sub(const uint32_t& s1, const uint32_t s2) const {
return _size & (_size - 1)
? subM(s1, s2, _size)
: sub(s1, s2, _size);
}
/** Circularly increase the head pointer.
* Check that the queue is not empty. And set it to empty if it
* had only one value prior to insertion.
*/
void pop_front(size_t n = 1)
{
#if 0
int new_head = _head;
int wraps = increase(new_head, n, _size)
assert (!_empty);
_empty = _head == _tail;
increase(_head, _size);
#else
if (n == 0) return;
auto hIt = begin();
hIt += n;
assert(hIt <= end());
_empty = hIt == end();
_head = hIt._idx;
#endif
}
/** Circularly decrease the tail pointer. */
void pop_back()
{
assert (!_empty);
_empty = _head == _tail;
if (_tail == 0)
--_round;
decrease(_tail, _size);
}
/** Increase the size on the tail.
* Check for wrap-arounds to update the round counter.
* */
void
advance_tail(int n = 1)
{
#if 0
int new_tail = _tail;
int wraps = increase(new_tail, n, _size);
assert(wraps == 0 &&
(_tail >= _head && new_tail < head
_empty = !n && _empty;
#else
increase(_tail, _size);
if (_tail == 0)
++_round;
_empty = false;
#endif
}
/** Is the queue empty? */
bool empty() const { return _empty; }
/** Is the queue full?
* A queue is full if the head is the 0^{th} element and the tail is
* the (size-1)^{th} element, or if the head is the n^{th} element and
* the tail the (n-1)^{th} element.
*/
bool full() const {
return !_empty &&
(_tail + 1 == _head || (_tail + 1 == _size && _head == 0));
}
/** Iterators. */
/** @{ */
/*
* (4,F,2,0,r): | o]| -|[o|o | -> (1, r-1)
* (4,F,0,3,r): |[o | o|o |o]| -> (0, r)
* (4,T,0,3,r): |[- | -|- |-]| -> (0, r+1)
* (4,T,1,0,r): | -]|[-|- |- | -> (1, r)
*/
iterator begin()
{
if (_empty)
return end();
else if (_head > _tail)
return iterator(this, _head, _round - 1);
else
return iterator(this, _head, _round);
}
/* TODO: This should return a const_iterator. */
iterator begin() const
{
if (_empty)
return end();
else if (_head > _tail)
return iterator(const_cast<circularQueue*>(this), _head,
_round - 1);
else
return iterator(const_cast<circularQueue*>(this), _head,
_round - 1);
}
iterator end() {
auto poi = add(_tail, 1);
auto round = _round;
if (poi == 0)
++round;
return iterator(this, poi, round);
}
iterator end() const {
auto poi = add(_tail, 1);
auto round = _round;
if (poi == 0)
++round;
return iterator(const_cast<circularQueue*>(this), poi, round);
}
/** @} */
/** Return an iterator to an index in the vector.
* This poses the problem of round determination. By convention, the round
* is picked so that isValidIndex(idx, round) is true. If that is not
* possible, then the round value is _round, unless _tail is at the end of
* the storage, in which case is _round + 1
*/
iterator getIterator(size_t idx) {
assert(isValidIdx(idx) || add(_tail, 1) == idx);
if (_empty && _head == idx)
return end();
uint32_t round = _round;
if (idx > _tail) {
if (idx >= _head && _head > _tail && !_empty) {
round -= 1;
}
} else if (idx < _head && _tail + 1 == _size) {
round += 1;
}
return iterator(this, idx, round);
}
};
#endif /* __BASE_CIRCULARQUEUE_HH__ */