blob: 63d60a55f5f409544060f012b9ad346351f4a2a6 [file] [log] [blame] [edit]
/*
* Copyright (c) 2017-2018 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.
*/
#ifndef __BASE_CIRCULAR_QUEUE_HH__
#define __BASE_CIRCULAR_QUEUE_HH__
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <type_traits>
#include <vector>
namespace gem5
{
/** Circular queue.
* Circular queue implemented in a standard vector. All indices are
* monotonically increasing, and modulo is used at access time to alias them
* down to the actual storage.
*
* The queue keeps track of two pieces of state, a head index, which is the
* index of the next element to come out of the queue, and a size which is how
* many valid elements are currently in the queue. Size can increase to, but
* never exceed, the capacity of the queue.
*
* In theory the index may overflow at some point, but since it's a 64 bit
* value that would take a very long time.
*
* @tparam T Type of the elements in the queue
*
* @ingroup api_base_utils
*/
template <typename T>
class CircularQueue
{
protected:
std::vector<T> data;
using reference = typename std::vector<T>::reference;
using const_reference = typename std::vector<T>::const_reference;
const size_t _capacity;
size_t _size = 0;
size_t _head = 1;
/** Iterator to the circular queue.
* iterator implementation to provide wrap around indexing which the
* vector iterator does not.
*/
public:
struct iterator
{
public:
CircularQueue* _cq = nullptr;
size_t _idx = 0;
/**
* @ingroup api_base_utils
*/
iterator(CircularQueue* cq, size_t idx) : _cq(cq), _idx(idx) {}
iterator() = default;
/**
* Iterator Traits
*
* @ingroup api_base_utils
* @{
*/
using value_type = T;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator_category = std::random_access_iterator_tag;
/** @} */ // end of api_base_utils
/** Trait reference type
* iterator satisfies OutputIterator, therefore reference
* must be T& */
static_assert(std::is_same_v<reference, T&>,
"reference type is not assignable as required");
/**
* @ingroup api_base_utils
*/
iterator(const iterator& it) : _cq(it._cq), _idx(it._idx) {}
/**
* @ingroup api_base_utils
*/
iterator&
operator=(const iterator& it)
{
_cq = it._cq;
_idx = it._idx;
return *this;
}
/**
* Test dereferenceability.
* An iterator is dereferenceable if it is pointing to a non-null
* circular queue, and the index is within the current range of the
* queue.
*
* @ingroup api_base_utils
*/
bool
dereferenceable() const
{
return _cq != nullptr && _cq->isValidIdx(_idx);
}
/** InputIterator. */
/**
* Equality operator.
* Two iterators must point to the same, possibly null, circular
* queue and the same element on it to be equal.
*
* @ingroup api_base_utils
*/
bool
operator==(const iterator& that) const
{
return _cq == that._cq && _idx == that._idx;
}
/**
* Inequality operator.
* Conversely, two iterators are different if they both point to
* different circular queues or they point to different elements.
*
* @ingroup api_base_utils
*/
bool operator!=(const iterator& that) { return !(*this == that); }
/**
* Dereference operator.
*
* @ingroup api_base_utils
*/
reference
operator*()
{
// This has to be dereferenceable.
return (*_cq)[_idx];
}
/**
* @ingroup api_base_utils
*/
const_reference
operator*() const
{
// This has to be dereferenceable.
return (*_cq)[_idx];
}
/**
* Dereference operator.
* Rely on operator* to check for dereferenceability.
*
* @ingroup api_base_utils
*/
pointer operator->() { return &**this; }
/**
* @ingroup api_base_utils
*/
const_pointer operator->() const { return &**this; }
/**
* Pre-increment operator.
*
* @ingroup api_base_utils
*/
iterator&
operator++()
{
++_idx;
return *this;
}
/**
* Post-increment operator.
*
* @ingroup api_base_utils
*/
iterator
operator++(int)
{
iterator t = *this;
++*this;
return t;
}
/** ForwardIterator
* The multipass guarantee is provided by the reliance on _idx.
*/
/** BidirectionalIterator requirements. */
public:
/**
* Pre-decrement operator.
*
* @ingroup api_base_utils
*/
iterator&
operator--()
{
/* this has to be decrementable. */
assert(_cq && _idx > _cq->head());
--_idx;
return *this;
}
/**
* Post-decrement operator.
*
* @ingroup api_base_utils
*/
iterator
operator--(int)
{
iterator t = *this;
--*this;
return t;
}
/**
* RandomAccessIterator requirements.
*
* @ingroup api_base_utils
*/
iterator&
operator+=(const difference_type& t)
{
_idx += t;
return *this;
}
/**
* @ingroup api_base_utils
*/
iterator&
operator-=(const difference_type& t)
{
assert(_cq && _idx >= _cq->head() + t);
_idx -= t;
return *this;
}
/**
* Addition operator.
*
* @ingroup api_base_utils
*/
iterator
operator+(const difference_type& t)
{
iterator ret(*this);
return ret += t;
}
/**
* @ingroup api_base_utils
*/
friend iterator
operator+(const difference_type& t, iterator& it)
{
iterator ret = it;
return ret += t;
}
/**
* Substraction operator.
*
* @ingroup api_base_utils
*/
iterator
operator-(const difference_type& t)
{
iterator ret(*this);
return ret -= t;
}
/**
* @ingroup api_base_utils
*/
friend iterator
operator-(const difference_type& t, iterator& it)
{
iterator ret = it;
return ret -= t;
}
/**
* Difference operator.
* that + ret == this
*
* @ingroup api_base_utils
*/
difference_type
operator-(const iterator& that)
{
return (ssize_t)_idx - (ssize_t)that._idx;
}
/**
* Index operator.
* The use of * tests for dereferenceability.
*
* @ingroup api_base_utils
*/
template<typename Idx>
typename std::enable_if_t<std::is_integral_v<Idx>, reference>
operator[](const Idx& index)
{
return *(*this + index);
}
/**
* Comparisons.
*
* @ingroup api_base_utils
*/
bool
operator<(const iterator& that) const
{
return _idx < that._idx;
}
/**
* @ingroup api_base_utils
*/
bool operator>(const iterator& that) const { return !(*this <= that); }
/**
* @ingroup api_base_utils
*/
bool operator>=(const iterator& that) const { return !(*this < that); }
/**
* @ingroup api_base_utils
*/
bool operator<=(const iterator& that) const { return !(that < *this); }
/**
* OutputIterator has no extra requirements.
*/
size_t idx() const { return _idx; }
};
public:
/**
* @ingroup api_base_utils
*/
template <typename Idx>
typename std::enable_if_t<std::is_integral_v<Idx>, reference>
operator[](const Idx& index)
{
assert(index >= 0);
return data[index % _capacity];
}
template <typename Idx>
typename std::enable_if_t<std::is_integral_v<Idx>, const_reference>
operator[](const Idx& index) const
{
assert(index >= 0);
return data[index % _capacity];
}
/**
* @ingroup api_base_utils
*/
explicit CircularQueue(size_t size=0) : data(size), _capacity(size) {}
/**
* Remove all the elements in the queue.
*
* Note: This does not actually remove elements from the backing
* store.
*
* @ingroup api_base_utils
*/
void
flush()
{
_head = 1;
_size = 0;
}
/**
* Test if the index is in the range of valid elements.
*/
bool
isValidIdx(size_t idx) const
{
return _head <= idx && idx < (_head + _size);
}
/**
* @ingroup api_base_utils
*/
reference front() { return (*this)[head()]; }
/**
* @ingroup api_base_utils
*/
reference back() { return (*this)[tail()]; }
/**
* @ingroup api_base_utils
*/
size_t head() const { return _head; }
/**
* @ingroup api_base_utils
*/
size_t tail() const { return _head + _size - 1; }
/**
* @ingroup api_base_utils
*/
size_t capacity() const { return _capacity; }
/**
* @ingroup api_base_utils
*/
size_t size() const { return _size; }
/** Circularly increase the head pointer.
* By increasing the head pointer we are removing elements from
* the begin of the circular queue.
*
* @params num_elem number of elements to remove
*
* @ingroup api_base_utils
*/
void
pop_front(size_t num_elem=1)
{
assert(num_elem <= size());
_head += num_elem;
_size -= num_elem;
}
/**
* Circularly decrease the tail pointer.
*
* @ingroup api_base_utils
*/
void
pop_back()
{
assert(!empty());
--_size;
}
/**
* Pushes an element at the end of the queue.
*
* @ingroup api_base_utils
*/
void
push_back(typename std::vector<T>::value_type val)
{
advance_tail();
back() = val;
}
/**
* Increases the tail by one. This may overwrite the head if the queue is
* full.
*
* @ingroup api_base_utils
*/
void
advance_tail()
{
if (full())
++_head;
else
++_size;
}
/**
* Increases the tail by a specified number of steps. This may overwrite
* one or more entries starting at the head if the queue is full.
*
* @param len Number of steps
*
* @ingroup api_base_utils
*/
void
advance_tail(size_t len)
{
size_t remaining = _capacity - _size;
if (len > remaining) {
size_t overflow = len - remaining;
_head += overflow;
len -= overflow;
}
_size += len;
}
/**
* Is the queue empty?
*
* @ingroup api_base_utils
*/
bool empty() const { return _size == 0; }
/**
* 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.
*
* @ingroup api_base_utils
*/
bool full() const { return _size == _capacity; }
/**
* Iterators.
*
* @ingroup api_base_utils
*/
iterator begin() { return iterator(this, _head); }
/* TODO: This should return a const_iterator. */
/**
* @ingroup api_base_utils
*/
iterator
begin() const
{
return iterator(const_cast<CircularQueue*>(this), _head);
}
/**
* @ingroup api_base_utils
*/
iterator end() { return iterator(this, tail() + 1); }
/**
* @ingroup api_base_utils
*/
iterator
end() const
{
return iterator(const_cast<CircularQueue*>(this), tail() + 1);
}
/** Return an iterator to an index in the queue. */
iterator getIterator(size_t idx) { return iterator(this, idx); }
};
} // namespace gem5
#endif /* __BASE_CIRCULARQUEUE_HH__ */