tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */