SlimLinkedList.h (11737B)
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 /* 8 * The classes SlimLinkedList<T> and SlimLinkedListElement<T> provide a 9 * type-safe doubly-linked list class which uses one word for the list and two 10 * words for each element (for comparison mozilla::LinkedList uses three words 11 * for the list and for each element due to padding). 12 * 13 * This aims to be a replacement for mozilla::LinkedList although the interface 14 * is not identical. In particular most actions are implemented as methods on 15 * the list itself as opposed to the element. 16 * 17 * Private element inheritance is not supported; clients must publicly derive 18 * from LinkedListElement. 19 */ 20 21 #ifndef ds_SlimLinkedList_h 22 #define ds_SlimLinkedList_h 23 24 #include "mozilla/Assertions.h" 25 26 #include <algorithm> 27 #include <utility> 28 29 namespace js { 30 31 template <typename T> 32 class SlimLinkedListElement; 33 34 template <typename T> 35 class SlimLinkedList; 36 37 template <typename T> 38 class SlimLinkedListElement { 39 using ElementPtr = T*; 40 using ConstElementPtr = const T*; 41 42 // Tag bit used to indicate the start/end of the list. The tag is set on the 43 // prev_ pointer of the first node and the next_ pointer of the last node in 44 // the list. 45 static constexpr uintptr_t EndTag = 1; 46 47 uintptr_t next_ = 0; 48 uintptr_t prev_ = 0; 49 50 friend class js::SlimLinkedList<T>; 51 52 static uintptr_t UntaggedPtr(ElementPtr ptr) { 53 MOZ_ASSERT((uintptr_t(ptr) & EndTag) == 0); 54 return uintptr_t(ptr); 55 } 56 static uintptr_t GetTag(uintptr_t taggedPtr) { return taggedPtr & EndTag; } 57 static ElementPtr GetPtr(uintptr_t taggedPtr) { 58 return reinterpret_cast<ElementPtr>(uintptr_t(taggedPtr) & ~EndTag); 59 } 60 static ConstElementPtr GetConstPtr(uintptr_t taggedPtr) { 61 return reinterpret_cast<ConstElementPtr>(uintptr_t(taggedPtr) & ~EndTag); 62 } 63 64 static void LinkElements(ElementPtr a, ElementPtr b, uintptr_t maybeTag = 0) { 65 MOZ_ASSERT((maybeTag & ~EndTag) == 0); 66 a->next_ = UntaggedPtr(b) | maybeTag; 67 b->prev_ = UntaggedPtr(a) | maybeTag; 68 } 69 70 public: 71 SlimLinkedListElement() = default; 72 73 ~SlimLinkedListElement() { MOZ_ASSERT(!isInList()); } 74 75 SlimLinkedListElement(const SlimLinkedListElement<T>& other) = delete; 76 SlimLinkedListElement& operator=(const SlimLinkedListElement<T>& other) = 77 delete; 78 79 // Don't allow moving elements that are part of a list. 80 SlimLinkedListElement(SlimLinkedListElement<T>&& other) { 81 MOZ_ASSERT(this != &other); 82 MOZ_ASSERT(!isInList()); 83 MOZ_ASSERT(!other.isInList()); 84 } 85 SlimLinkedListElement& operator=(SlimLinkedListElement<T>&& other) { 86 MOZ_ASSERT(this != &other); 87 MOZ_ASSERT(!isInList()); 88 MOZ_ASSERT(!other.isInList()); 89 return *this; 90 } 91 92 bool isInList() const { 93 MOZ_ASSERT(bool(next_) == bool(prev_)); 94 return next_; 95 } 96 97 bool isLast() const { 98 MOZ_ASSERT(isInList()); 99 return GetTag(next_); 100 } 101 102 bool isFirst() const { 103 MOZ_ASSERT(isInList()); 104 return GetTag(prev_); 105 } 106 107 ElementPtr getNext() { return isLast() ? nullptr : getNextUnchecked(); } 108 ConstElementPtr getNext() const { 109 return isLast() ? nullptr : getNextUnchecked(); 110 } 111 112 ElementPtr getPrev() { return isFirst() ? nullptr : getPrevUnchecked(); } 113 ConstElementPtr getPrev() const { 114 return isFirst() ? nullptr : getPrevUnchecked(); 115 } 116 117 private: 118 ElementPtr getNextUnchecked() { return GetPtr(next_); } 119 ConstElementPtr getNextUnchecked() const { return GetConstPtr(next_); }; 120 ElementPtr getPrevUnchecked() { return GetPtr(prev_); } 121 ConstElementPtr getPrevUnchecked() const { return GetConstPtr(prev_); }; 122 123 ElementPtr thisElement() { return static_cast<ElementPtr>(this); } 124 125 void makeSingleton() { 126 MOZ_ASSERT(!isInList()); 127 LinkElements(thisElement(), thisElement(), EndTag); 128 } 129 130 void insertAfter(ElementPtr newElement) { 131 insertListAfter(newElement, newElement); 132 } 133 134 /* 135 * Insert the list of elements from |listFirst| to |listLast| between |this| 136 * and the next element |next|. Any tag goes between |listLast| and |next|. 137 */ 138 void insertListAfter(ElementPtr listFirst, ElementPtr listLast) { 139 MOZ_ASSERT(isInList()); 140 MOZ_ASSERT_IF(listFirst != listLast, 141 listFirst->getPrevUnchecked() == listLast); 142 MOZ_ASSERT_IF(listFirst != listLast, 143 listLast->getNextUnchecked() == listFirst); 144 145 ElementPtr next = GetPtr(next_); 146 uintptr_t tag = GetTag(next_); 147 148 LinkElements(thisElement(), listFirst); 149 LinkElements(listLast, next, tag); 150 } 151 152 void insertBefore(ElementPtr newElement) { 153 insertListBefore(newElement, newElement); 154 } 155 156 /* 157 * Insert the list of elements from |listFirst| to |listLast| between the 158 * previous element |prev| and |this|. Any tag goes between |prev| and 159 * |listFirst|. 160 */ 161 void insertListBefore(ElementPtr listFirst, ElementPtr listLast) { 162 MOZ_ASSERT(isInList()); 163 MOZ_ASSERT_IF(listFirst != listLast, 164 listFirst->getPrevUnchecked() == listLast); 165 MOZ_ASSERT_IF(listFirst != listLast, 166 listLast->getNextUnchecked() == listFirst); 167 168 ElementPtr prev = GetPtr(prev_); 169 uintptr_t tag = GetTag(prev_); 170 171 LinkElements(prev, listFirst, tag); 172 LinkElements(listLast, thisElement()); 173 } 174 175 /* 176 * Remove element |this| from its containing list. 177 */ 178 void remove() { 179 MOZ_ASSERT(isInList()); 180 181 ElementPtr prev = GetPtr(prev_); 182 ElementPtr next = GetPtr(next_); 183 uintptr_t tag = GetTag(prev_) | GetTag(next_); 184 185 LinkElements(prev, next, tag); 186 187 next_ = 0; 188 prev_ = 0; 189 } 190 }; 191 192 template <typename T> 193 class SlimLinkedList { 194 using ElementPtr = T*; 195 using ConstElementPtr = const T*; 196 197 ElementPtr first_ = nullptr; 198 199 public: 200 template <typename Type> 201 class Iterator { 202 Type current_; 203 204 public: 205 using iterator_category = std::forward_iterator_tag; 206 using value_type = T; 207 using difference_type = std::ptrdiff_t; 208 using pointer = T*; 209 using reference = T&; 210 211 explicit Iterator(Type current) : current_(current) {} 212 213 Type operator*() const { return current_; } 214 215 const Iterator& operator++() { 216 current_ = current_->getNext(); 217 return *this; 218 } 219 220 bool operator==(const Iterator& other) const { 221 return current_ == other.current_; 222 } 223 bool operator!=(const Iterator& other) const { return !(*this == other); } 224 }; 225 226 SlimLinkedList() = default; 227 228 SlimLinkedList(const SlimLinkedList<T>& other) = delete; 229 SlimLinkedList& operator=(const SlimLinkedList<T>& other) = delete; 230 231 SlimLinkedList(SlimLinkedList<T>&& other) { 232 MOZ_ASSERT(this != &other); 233 MOZ_ASSERT(isEmpty()); 234 std::swap(first_, other.first_); 235 } 236 SlimLinkedList& operator=(SlimLinkedList<T>&& other) { 237 MOZ_ASSERT(this != &other); 238 MOZ_ASSERT(isEmpty()); 239 std::swap(first_, other.first_); 240 return *this; 241 } 242 243 ~SlimLinkedList() { MOZ_ASSERT(isEmpty()); } 244 245 /* 246 * Add |newElement| to the front of the list. 247 */ 248 void pushFront(ElementPtr newElement) { 249 if (isEmpty()) { 250 newElement->makeSingleton(); 251 } else { 252 first_->insertBefore(newElement); 253 } 254 first_ = newElement; 255 } 256 257 /* 258 * Add |newElement| to the back of the list. 259 */ 260 void pushBack(ElementPtr newElement) { 261 if (isEmpty()) { 262 newElement->makeSingleton(); 263 first_ = newElement; 264 return; 265 } 266 267 getLast()->insertAfter(newElement); 268 } 269 270 /* 271 * Move all elements from list |other| to the end of this list. |other| is 272 * left empty. 273 */ 274 void append(SlimLinkedList<T>&& other) { 275 MOZ_ASSERT(this != &other); 276 if (other.isEmpty()) { 277 return; 278 } 279 280 if (isEmpty()) { 281 *this = std::move(other); 282 return; 283 } 284 285 getLast()->insertListAfter(other.getFirst(), other.getLast()); 286 other.first_ = nullptr; 287 } 288 289 /* 290 * Move all elements from list |other| to the start of this list. |other| is 291 * left empty. 292 */ 293 void prepend(SlimLinkedList<T>&& other) { 294 MOZ_ASSERT(this != &other); 295 if (other.isEmpty()) { 296 return; 297 } 298 299 if (isEmpty()) { 300 *this = std::move(other); 301 return; 302 } 303 304 getFirst()->insertListBefore(other.getFirst(), other.getLast()); 305 first_ = other.first_; 306 other.first_ = nullptr; 307 } 308 309 /* 310 * Get the first element of the list, or nullptr if the list is empty. 311 */ 312 ElementPtr getFirst() { return first_; } 313 ConstElementPtr getFirst() const { return first_; } 314 315 /* 316 * Get the last element of the list, or nullptr if the list is empty. 317 */ 318 ElementPtr getLast() { 319 return isEmpty() ? nullptr : first_->getPrevUnchecked(); 320 } 321 ConstElementPtr getLast() const { 322 return isEmpty() ? nullptr : first_->getPrevUnchecked(); 323 } 324 325 /* 326 * Get and remove the first element of the list. If the list is empty, return 327 * nullptr. 328 */ 329 ElementPtr popFirst() { 330 if (isEmpty()) { 331 return nullptr; 332 } 333 334 ElementPtr result = first_; 335 first_ = result->getNext(); 336 result->remove(); 337 return result; 338 } 339 340 /* 341 * Get and remove the last element of the list. If the list is empty, return 342 * nullptr. 343 */ 344 ElementPtr popLast() { 345 if (isEmpty()) { 346 return nullptr; 347 } 348 349 ElementPtr result = getLast(); 350 if (result == first_) { 351 first_ = nullptr; 352 } 353 result->remove(); 354 return result; 355 } 356 357 /* 358 * Return true if the list is empty, or false otherwise. 359 */ 360 bool isEmpty() const { return !first_; } 361 362 /* 363 * Returns whether the given element is in the list. 364 */ 365 bool contains(ConstElementPtr aElm) const { 366 return std::find(begin(), end(), aElm) != end(); 367 } 368 369 /* 370 * Remove |element| from this list. 371 */ 372 void remove(ElementPtr element) { 373 checkContains(element); 374 if (element == first_) { 375 first_ = element->getNext(); 376 } 377 element->remove(); 378 } 379 380 void checkContains(ElementPtr element) { 381 #ifdef DEBUG 382 size_t i = 0; 383 for (const auto& e : *this) { 384 if (e == element) { 385 return; // Found. 386 } 387 if (i == 100) { 388 return; // Limit time spent checking. 389 } 390 } 391 MOZ_CRASH("Element not found"); 392 #endif 393 } 394 395 /* 396 * Remove all the elements from the list. 397 * 398 * This runs in time linear to the list's length, because we have to mark 399 * each element as not in the list. 400 */ 401 void clear() { 402 while (popFirst()) { 403 } 404 } 405 406 /* 407 * Remove all the elements from the list, calling |func| on each one first. On 408 * return the list is empty. 409 */ 410 template <typename F> 411 void drain(F&& func) { 412 while (ElementPtr element = popFirst()) { 413 func(element); 414 } 415 } 416 417 /* 418 * Remove all the elements from the list that match a predicate. 419 */ 420 template <typename F> 421 void eraseIf(F&& pred) { 422 ElementPtr element = getFirst(); 423 while (element) { 424 ElementPtr next = element->getNext(); 425 if (pred(element)) { 426 remove(element); 427 } 428 element = next; 429 } 430 } 431 432 /** 433 * Return the length of elements in the list. 434 */ 435 size_t length() const { return std::distance(begin(), end()); } 436 437 /* 438 * Allow range-based iteration: 439 * 440 * for (MyElementPtr* elt : myList) { ... } 441 */ 442 Iterator<ElementPtr> begin() { return Iterator<ElementPtr>(getFirst()); } 443 Iterator<ConstElementPtr> begin() const { 444 return Iterator<ConstElementPtr>(getFirst()); 445 } 446 Iterator<ElementPtr> end() { return Iterator<ElementPtr>(nullptr); } 447 Iterator<ConstElementPtr> end() const { 448 return Iterator<ConstElementPtr>(nullptr); 449 } 450 }; 451 452 } /* namespace js */ 453 454 #endif /* ds_SlimLinkedList_h */