Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / xpcom / threads / Queue.h
blobfa36433fdfeb7c692a0a7c52a02456e6ff94f29d
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/. */
7 #ifndef mozilla_Queue_h
8 #define mozilla_Queue_h
10 #include <utility>
11 #include <stdint.h>
12 #include "mozilla/MemoryReporting.h"
13 #include "mozilla/Assertions.h"
14 #include "mozalloc.h"
16 namespace mozilla {
18 // define to turn on additional (DEBUG) asserts
19 // #define EXTRA_ASSERTS 1
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.
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.
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
50 template <class T, size_t RequestedItemsPerPage = 256>
51 class Queue {
52 public:
53 Queue() = default;
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)) {}
62 Queue& operator=(Queue&& aOther) noexcept {
63 Clear();
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;
73 ~Queue() { Clear(); }
75 // Discard all elements form the queue, clearing it to be empty.
76 void Clear() {
77 while (!IsEmpty()) {
78 Pop();
80 if (mHead) {
81 free(mHead);
82 mHead = nullptr;
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);
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;
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);
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;
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;
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;
140 bool IsEmpty() const {
141 return !mHead || (mHead == mTail && mHeadLength == 0);
144 T Pop() {
145 #if defined(EXTRA_ASSERTS) && DEBUG
146 size_t original_length = Count();
147 #endif
148 MOZ_ASSERT(!IsEmpty());
150 T result = std::move(mHead->mEvents[mOffsetHead]);
151 mHead->mEvents[mOffsetHead].~T();
152 mOffsetHead = (mOffsetHead + 1) % ItemsPerPage;
153 mHeadLength -= 1;
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;
171 #ifdef EXTRA_ASSERTS
172 MOZ_ASSERT(Count() == original_length - 1);
173 #endif
174 return result;
177 T& FirstElement() {
178 MOZ_ASSERT(!IsEmpty());
179 return mHead->mEvents[mOffsetHead];
182 const T& FirstElement() const {
183 MOZ_ASSERT(!IsEmpty());
184 return mHead->mEvents[mOffsetHead];
187 T& LastElement() {
188 MOZ_ASSERT(!IsEmpty());
189 uint16_t offset =
190 mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1;
191 return mTail->mEvents[offset];
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];
201 size_t Count() const {
202 // It is obvious count is 0 when the queue is empty.
203 if (!mHead) {
204 return 0;
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;
214 // add first and last page
215 count += mHeadLength + mTailLength;
216 MOZ_ASSERT(count >= 0);
218 return count;
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);
228 return n;
231 size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
232 return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
235 private:
236 static_assert(
237 (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0,
238 "RequestedItemsPerPage should be a power of two to avoid heap slop.");
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;
245 // Page objects are linked together to form a simple deque.
246 struct Page {
247 struct Page* mNext;
248 T mEvents[ItemsPerPage];
251 static Page* NewPage() {
252 return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
255 Page* mHead = nullptr;
256 Page* mTail = nullptr;
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
263 } // namespace mozilla
265 #endif // mozilla_Queue_h