1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef INCLUDE_PERFETTO_EXT_BASE_CIRCULAR_QUEUE_H_ 18 #define INCLUDE_PERFETTO_EXT_BASE_CIRCULAR_QUEUE_H_ 19 20 #include <stdint.h> 21 #include <stdlib.h> 22 23 #include <iterator> 24 25 #include "perfetto/base/logging.h" 26 #include "perfetto/ext/base/utils.h" 27 28 namespace perfetto { 29 namespace base { 30 31 // CircularQueue is a push-back-only / pop-front-only queue with the following 32 // characteristics: 33 // - The storage is based on a flat circular buffer. Beginning and end wrap 34 // as necessary, to keep pushes and pops O(1) as long as capacity expansion is 35 // not required. 36 // - Capacity is automatically expanded like in a std::vector. Expansion has a 37 // O(N) cost. 38 // - It allows random access, allowing in-place std::sort. 39 // - Iterators are not stable. Mutating the container invalidates all iterators. 40 // - It doesn't bother with const-correctness. 41 // 42 // Implementation details: 43 // Internally, |begin|, |end| and iterators use 64-bit monotonic indexes, which 44 // are incremented as if the queue was backed by unlimited storage. 45 // Even assuming that elements are inserted and removed every nanosecond, 64 bit 46 // is enough for 584 years. 47 // Wrapping happens only when addressing elements in the underlying circular 48 // storage. This limits the complexity and avoiding dealing with modular 49 // arithmetic all over the places. 50 template <class T> 51 class CircularQueue { 52 public: 53 class Iterator { 54 public: 55 using difference_type = ptrdiff_t; 56 using value_type = T; 57 using pointer = T*; 58 using reference = T&; 59 using iterator_category = std::random_access_iterator_tag; 60 Iterator(CircularQueue * queue,uint64_t pos,uint32_t generation)61 Iterator(CircularQueue* queue, uint64_t pos, uint32_t generation) 62 : queue_(queue), 63 pos_(pos) 64 #if PERFETTO_DCHECK_IS_ON() 65 , 66 generation_(generation) 67 #endif 68 { 69 ignore_result(generation); 70 } 71 72 Iterator(const Iterator&) noexcept = default; 73 Iterator& operator=(const Iterator&) noexcept = default; 74 Iterator(Iterator&&) noexcept = default; 75 Iterator& operator=(Iterator&&) noexcept = default; 76 77 T* operator->() const { 78 #if PERFETTO_DCHECK_IS_ON() 79 PERFETTO_DCHECK(generation_ == queue_->generation()); 80 #endif 81 return queue_->Get(pos_); 82 } 83 84 T& operator*() const { return *(operator->()); } 85 86 value_type& operator[](difference_type i) { return *(*this + i); } 87 88 Iterator& operator++() { 89 Add(1); 90 return *this; 91 } 92 93 Iterator operator++(int) { 94 Iterator ret = *this; 95 Add(1); 96 return ret; 97 } 98 99 Iterator& operator--() { 100 Add(-1); 101 return *this; 102 } 103 104 Iterator operator--(int) { 105 Iterator ret = *this; 106 Add(-1); 107 return ret; 108 } 109 110 friend Iterator operator+(const Iterator& iter, difference_type offset) { 111 Iterator ret = iter; 112 ret.Add(offset); 113 return ret; 114 } 115 116 Iterator& operator+=(difference_type offset) { 117 Add(offset); 118 return *this; 119 } 120 121 friend Iterator operator-(const Iterator& iter, difference_type offset) { 122 Iterator ret = iter; 123 ret.Add(-offset); 124 return ret; 125 } 126 127 Iterator& operator-=(difference_type offset) { 128 Add(-offset); 129 return *this; 130 } 131 132 friend ptrdiff_t operator-(const Iterator& lhs, const Iterator& rhs) { 133 return static_cast<ptrdiff_t>(lhs.pos_) - 134 static_cast<ptrdiff_t>(rhs.pos_); 135 } 136 137 friend bool operator==(const Iterator& lhs, const Iterator& rhs) { 138 return lhs.pos_ == rhs.pos_; 139 } 140 141 friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { 142 return lhs.pos_ != rhs.pos_; 143 } 144 145 friend bool operator<(const Iterator& lhs, const Iterator& rhs) { 146 return lhs.pos_ < rhs.pos_; 147 } 148 149 friend bool operator<=(const Iterator& lhs, const Iterator& rhs) { 150 return lhs.pos_ <= rhs.pos_; 151 } 152 153 friend bool operator>(const Iterator& lhs, const Iterator& rhs) { 154 return lhs.pos_ > rhs.pos_; 155 } 156 157 friend bool operator>=(const Iterator& lhs, const Iterator& rhs) { 158 return lhs.pos_ >= rhs.pos_; 159 } 160 161 private: Add(difference_type offset)162 inline void Add(difference_type offset) { 163 pos_ = static_cast<uint64_t>(static_cast<difference_type>(pos_) + offset); 164 PERFETTO_DCHECK(pos_ <= queue_->end_); 165 } 166 167 CircularQueue* queue_; 168 uint64_t pos_; 169 170 #if PERFETTO_DCHECK_IS_ON() 171 uint32_t generation_; 172 #endif 173 }; 174 175 explicit CircularQueue(size_t initial_capacity = 1024) { 176 Grow(initial_capacity); 177 } 178 CircularQueue(CircularQueue && other)179 CircularQueue(CircularQueue&& other) noexcept 180 : entries_(std::move(other.entries_)), 181 capacity_(other.capacity_), 182 begin_(other.begin_), 183 end_(other.end_) { 184 increment_generation(); 185 new (&other) CircularQueue(); // Reset the old queue so it's still usable. 186 } 187 188 CircularQueue& operator=(CircularQueue&& other) noexcept { 189 this->~CircularQueue(); // Destroy the current state. 190 new (this) CircularQueue(std::move(other)); // Use the move ctor above. 191 return *this; 192 } 193 ~CircularQueue()194 ~CircularQueue() { 195 if (!entries_) { 196 PERFETTO_DCHECK(empty()); 197 return; 198 } 199 clear(); // Invoke destructors on all alive entries. 200 PERFETTO_DCHECK(empty()); 201 } 202 203 template <typename... Args> emplace_back(Args &&...args)204 void emplace_back(Args&&... args) { 205 increment_generation(); 206 if (PERFETTO_UNLIKELY(size() >= capacity_)) 207 Grow(); 208 T* slot = Get(end_++); 209 new (slot) T(std::forward<Args>(args)...); 210 } 211 erase_front(size_t n)212 void erase_front(size_t n) { 213 increment_generation(); 214 for (; n && (begin_ < end_); --n) { 215 Get(begin_)->~T(); 216 begin_++; // This needs to be its own statement, Get() checks begin_. 217 } 218 } 219 pop_front()220 void pop_front() { erase_front(1); } 221 clear()222 void clear() { erase_front(size()); } 223 at(size_t idx)224 T& at(size_t idx) { 225 PERFETTO_DCHECK(idx < size()); 226 return *Get(begin_ + idx); 227 } 228 begin()229 Iterator begin() { return Iterator(this, begin_, generation()); } end()230 Iterator end() { return Iterator(this, end_, generation()); } front()231 T& front() { return *begin(); } back()232 T& back() { return *(end() - 1); } 233 empty()234 bool empty() const { return size() == 0; } 235 size()236 size_t size() const { 237 PERFETTO_DCHECK(end_ - begin_ <= capacity_); 238 return static_cast<size_t>(end_ - begin_); 239 } 240 capacity()241 size_t capacity() const { return capacity_; } 242 243 #if PERFETTO_DCHECK_IS_ON() generation()244 uint32_t generation() const { return generation_; } increment_generation()245 void increment_generation() { ++generation_; } 246 #else generation()247 uint32_t generation() const { return 0; } increment_generation()248 void increment_generation() {} 249 #endif 250 251 private: 252 CircularQueue(const CircularQueue&) = delete; 253 CircularQueue& operator=(const CircularQueue&) = delete; 254 255 void Grow(size_t new_capacity = 0) { 256 // Capacity must be always a power of two. This allows Get() to use a simple 257 // bitwise-AND for handling the wrapping instead of a full division. 258 new_capacity = new_capacity ? new_capacity : capacity_ * 2; 259 PERFETTO_CHECK((new_capacity & (new_capacity - 1)) == 0); // Must be pow2. 260 261 // On 32-bit systems this might hit the 4GB wall and overflow. We can't do 262 // anything other than crash in this case. 263 PERFETTO_CHECK(new_capacity > capacity_); 264 265 AlignedUniquePtr<T[]> new_vec = AlignedAllocTyped<T[]>(new_capacity); 266 267 // Move all elements in the expanded array. 268 size_t new_size = 0; 269 for (uint64_t i = begin_; i < end_; i++) 270 new (&new_vec[new_size++]) T(std::move(*Get(i))); // Placement move ctor. 271 272 // Even if all the elements are std::move()-d and likely empty, we are still 273 // required to call the dtor for them. 274 for (uint64_t i = begin_; i < end_; i++) 275 Get(i)->~T(); 276 277 begin_ = 0; 278 end_ = new_size; 279 capacity_ = new_capacity; 280 entries_ = std::move(new_vec); 281 } 282 Get(uint64_t pos)283 inline T* Get(uint64_t pos) { 284 PERFETTO_DCHECK(pos >= begin_ && pos < end_); 285 PERFETTO_DCHECK((capacity_ & (capacity_ - 1)) == 0); // Must be a pow2. 286 auto index = static_cast<size_t>(pos & (capacity_ - 1)); 287 return &entries_[index]; 288 } 289 290 // Underlying storage. It's raw malloc-ed rather than being a unique_ptr<T[]> 291 // to allow having uninitialized entries inside it. 292 AlignedUniquePtr<T[]> entries_; 293 size_t capacity_ = 0; // Number of allocated slots (NOT bytes) in |entries_|. 294 295 // The |begin_| and |end_| indexes are monotonic and never wrap. Modular arith 296 // is used only when dereferencing entries in the vector. 297 uint64_t begin_ = 0; 298 uint64_t end_ = 0; 299 300 // Generation is used in debug builds only for checking iterator validity. 301 #if PERFETTO_DCHECK_IS_ON() 302 uint32_t generation_ = 0; 303 #endif 304 }; 305 306 } // namespace base 307 } // namespace perfetto 308 309 #endif // INCLUDE_PERFETTO_EXT_BASE_CIRCULAR_QUEUE_H_ 310