/* * 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 #include #include #include #include #include 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 class CircularQueue { protected: std::vector data; using reference = typename std::vector::reference; using const_reference = typename std::vector::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 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 std::enable_if_t, 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 std::enable_if_t, reference> operator[](const Idx& index) { assert(index >= 0); return data[index % _capacity]; } template typename std::enable_if_t, 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::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(this), _head); } /** * @ingroup api_base_utils */ iterator end() { return iterator(this, tail() + 1); } /** * @ingroup api_base_utils */ iterator end() const { return iterator(const_cast(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__ */