1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_Queue_h
8 #define mozilla_Queue_h
9 
10 #include <utility>
11 #include <stdint.h>
12 #include "mozilla/MemoryReporting.h"
13 #include "mozilla/Assertions.h"
14 #include "mozalloc.h"
15 
16 namespace mozilla {
17 
18 // define to turn on additional (DEBUG) asserts
19 // #define EXTRA_ASSERTS 1
20 
21 // A queue implements a singly linked list of pages, each of which contains some
22 // number of elements. Since the queue needs to store a "next" pointer, the
23 // actual number of elements per page won't be quite as many as were requested.
24 //
25 // Each page consists of N entries.  We use the head buffer as a circular buffer
26 // if it's the only buffer; if we have more than one buffer when the head is
27 // empty we release it.  This avoids occasional freeing and reallocating buffers
28 // every N entries.  We'll still allocate and free every N if the normal queue
29 // depth is greated than N.  A fancier solution would be to move an empty Head
30 // buffer to be an empty tail buffer, freeing if we have multiple empty tails,
31 // but that probably isn't worth it.
32 //
33 // Cases:
34 //   a) single buffer, circular
35 //      Push: if not full:
36 //              Add to tail, bump tail and reset to 0 if at end
37 //            full:
38 //              Add new page, insert there and set tail to 1
39 //      Pop:
40 //            take entry and bump head, reset to 0 if at end
41 //   b) multiple buffers:
42 //      Push: if not full:
43 //              Add to tail, bump tail
44 //            full:
45 //              Add new page, insert there and set tail to 1
46 //      Pop:
47 //            take entry and bump head, reset to 0 if at end
48 //            if buffer is empty, free head buffer and promote next to head
49 //
50 template <class T, size_t RequestedItemsPerPage = 256>
51 class Queue {
52  public:
53   Queue() = default;
54 
Queue(Queue && aOther)55   Queue(Queue&& aOther) noexcept
56       : mHead(std::exchange(aOther.mHead, nullptr)),
57         mTail(std::exchange(aOther.mTail, nullptr)),
58         mOffsetHead(std::exchange(aOther.mOffsetHead, 0)),
59         mHeadLength(std::exchange(aOther.mHeadLength, 0)),
60         mTailLength(std::exchange(aOther.mTailLength, 0)) {}
61 
62   Queue& operator=(Queue&& aOther) noexcept {
63     Clear();
64 
65     mHead = std::exchange(aOther.mHead, nullptr);
66     mTail = std::exchange(aOther.mTail, nullptr);
67     mOffsetHead = std::exchange(aOther.mOffsetHead, 0);
68     mHeadLength = std::exchange(aOther.mHeadLength, 0);
69     mTailLength = std::exchange(aOther.mTailLength, 0);
70     return *this;
71   }
72 
~Queue()73   ~Queue() { Clear(); }
74 
75   // Discard all elements form the queue, clearing it to be empty.
Clear()76   void Clear() {
77     while (!IsEmpty()) {
78       Pop();
79     }
80     if (mHead) {
81       free(mHead);
82       mHead = nullptr;
83     }
84   }
85 
Push(T && aElement)86   T& Push(T&& aElement) {
87 #if defined(EXTRA_ASSERTS) && DEBUG
88     size_t original_length = Count();
89 #endif
90     if (!mHead) {
91       mHead = NewPage();
92       MOZ_ASSERT(mHead);
93 
94       mTail = mHead;
95       T* eltPtr = &mTail->mEvents[0];
96       new (eltPtr) T(std::move(aElement));
97       mOffsetHead = 0;
98       mHeadLength = 1;
99 #ifdef EXTRA_ASSERTS
100       MOZ_ASSERT(Count() == original_length + 1);
101 #endif
102       return *eltPtr;
103     }
104     if ((mHead == mTail && mHeadLength == ItemsPerPage) ||
105         (mHead != mTail && mTailLength == ItemsPerPage)) {
106       // either we have one (circular) buffer and it's full, or
107       // we have multiple buffers and the last buffer is full
108       Page* page = NewPage();
109       MOZ_ASSERT(page);
110 
111       mTail->mNext = page;
112       mTail = page;
113       T* eltPtr = &page->mEvents[0];
114       new (eltPtr) T(std::move(aElement));
115       mTailLength = 1;
116 #ifdef EXTRA_ASSERTS
117       MOZ_ASSERT(Count() == original_length + 1);
118 #endif
119       return *eltPtr;
120     }
121     if (mHead == mTail) {
122       // we have space in the (single) head buffer
123       uint16_t offset = (mOffsetHead + mHeadLength++) % ItemsPerPage;
124       T* eltPtr = &mTail->mEvents[offset];
125       new (eltPtr) T(std::move(aElement));
126 #ifdef EXTRA_ASSERTS
127       MOZ_ASSERT(Count() == original_length + 1);
128 #endif
129       return *eltPtr;
130     }
131     // else we have space to insert into last buffer
132     T* eltPtr = &mTail->mEvents[mTailLength++];
133     new (eltPtr) T(std::move(aElement));
134 #ifdef EXTRA_ASSERTS
135     MOZ_ASSERT(Count() == original_length + 1);
136 #endif
137     return *eltPtr;
138   }
139 
IsEmpty()140   bool IsEmpty() const {
141     return !mHead || (mHead == mTail && mHeadLength == 0);
142   }
143 
Pop()144   T Pop() {
145 #if defined(EXTRA_ASSERTS) && DEBUG
146     size_t original_length = Count();
147 #endif
148     MOZ_ASSERT(!IsEmpty());
149 
150     T result = std::move(mHead->mEvents[mOffsetHead]);
151     mHead->mEvents[mOffsetHead].~T();
152     mOffsetHead = (mOffsetHead + 1) % ItemsPerPage;
153     mHeadLength -= 1;
154 
155     // Check if mHead points to empty (circular) Page and we have more
156     // pages
157     if (mHead != mTail && mHeadLength == 0) {
158       Page* dead = mHead;
159       mHead = mHead->mNext;
160       free(dead);
161       mOffsetHead = 0;
162       // if there are still >1 pages, the new head is full.
163       if (mHead != mTail) {
164         mHeadLength = ItemsPerPage;
165       } else {
166         mHeadLength = mTailLength;
167         mTailLength = 0;
168       }
169     }
170 
171 #ifdef EXTRA_ASSERTS
172     MOZ_ASSERT(Count() == original_length - 1);
173 #endif
174     return result;
175   }
176 
FirstElement()177   T& FirstElement() {
178     MOZ_ASSERT(!IsEmpty());
179     return mHead->mEvents[mOffsetHead];
180   }
181 
FirstElement()182   const T& FirstElement() const {
183     MOZ_ASSERT(!IsEmpty());
184     return mHead->mEvents[mOffsetHead];
185   }
186 
LastElement()187   T& LastElement() {
188     MOZ_ASSERT(!IsEmpty());
189     uint16_t offset =
190         mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1;
191     return mTail->mEvents[offset];
192   }
193 
LastElement()194   const T& LastElement() const {
195     MOZ_ASSERT(!IsEmpty());
196     uint16_t offset =
197         mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1;
198     return mTail->mEvents[offset];
199   }
200 
Count()201   size_t Count() const {
202     // It is obvious count is 0 when the queue is empty.
203     if (!mHead) {
204       return 0;
205     }
206 
207     // Compute full (intermediate) pages; Doesn't count first or last page
208     int count = 0;
209     // 1 buffer will have mHead == mTail; 2 will have mHead->mNext == mTail
210     for (Page* page = mHead; page != mTail && page->mNext != mTail;
211          page = page->mNext) {
212       count += ItemsPerPage;
213     }
214     // add first and last page
215     count += mHeadLength + mTailLength;
216     MOZ_ASSERT(count >= 0);
217 
218     return count;
219   }
220 
ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf)221   size_t ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
222     size_t n = 0;
223     if (mHead) {
224       for (Page* page = mHead; page != mTail; page = page->mNext) {
225         n += aMallocSizeOf(page);
226       }
227     }
228     return n;
229   }
230 
ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf)231   size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
232     return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
233   }
234 
235  private:
236   static_assert(
237       (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0,
238       "RequestedItemsPerPage should be a power of two to avoid heap slop.");
239 
240   // Since a Page must also contain a "next" pointer, we use one of the items to
241   // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be
242   // wasted. So be it.
243   static const size_t ItemsPerPage = RequestedItemsPerPage - 1;
244 
245   // Page objects are linked together to form a simple deque.
246   struct Page {
247     struct Page* mNext;
248     T mEvents[ItemsPerPage];
249   };
250 
NewPage()251   static Page* NewPage() {
252     return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
253   }
254 
255   Page* mHead = nullptr;
256   Page* mTail = nullptr;
257 
258   uint16_t mOffsetHead = 0;  // Read position in head page
259   uint16_t mHeadLength = 0;  // Number of items in the head page
260   uint16_t mTailLength = 0;  // Number of items in the tail page
261 };
262 
263 }  // namespace mozilla
264 
265 #endif  // mozilla_Queue_h
266