xref: /aosp_15_r20/external/libgav1/src/utils/unbounded_queue.h (revision 095378508e87ed692bf8dfeb34008b65b3735891)
1*09537850SAkhilesh Sanikop /*
2*09537850SAkhilesh Sanikop  * Copyright 2019 The libgav1 Authors
3*09537850SAkhilesh Sanikop  *
4*09537850SAkhilesh Sanikop  * Licensed under the Apache License, Version 2.0 (the "License");
5*09537850SAkhilesh Sanikop  * you may not use this file except in compliance with the License.
6*09537850SAkhilesh Sanikop  * You may obtain a copy of the License at
7*09537850SAkhilesh Sanikop  *
8*09537850SAkhilesh Sanikop  *      http://www.apache.org/licenses/LICENSE-2.0
9*09537850SAkhilesh Sanikop  *
10*09537850SAkhilesh Sanikop  * Unless required by applicable law or agreed to in writing, software
11*09537850SAkhilesh Sanikop  * distributed under the License is distributed on an "AS IS" BASIS,
12*09537850SAkhilesh Sanikop  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*09537850SAkhilesh Sanikop  * See the License for the specific language governing permissions and
14*09537850SAkhilesh Sanikop  * limitations under the License.
15*09537850SAkhilesh Sanikop  */
16*09537850SAkhilesh Sanikop 
17*09537850SAkhilesh Sanikop #ifndef LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_
18*09537850SAkhilesh Sanikop #define LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_
19*09537850SAkhilesh Sanikop 
20*09537850SAkhilesh Sanikop #include <cassert>
21*09537850SAkhilesh Sanikop #include <cstddef>
22*09537850SAkhilesh Sanikop #include <memory>
23*09537850SAkhilesh Sanikop #include <new>
24*09537850SAkhilesh Sanikop #include <utility>
25*09537850SAkhilesh Sanikop 
26*09537850SAkhilesh Sanikop #include "src/utils/compiler_attributes.h"
27*09537850SAkhilesh Sanikop #include "src/utils/memory.h"
28*09537850SAkhilesh Sanikop 
29*09537850SAkhilesh Sanikop namespace libgav1 {
30*09537850SAkhilesh Sanikop 
31*09537850SAkhilesh Sanikop // A FIFO queue of an unbounded capacity.
32*09537850SAkhilesh Sanikop //
33*09537850SAkhilesh Sanikop // This implementation uses the general approach used in std::deque
34*09537850SAkhilesh Sanikop // implementations. See, for example,
35*09537850SAkhilesh Sanikop // https://stackoverflow.com/questions/6292332/what-really-is-a-deque-in-stl
36*09537850SAkhilesh Sanikop //
37*09537850SAkhilesh Sanikop // It is much simpler because it just needs to support the queue interface.
38*09537850SAkhilesh Sanikop // The blocks are chained into a circular list, not managed by a "map". It
39*09537850SAkhilesh Sanikop // does not shrink the internal buffer.
40*09537850SAkhilesh Sanikop //
41*09537850SAkhilesh Sanikop // An alternative implementation approach is a resizable circular array. See,
42*09537850SAkhilesh Sanikop // for example, ResizingArrayQueue.java in https://algs4.cs.princeton.edu/code/
43*09537850SAkhilesh Sanikop // and base::circular_deque in Chromium's base/containers library.
44*09537850SAkhilesh Sanikop template <typename T>
45*09537850SAkhilesh Sanikop class UnboundedQueue {
46*09537850SAkhilesh Sanikop  public:
47*09537850SAkhilesh Sanikop   UnboundedQueue() = default;
48*09537850SAkhilesh Sanikop 
49*09537850SAkhilesh Sanikop   // Move only.
UnboundedQueue(UnboundedQueue && other)50*09537850SAkhilesh Sanikop   UnboundedQueue(UnboundedQueue&& other)
51*09537850SAkhilesh Sanikop       : first_block_(other.first_block_),
52*09537850SAkhilesh Sanikop         front_(other.front_),
53*09537850SAkhilesh Sanikop         last_block_(other.last_block_),
54*09537850SAkhilesh Sanikop         back_(other.back_) {
55*09537850SAkhilesh Sanikop     other.first_block_ = nullptr;
56*09537850SAkhilesh Sanikop     other.front_ = 0;
57*09537850SAkhilesh Sanikop     other.last_block_ = nullptr;
58*09537850SAkhilesh Sanikop     other.back_ = 0;
59*09537850SAkhilesh Sanikop   }
60*09537850SAkhilesh Sanikop   UnboundedQueue& operator=(UnboundedQueue&& other) {
61*09537850SAkhilesh Sanikop     if (this != &other) {
62*09537850SAkhilesh Sanikop       Destroy();
63*09537850SAkhilesh Sanikop       first_block_ = other.first_block_;
64*09537850SAkhilesh Sanikop       front_ = other.front_;
65*09537850SAkhilesh Sanikop       last_block_ = other.last_block_;
66*09537850SAkhilesh Sanikop       back_ = other.back_;
67*09537850SAkhilesh Sanikop       other.first_block_ = nullptr;
68*09537850SAkhilesh Sanikop       other.front_ = 0;
69*09537850SAkhilesh Sanikop       other.last_block_ = nullptr;
70*09537850SAkhilesh Sanikop       other.back_ = 0;
71*09537850SAkhilesh Sanikop     }
72*09537850SAkhilesh Sanikop     return *this;
73*09537850SAkhilesh Sanikop   }
74*09537850SAkhilesh Sanikop 
~UnboundedQueue()75*09537850SAkhilesh Sanikop   ~UnboundedQueue() { Destroy(); }
76*09537850SAkhilesh Sanikop 
77*09537850SAkhilesh Sanikop   // Allocates two Blocks upfront because most access patterns require at
78*09537850SAkhilesh Sanikop   // least two Blocks. Returns false if the allocation of the Blocks failed.
Init()79*09537850SAkhilesh Sanikop   LIBGAV1_MUST_USE_RESULT bool Init() {
80*09537850SAkhilesh Sanikop     std::unique_ptr<Block> new_block0(new (std::nothrow) Block);
81*09537850SAkhilesh Sanikop     std::unique_ptr<Block> new_block1(new (std::nothrow) Block);
82*09537850SAkhilesh Sanikop     if (new_block0 == nullptr || new_block1 == nullptr) return false;
83*09537850SAkhilesh Sanikop     first_block_ = last_block_ = new_block0.release();
84*09537850SAkhilesh Sanikop     new_block1->next = first_block_;
85*09537850SAkhilesh Sanikop     last_block_->next = new_block1.release();
86*09537850SAkhilesh Sanikop     return true;
87*09537850SAkhilesh Sanikop   }
88*09537850SAkhilesh Sanikop 
89*09537850SAkhilesh Sanikop   // Checks if the queue has room for a new element. If the queue is full,
90*09537850SAkhilesh Sanikop   // tries to grow it. Returns false if the queue is full and the attempt to
91*09537850SAkhilesh Sanikop   // grow it failed.
92*09537850SAkhilesh Sanikop   //
93*09537850SAkhilesh Sanikop   // NOTE: GrowIfNeeded() must be called before each call to Push(). This
94*09537850SAkhilesh Sanikop   // inconvenient design is necessary to guarantee a successful Push() call.
95*09537850SAkhilesh Sanikop   //
96*09537850SAkhilesh Sanikop   // Push(T&& value) is often called with the argument std::move(value). The
97*09537850SAkhilesh Sanikop   // moved-from object |value| won't be usable afterwards, so it would be
98*09537850SAkhilesh Sanikop   // problematic if Push(T&& value) failed and we lost access to the original
99*09537850SAkhilesh Sanikop   // |value| object.
GrowIfNeeded()100*09537850SAkhilesh Sanikop   LIBGAV1_MUST_USE_RESULT bool GrowIfNeeded() {
101*09537850SAkhilesh Sanikop     assert(last_block_ != nullptr);
102*09537850SAkhilesh Sanikop     if (back_ == kBlockCapacity) {
103*09537850SAkhilesh Sanikop       if (last_block_->next == first_block_) {
104*09537850SAkhilesh Sanikop         // All Blocks are in use.
105*09537850SAkhilesh Sanikop         std::unique_ptr<Block> new_block(new (std::nothrow) Block);
106*09537850SAkhilesh Sanikop         if (new_block == nullptr) return false;
107*09537850SAkhilesh Sanikop         new_block->next = first_block_;
108*09537850SAkhilesh Sanikop         last_block_->next = new_block.release();
109*09537850SAkhilesh Sanikop       }
110*09537850SAkhilesh Sanikop       last_block_ = last_block_->next;
111*09537850SAkhilesh Sanikop       back_ = 0;
112*09537850SAkhilesh Sanikop     }
113*09537850SAkhilesh Sanikop     return true;
114*09537850SAkhilesh Sanikop   }
115*09537850SAkhilesh Sanikop 
116*09537850SAkhilesh Sanikop   // Pushes the element |value| to the end of the queue. It is an error to call
117*09537850SAkhilesh Sanikop   // Push() when the queue is full.
Push(const T & value)118*09537850SAkhilesh Sanikop   void Push(const T& value) {
119*09537850SAkhilesh Sanikop     assert(last_block_ != nullptr);
120*09537850SAkhilesh Sanikop     assert(back_ < kBlockCapacity);
121*09537850SAkhilesh Sanikop     T* elements = reinterpret_cast<T*>(last_block_->buffer);
122*09537850SAkhilesh Sanikop     new (&elements[back_++]) T(value);
123*09537850SAkhilesh Sanikop   }
124*09537850SAkhilesh Sanikop 
Push(T && value)125*09537850SAkhilesh Sanikop   void Push(T&& value) {
126*09537850SAkhilesh Sanikop     assert(last_block_ != nullptr);
127*09537850SAkhilesh Sanikop     assert(back_ < kBlockCapacity);
128*09537850SAkhilesh Sanikop     T* elements = reinterpret_cast<T*>(last_block_->buffer);
129*09537850SAkhilesh Sanikop     new (&elements[back_++]) T(std::move(value));
130*09537850SAkhilesh Sanikop   }
131*09537850SAkhilesh Sanikop 
132*09537850SAkhilesh Sanikop   // Returns the element at the front of the queue. It is an error to call
133*09537850SAkhilesh Sanikop   // Front() when the queue is empty.
Front()134*09537850SAkhilesh Sanikop   T& Front() {
135*09537850SAkhilesh Sanikop     assert(!Empty());
136*09537850SAkhilesh Sanikop     T* elements = reinterpret_cast<T*>(first_block_->buffer);
137*09537850SAkhilesh Sanikop     return elements[front_];
138*09537850SAkhilesh Sanikop   }
139*09537850SAkhilesh Sanikop 
Front()140*09537850SAkhilesh Sanikop   const T& Front() const {
141*09537850SAkhilesh Sanikop     assert(!Empty());
142*09537850SAkhilesh Sanikop     T* elements = reinterpret_cast<T*>(first_block_->buffer);
143*09537850SAkhilesh Sanikop     return elements[front_];
144*09537850SAkhilesh Sanikop   }
145*09537850SAkhilesh Sanikop 
146*09537850SAkhilesh Sanikop   // Removes the element at the front of the queue from the queue. It is an
147*09537850SAkhilesh Sanikop   // error to call Pop() when the queue is empty.
Pop()148*09537850SAkhilesh Sanikop   void Pop() {
149*09537850SAkhilesh Sanikop     assert(!Empty());
150*09537850SAkhilesh Sanikop     T* elements = reinterpret_cast<T*>(first_block_->buffer);
151*09537850SAkhilesh Sanikop     elements[front_++].~T();
152*09537850SAkhilesh Sanikop     if (front_ == kBlockCapacity) {
153*09537850SAkhilesh Sanikop       // The first block has become empty.
154*09537850SAkhilesh Sanikop       front_ = 0;
155*09537850SAkhilesh Sanikop       if (first_block_ == last_block_) {
156*09537850SAkhilesh Sanikop         // Only one Block is in use. Simply reset back_.
157*09537850SAkhilesh Sanikop         back_ = 0;
158*09537850SAkhilesh Sanikop       } else {
159*09537850SAkhilesh Sanikop         first_block_ = first_block_->next;
160*09537850SAkhilesh Sanikop       }
161*09537850SAkhilesh Sanikop     }
162*09537850SAkhilesh Sanikop   }
163*09537850SAkhilesh Sanikop 
164*09537850SAkhilesh Sanikop   // Returns true if the queue is empty.
Empty()165*09537850SAkhilesh Sanikop   bool Empty() const { return first_block_ == last_block_ && front_ == back_; }
166*09537850SAkhilesh Sanikop 
167*09537850SAkhilesh Sanikop  private:
168*09537850SAkhilesh Sanikop   // kBlockCapacity is the maximum number of elements each Block can hold.
169*09537850SAkhilesh Sanikop   // sizeof(void*) is subtracted from 2048 to account for the |next| pointer in
170*09537850SAkhilesh Sanikop   // the Block struct.
171*09537850SAkhilesh Sanikop   //
172*09537850SAkhilesh Sanikop   // In Linux x86_64, sizeof(std::function<void()>) is 32, so each Block can
173*09537850SAkhilesh Sanikop   // hold 63 std::function<void()> objects.
174*09537850SAkhilesh Sanikop   //
175*09537850SAkhilesh Sanikop   // NOTE: The corresponding value in <deque> in libc++ revision
176*09537850SAkhilesh Sanikop   // 245b5ba3448b9d3f6de5962066557e253a6bc9a4 is:
177*09537850SAkhilesh Sanikop   //   template <class _ValueType, class _DiffType>
178*09537850SAkhilesh Sanikop   //   struct __deque_block_size {
179*09537850SAkhilesh Sanikop   //     static const _DiffType value =
180*09537850SAkhilesh Sanikop   //         sizeof(_ValueType) < 256 ? 4096 / sizeof(_ValueType) : 16;
181*09537850SAkhilesh Sanikop   //   };
182*09537850SAkhilesh Sanikop   //
183*09537850SAkhilesh Sanikop   // Note that 4096 / 256 = 16, so apparently this expression is intended to
184*09537850SAkhilesh Sanikop   // ensure the block size is at least 4096 bytes and each block can hold at
185*09537850SAkhilesh Sanikop   // least 16 elements.
186*09537850SAkhilesh Sanikop   static constexpr size_t kBlockCapacity =
187*09537850SAkhilesh Sanikop       (sizeof(T) < 128) ? (2048 - sizeof(void*)) / sizeof(T) : 16;
188*09537850SAkhilesh Sanikop 
189*09537850SAkhilesh Sanikop   struct Block : public Allocable {
190*09537850SAkhilesh Sanikop     alignas(T) char buffer[kBlockCapacity * sizeof(T)];
191*09537850SAkhilesh Sanikop     Block* next;
192*09537850SAkhilesh Sanikop   };
193*09537850SAkhilesh Sanikop 
Destroy()194*09537850SAkhilesh Sanikop   void Destroy() {
195*09537850SAkhilesh Sanikop     if (first_block_ == nullptr) return;  // An uninitialized queue.
196*09537850SAkhilesh Sanikop 
197*09537850SAkhilesh Sanikop     // First free the unused blocks, which are located after last_block and
198*09537850SAkhilesh Sanikop     // before first_block_.
199*09537850SAkhilesh Sanikop     Block* block = last_block_->next;
200*09537850SAkhilesh Sanikop     // Cut the circular list open after last_block_.
201*09537850SAkhilesh Sanikop     last_block_->next = nullptr;
202*09537850SAkhilesh Sanikop     while (block != first_block_) {
203*09537850SAkhilesh Sanikop       Block* next = block->next;
204*09537850SAkhilesh Sanikop       delete block;
205*09537850SAkhilesh Sanikop       block = next;
206*09537850SAkhilesh Sanikop     }
207*09537850SAkhilesh Sanikop 
208*09537850SAkhilesh Sanikop     // Then free the used blocks. Destruct the elements in the used blocks.
209*09537850SAkhilesh Sanikop     while (block != nullptr) {
210*09537850SAkhilesh Sanikop       const size_t begin = (block == first_block_) ? front_ : 0;
211*09537850SAkhilesh Sanikop       const size_t end = (block == last_block_) ? back_ : kBlockCapacity;
212*09537850SAkhilesh Sanikop       T* elements = reinterpret_cast<T*>(block->buffer);
213*09537850SAkhilesh Sanikop       for (size_t i = begin; i < end; ++i) {
214*09537850SAkhilesh Sanikop         elements[i].~T();
215*09537850SAkhilesh Sanikop       }
216*09537850SAkhilesh Sanikop       Block* next = block->next;
217*09537850SAkhilesh Sanikop       delete block;
218*09537850SAkhilesh Sanikop       block = next;
219*09537850SAkhilesh Sanikop     }
220*09537850SAkhilesh Sanikop   }
221*09537850SAkhilesh Sanikop 
222*09537850SAkhilesh Sanikop   // Blocks are chained in a circular singly-linked list. If the list of Blocks
223*09537850SAkhilesh Sanikop   // is empty, both first_block_ and last_block_ are null pointers. If the list
224*09537850SAkhilesh Sanikop   // is nonempty, first_block_ points to the first used Block and last_block_
225*09537850SAkhilesh Sanikop   // points to the last used Block.
226*09537850SAkhilesh Sanikop   //
227*09537850SAkhilesh Sanikop   // Invariant: If Init() is called and succeeds, the queue is always nonempty.
228*09537850SAkhilesh Sanikop   // This allows all methods (except the destructor) to avoid null pointer
229*09537850SAkhilesh Sanikop   // checks for first_block_ and last_block_.
230*09537850SAkhilesh Sanikop   Block* first_block_ = nullptr;
231*09537850SAkhilesh Sanikop   // The index of the element in first_block_ to be removed by Pop().
232*09537850SAkhilesh Sanikop   size_t front_ = 0;
233*09537850SAkhilesh Sanikop   Block* last_block_ = nullptr;
234*09537850SAkhilesh Sanikop   // The index in last_block_ where the new element is inserted by Push().
235*09537850SAkhilesh Sanikop   size_t back_ = 0;
236*09537850SAkhilesh Sanikop };
237*09537850SAkhilesh Sanikop 
238*09537850SAkhilesh Sanikop #if !LIBGAV1_CXX17
239*09537850SAkhilesh Sanikop template <typename T>
240*09537850SAkhilesh Sanikop constexpr size_t UnboundedQueue<T>::kBlockCapacity;
241*09537850SAkhilesh Sanikop #endif
242*09537850SAkhilesh Sanikop 
243*09537850SAkhilesh Sanikop }  // namespace libgav1
244*09537850SAkhilesh Sanikop 
245*09537850SAkhilesh Sanikop #endif  // LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_
246