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