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