The Sparta Modeling Framework
Loading...
Searching...
No Matches
Queue.hpp
Go to the documentation of this file.
1// <Queue.hpp> -*- C++ -*-
2
3
10#pragma once
11
12#include <cinttypes>
13#include <vector>
14#include <type_traits>
15
17#include "sparta/ports/Port.hpp"
20#include "sparta/collection/IterableCollector.hpp"
23
24namespace sparta
25{
68 template <class DataT>
69 class Queue
70 {
71 public:
72
74 using value_type = DataT;
75
78
79 // Typedef for size_type
80 using size_type = uint32_t;
81
82 private:
83
84 //
85 // How the Queue works internally.
86 //
87 // The queue will create storage for the next pow2 elements
88 // for efficiency. So, if the user creates a Queue of 12
89 // elements, the internal storage will be 16.
90 //
91 // There are two pointers, each represents the _physical_
92 // location in the array, not the logical.
93 //
94 // current_write_idx_ -- the insertion point used by push()
95 // current_head_idx_ -- the top of the queue, used by pop()
96 //
97 // There is one dynamically created array for the data:
98 //
99 // queue_data_ -- equal to capacity ^ next pow2
100 //
101 // At init time, current_head_idx_ == current_write_idx_. As
102 // elements are push()ed, the new objects is added and
103 // current_write_idx_ is incremented. When current_write_idx_
104 // surpasses the end of the array, it will be wrapped to the
105 // beginning -- as long as there are invalidations. The
106 // "distance" between current_write_idx_ and current_head_idx_
107 // is always <= the capacity() of the queue. The distance
108 // between the indexes represents the size().
109 //
110 // Valid iterators are given an index into the queue, which is
111 // used to retrieve the data at the given location in the
112 // queue. As the iterator is incremented, it re-validates the
113 // index given, so it will always fall into one of two
114 // locations: the next valid data element, or end()
115 //
116 // Reading/accessing the Queue from a modeler's POV is a
117 // little different. The modeler can index into the queue
118 // starting from 0 -> size(). The Queue will need to
119 // "convert" this logical index to the physical one. For
120 // example, 0 might not be the data at index 0 of the array,
121 // but instead, index 6:
122 //
123 // L P
124 // 2 0 x
125 // 3 1 x
126 // 4 2 x
127 // 5 3 <- write idx
128 // 6 4
129 // 7 5
130 // 0 6 x <- head
131 // 1 7 x
132 //
133 // Another layout of queue during runtime:
134 //
135 // L P
136 // 7 0
137 // 0 1 x <- head
138 // 1 2 x
139 // 2 3 x
140 // 3 4 x
141 // 4 5 x
142 // 5 6 x
143 // 6 7 <- write idx
144
147 constexpr uint32_t nextPowerOfTwo_(uint32_t val) const
148 {
149 if(val < 2) { return 1ull; }
150 return 1ull << ((sizeof(uint64_t) * 8) - __builtin_clzll(val - 1ull));
151 }
152
154 uint32_t rollPhysicalIndex_(const uint32_t phys_idx) const {
155 return (vector_size_ - 1) & (phys_idx);
156 }
157
159 uint32_t getPhysicalIndex_(const uint32_t logical_idx) const {
160 return rollPhysicalIndex_(logical_idx + current_head_idx_);
161 }
162
166 uint32_t incrementIndexValue_(const uint32_t val) const {
167 return rollPhysicalIndex_(val + 1);
168 }
169
173 uint32_t decrementIndexValue_(const uint32_t val) const {
174 return rollPhysicalIndex_(val - 1);
175 }
176
178 struct QueueData {
179 template<class DataTRef>
180 QueueData(DataTRef && dat, uint64_t in_obj_id) :
181 data(std::forward<DataTRef>(dat)),
182 obj_id(in_obj_id)
183 {}
184
185 DataT data;
186 uint64_t obj_id = 0;
187 };
188
190 const value_type & readPhysical_(const uint32_t phys_idx) const
191 {
192 return queue_data_[phys_idx].data;
193 }
194
196 value_type & accessPhysical_(const uint32_t phys_idx)
197 {
198 return queue_data_[phys_idx].data;
199 }
200
202 uint32_t physicalToLogical_(const uint32_t physical_idx) const {
203 if(physical_idx == invalid_index_) { return invalid_index_; }
204 // Neat trick from the previous author...
205 return rollPhysicalIndex_(physical_idx - current_head_idx_);
206 }
207
209 bool isValidPhysical_(const uint32_t physical_idx) const
210 {
211 const auto log_idx = physicalToLogical_(physical_idx);
212 return (log_idx < size());
213 }
214
215 public:
216
226 template <bool is_const_iterator = true>
227 class QueueIterator : public utils::IteratorTraits<std::bidirectional_iterator_tag, value_type>
228 {
229 private:
230 using DataReferenceType = typename std::conditional<is_const_iterator,
231 const value_type &, value_type &>::type;
232 using QueuePointerType = typename std::conditional<is_const_iterator,
233 const QueueType * , QueueType * >::type;
234
240 QueueIterator(QueuePointerType q, uint32_t physical_index, uint32_t obj_id) :
241 attached_queue_(q),
242 physical_index_(physical_index),
243 obj_id_(obj_id)
244 { }
245
247 friend class Queue<DataT>;
248
250 bool isAttached_() const { return nullptr != attached_queue_; }
251
252 public:
253
255 QueueIterator() = default;
256
257 /* Make QueueIterator<true> a friend class of QueueIterator<false>
258 * So const iterator can access non-const iterators private members
259 */
260 friend class QueueIterator<true>;
261
266 attached_queue_(iter.attached_queue_),
267 physical_index_(iter.physical_index_),
268 obj_id_(iter.obj_id_)
269 {}
270
275 attached_queue_(iter.attached_queue_),
276 physical_index_(iter.physical_index_),
277 obj_id_(iter.obj_id_)
278 {}
279
283 bool isValid() const {
284 if(nullptr == attached_queue_) { return false; }
285 return attached_queue_->determineIteratorValidity_(this);
286 }
287
291 QueueIterator& operator=(const QueueIterator& rhs) = default;
292
294 bool operator<(const QueueIterator& rhs) const
295 {
296 sparta_assert(attached_queue_ == rhs.attached_queue_,
297 "Cannot compare QueueIterators created by different Queues");
298 return getIndex() < rhs.getIndex();
299 }
300
302 bool operator>(const QueueIterator& rhs) const
303 {
304 sparta_assert(attached_queue_ == rhs.attached_queue_,
305 "Cannot compare QueueIterators created by different Queues");
306 return getIndex() > rhs.getIndex();
307 }
308
310 bool operator==(const QueueIterator& rhs) const
311 {
312 sparta_assert(attached_queue_ == rhs.attached_queue_,
313 "Cannot compare QueueIterators created by different Queues");
314 return (physical_index_ == rhs.physical_index_) && (obj_id_ == rhs.obj_id_);
315 }
316
318 bool operator!=(const QueueIterator& rhs) const {
319 return !(*this == rhs);
320 }
321
324 {
325 sparta_assert(isAttached_(), "This is an invalid iterator");
326 attached_queue_->incrementIterator_(this);
327 return *this;
328 }
329
332 {
333 QueueIterator it(*this);
334 operator++();
335 return it;
336 }
337
340 {
341 sparta_assert(isAttached_(), "This is an invalid iterator");
342 attached_queue_->decrementIterator_(this);
343 return *this;
344 }
345
348 QueueIterator it(*this);
349 operator--();
350 return it;
351 }
352
354 DataReferenceType operator* ()
355 {
356 sparta_assert(isValid(), "This is an invalid iterator");
357 return getAccess_(std::integral_constant<bool, is_const_iterator>());
358 }
359
361 value_type* operator->()
362 {
363 sparta_assert(isValid(), "This is an invalid iterator");
364 return std::addressof(getAccess_(std::integral_constant<bool, is_const_iterator>()));
365 }
366
367 const value_type* operator->() const
368 {
369 sparta_assert(isValid(), "This is an invalid iterator");
370 return std::addressof(getAccess_(std::integral_constant<bool, is_const_iterator>()));
371 }
372
377 uint32_t getIndex() const
378 {
379 sparta_assert(isAttached_(), "This is an invalid iterator");
380 return attached_queue_->physicalToLogical_(physical_index_);
381 }
382
383 private:
384
385 QueuePointerType attached_queue_ {nullptr};
386 uint32_t physical_index_ = std::numeric_limits<uint32_t>::max();
387 uint64_t obj_id_ = 0;
388
390 DataReferenceType getAccess_(std::false_type) const {
391 return attached_queue_->accessPhysical_(physical_index_);
392 }
393
395 DataReferenceType getAccess_(std::true_type) const {
396 return attached_queue_->readPhysical_(physical_index_);
397 }
398 };
399
402
405
440 Queue(const std::string & name,
441 const uint32_t num_entries,
442 const Clock * clk,
443 StatisticSet * statset = nullptr,
448 num_entries_(num_entries),
449 vector_size_(nextPowerOfTwo_(num_entries*2)),
450 name_(name),
451 collector_(nullptr)
452 {
453
454 if((num_entries > 0) && statset)
455 {
456 utilization_.reset(new CycleHistogramStandalone(statset, clk,
457 name + "_utilization",
458 name + " occupancy histogram",
459 0, num_entries, 1, 0,
460 stat_vis_general,
461 stat_vis_detailed,
462 stat_vis_max,
463 stat_vis_avg));
464 }
465
466 // Make the queue twice as large and a power of two to
467 // allow a complete invalidation followed by a complete
468 // population
469 queue_data_.reset(static_cast<QueueData *>(malloc(sizeof(QueueData) * vector_size_)));
470 }
471
473 ~Queue() { clear(); }
474
476 Queue(const Queue<value_type> & ) = delete;
477
480
484 const std::string & getName() const {
485 return name_;
486 }
487
494 bool isValid(uint32_t idx) const {
495 return (idx < size());
496 }
497
503 const value_type & read(uint32_t idx) const {
504 sparta_assert(isValid(idx), "Cannot read an invalid index");
505 return readPhysical_(getPhysicalIndex_(idx));
506 }
507
515 value_type & access(uint32_t idx) {
516 sparta_assert(isValid(idx), name_ << ": Cannot read an invalid index");
517 return accessPhysical_(getPhysicalIndex_(idx));
518 }
519
524 value_type & front() const {
525 sparta_assert(size() != 0, name_ << ": Trying to get front() on an empty Queue");
526 return queue_data_[current_head_idx_].data;
527 }
528
533 value_type & back() const {
534 sparta_assert(size() != 0, name_ << ": Trying to get back() on an empty Queue");
535 uint32_t index = current_write_idx_;
536 index = decrementIndexValue_(index);
537 return queue_data_[index].data;
538 }
539
544 uint32_t capacity() const {
545 return num_entries_;
546 }
547
552 size_type size() const {
553 return total_valid_;
554 }
561 size_type numFree() const {
562 return (capacity() - total_valid_);
563 }
564
571 bool empty() const {
572 return (total_valid_ == 0);
573 }
574
580 void clear()
581 {
582 auto idx = current_head_idx_;
583 while (idx != current_write_idx_)
584 {
585 queue_data_[idx].data.~value_type();
586 idx = incrementIndexValue_(idx);
587 }
588 current_write_idx_ = 0;
589 current_head_idx_ = 0;
590 total_valid_ = 0;
591 updateUtilizationCounters_();
592 }
593
603 void enableCollection(TreeNode * parent) {
605 (parent, name_, this, capacity()));
606 }
607
616 {
617 return pushImpl_(dat);
618 }
619
628 {
629 return pushImpl_(std::move(dat));
630 }
631
636 void pop()
637 {
638 sparta_assert(total_valid_ != 0, name_ << ": Trying to pop an empty Queue");
639
640 // Destruct the element at the head
641 queue_data_[current_head_idx_].data.~value_type();
642
643 // Our head moves upward
644 current_head_idx_ = incrementIndexValue_(current_head_idx_);
645
646 // Clean up
647 processInvalidation_();
648 }
649
654 void pop_back()
655 {
656 sparta_assert(total_valid_ != 0, name_ << ": Trying to pop_back an empty Queue");
657
658 // our tail moves upward in the vector now.
659 current_write_idx_ = decrementIndexValue_(current_write_idx_);
660
661 // Destroy the object at the current write index
662 queue_data_[current_write_idx_].data.~value_type();
663
664 // Clean up
665 processInvalidation_();
666 }
667
672 return end();
673 }
674 else {
675 return iterator(this, current_head_idx_, queue_data_[current_head_idx_].obj_id);
676 }
677 }
678
681 iterator end() { return iterator(this, invalid_index_, invalid_index_);}
682
687 return end();
688 }
689 else {
690 return const_iterator(this, current_head_idx_, queue_data_[current_head_idx_].obj_id);
691 }
692 }
693
696 const_iterator end() const { return const_iterator(this, invalid_index_, invalid_index_);}
697
698 private:
699
700 template<typename U>
701 iterator pushImpl_ (U && dat)
702 {
703 sparta_assert(total_valid_ != capacity(), name_ << ": Queue is full");
704
705 // can't write more than the allowed items
706 sparta_assert(current_write_idx_ <= vector_size_);
707 new (queue_data_.get() + current_write_idx_) QueueData(std::forward<U>(dat), ++obj_id_);
708
709 // move the write index up.
710 const uint32_t write_idx = current_write_idx_;
711 current_write_idx_ = incrementIndexValue_(current_write_idx_);
712
713 // either process now or schedule for later at the correct precedence.
714 // update the valid count.
715 total_valid_ += 1;
716
717 updateUtilizationCounters_();
718
719 return iterator(this, write_idx, obj_id_);
720 }
721
722 template<typename IteratorType>
723 bool determineIteratorValidity_(IteratorType * itr) const
724 {
725 const auto physical_index = itr->physical_index_;
726
727 if(physical_index == invalid_index_) {
728 return false;
729 }
730
731 // Short cut... if we're empty, the iterator ain't valid
732 if(empty()) { return false; }
733
734 if(isValidPhysical_(physical_index)) {
735 return (queue_data_[physical_index].obj_id == itr->obj_id_);
736 }
737 return false;
738 }
739
740 template<typename IteratorType>
741 void decrementIterator_(IteratorType * itr) const
742 {
743 sparta_assert(itr->physical_index_ != 0, name_ <<
744 ": Iterator is not valid for decrementing");
745
746 uint32_t physical_index = itr->physical_index_;
747
748 // If it's the end iterator, go to the back - 1
749 if(SPARTA_EXPECT_FALSE(physical_index == invalid_index_)) {
750 const uint32_t phys_idx = decrementIndexValue_(current_write_idx_);
751 itr->physical_index_ = phys_idx;
752 itr->obj_id_ = queue_data_[phys_idx].obj_id;
753 }
754 else {
755 // See if decrementing this iterator puts into the weeds.
756 // If so, invalidate it.
757 physical_index = decrementIndexValue_(physical_index);
758 if(isValidPhysical_(physical_index)) {
759 itr->physical_index_ = physical_index;
760 itr->obj_id_ = queue_data_[physical_index].obj_id;
761 }
762 else {
763 itr->physical_index_ = invalid_index_;
764 itr->obj_id_ = invalid_index_;
765 }
766 }
767 }
768
769 template<typename IteratorType>
770 void incrementIterator_(IteratorType * itr) const
771 {
772 uint32_t physical_index = itr->physical_index_;
773
774 // See if incrementing this iterator puts it at the end.
775 // If so, put it there.
776 sparta_assert(physical_index != invalid_index_,
777 name_ << ": Trying to increment an invalid iterator");
778
779 physical_index = incrementIndexValue_(physical_index);
780
781 // See if the old logical index was valid. We could be
782 // incrementing to end()
783 if(isValidPhysical_(physical_index))
784 {
785 // Safe to increment the physical_index
786 itr->physical_index_ = physical_index;
787 itr->obj_id_ = queue_data_[physical_index].obj_id;
788 }
789 else {
790 // No longer valid index, but we could be rolling off
791 // to the end().
792 itr->physical_index_ = invalid_index_;
793 itr->obj_id_ = invalid_index_;
794 }
795 }
796
797 void updateUtilizationCounters_() {
798 // Update Counters
799 if(utilization_) {
800 utilization_->setValue(total_valid_);
801 }
802 }
803
805 void processInvalidation_()
806 {
807 sparta_assert(total_valid_ > 0);
808 total_valid_ -= 1;
809 updateUtilizationCounters_();
810 }
811
812 const size_type num_entries_;
813 size_type current_write_idx_ = 0;
814 size_type current_head_idx_ = 0;
815 size_type total_valid_ = 0;
816 const size_type vector_size_;
817 const size_type invalid_index_ {vector_size_ + 1};
818 const std::string name_;
821 uint64_t obj_id_ = 0;
822
824 // Counters
825 std::unique_ptr<sparta::CycleHistogramStandalone> utilization_;
826
828 // Collectors
829 std::unique_ptr<collection::IterableCollector<Queue<value_type> > > collector_;
830
831 // Notice that our list for storing data is a dynamic array.
832 // This is used instead of a stl vector to promote debug
833 // performance.
835 struct DeleteToFree_{
836 void operator()(void * x){
837 free(x);
838 }
839 };
840 std::unique_ptr<QueueData[], DeleteToFree_> queue_data_;
841 };
842
843}
CycleHistogram implementation using sparta CycleCounter.
Virtual interface node for simulator instrumentation (e.g. counters, stats, nontifications).
Defines a few handy (and now deprecated) C++ iterator traits.
File that defines the Port base class.
Set of macros for Sparta assertions. Caught by the framework.
#define sparta_assert(...)
Simple variadic assertion that will throw a sparta_exception if the condition fails.
#define SPARTA_EXPECT_FALSE(x)
A macro for hinting to the compiler a particular condition should be considered most likely false.
File that defines the StatisticSet class.
A representation of simulated time.
Definition Clock.hpp:51
CycleHistogramStandalone class for uint64_t values.
uint32_t visibility_t
Continuous visibility level. Several key points along continum are indicated within Visibility.
@ VIS_HIDDEN
Hidden hint. Lowest possible visibility.
static constexpr visibility_t AUTO_VISIBILITY
The default sparta resource visibility value that should be used. This is an alias of VIS_MAX at the ...
Class that alows queue elements to be accessed like a normal stl iterator. Queue iterator is a bidire...
Definition Queue.hpp:228
DataReferenceType operator*()
Dereferencing operator.
Definition Queue.hpp:354
QueueIterator & operator=(const QueueIterator &rhs)=default
Assignment operator.
QueueIterator(const QueueIterator< true > &iter)
Definition Queue.hpp:274
QueueIterator()=default
Default constructor.
value_type * operator->()
support -> operator
Definition Queue.hpp:361
bool operator<(const QueueIterator &rhs) const
overload the comparison operator.
Definition Queue.hpp:294
bool operator==(const QueueIterator &rhs) const
overload the comparison operator.
Definition Queue.hpp:310
QueueIterator operator++(int)
Post-Increment iterator.
Definition Queue.hpp:331
bool operator!=(const QueueIterator &rhs) const
overload the comparison operator.
Definition Queue.hpp:318
QueueIterator(const QueueIterator< false > &iter)
Definition Queue.hpp:265
bool operator>(const QueueIterator &rhs) const
overload the comparison operator.
Definition Queue.hpp:302
QueueIterator & operator--()
Pre-decrement iterator.
Definition Queue.hpp:339
QueueIterator & operator++()
Pre-Increment operator.
Definition Queue.hpp:323
uint32_t getIndex() const
Definition Queue.hpp:377
A data structure that allows appending at the back and invalidating from the front.
Definition Queue.hpp:70
DataT value_type
Typedef for the DataT.
Definition Queue.hpp:74
const_iterator begin() const
STL-like begin operation, starts at front (oldest element)
Definition Queue.hpp:685
value_type & back() const
Read and return the last pushed in element(newest element), reference.
Definition Queue.hpp:533
bool isValid(uint32_t idx) const
Determine if data at the index is valid.
Definition Queue.hpp:494
value_type & front() const
Read and return the data at the front(oldest element), reference.
Definition Queue.hpp:524
size_type numFree() const
Return the number of free entries.
Definition Queue.hpp:561
Queue(const Queue< value_type > &)=delete
No copies, no moves.
const_iterator end() const
STL - like end operation, starts at element one past head.
Definition Queue.hpp:696
QueueIterator< false > iterator
Typedef for regular iterator.
Definition Queue.hpp:401
size_type size() const
Return the number of valid entries.
Definition Queue.hpp:552
bool empty() const
Return if the queue is empty or not.
Definition Queue.hpp:571
iterator begin()
STL-like begin operation, starts at front (oldest element)
Definition Queue.hpp:670
uint32_t capacity() const
Return the fixed size of this queue.
Definition Queue.hpp:544
const value_type & read(uint32_t idx) const
Read and return the data at the given index, const reference.
Definition Queue.hpp:503
Queue & operator=(const Queue< value_type > &)=delete
Deleting default assignment operator to prevent copies.
QueueIterator< true > const_iterator
Typedef for constant iterator.
Definition Queue.hpp:404
const std::string & getName() const
Name of this resource.
Definition Queue.hpp:484
iterator end()
STL - like end operation, starts at element one past head.
Definition Queue.hpp:681
void pop_back()
Pops the data at the back of the structure (newest element) After pop iterator always points to the l...
Definition Queue.hpp:654
Queue(const std::string &name, const uint32_t num_entries, const Clock *clk, StatisticSet *statset=nullptr, InstrumentationNode::visibility_t stat_vis_general=InstrumentationNode::AUTO_VISIBILITY, InstrumentationNode::visibility_t stat_vis_detailed=InstrumentationNode::VIS_HIDDEN, InstrumentationNode::visibility_t stat_vis_max=InstrumentationNode::AUTO_VISIBILITY, InstrumentationNode::visibility_t stat_vis_avg=InstrumentationNode::AUTO_VISIBILITY)
Construct a queue.
Definition Queue.hpp:440
iterator push(const value_type &dat)
push data to the Queue.
Definition Queue.hpp:615
value_type & access(uint32_t idx)
Read and return the data at the given index, reference, non-const method.
Definition Queue.hpp:515
iterator push(value_type &&dat)
push data to the Queue.
Definition Queue.hpp:627
~Queue()
Destroy the Queue, clearing everything out.
Definition Queue.hpp:473
void enableCollection(TreeNode *parent)
Request that this queue begin collecting its contents for pipeline collection.
Definition Queue.hpp:603
void clear()
Empty the queue.
Definition Queue.hpp:580
void pop()
Pops the data at the front of the structure (oldest element) After pop iterator always points to the ...
Definition Queue.hpp:636
Set of StatisticDef and CounterBase-derived objects for visiblility through a sparta Tree.
Node in a composite tree representing a sparta Tree item.
Definition TreeNode.hpp:205
A collector of any iterable type (std::vector, std::list, sparta::Buffer, etc)
Macros for handling exponential backoff.