tor-browser

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

SegmentedVector.h (11243B)


      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 // A simple segmented vector class.
      8 //
      9 // This class should be used in preference to mozilla::Vector or nsTArray when
     10 // you are simply gathering items in order to later iterate over them.
     11 //
     12 // - In the case where you don't know the final size in advance, using
     13 //   SegmentedVector avoids the need to repeatedly allocate increasingly large
     14 //   buffers and copy the data into them.
     15 //
     16 // - In the case where you know the final size in advance and so can set the
     17 //   capacity appropriately, using SegmentedVector still avoids the need for
     18 //   large allocations (which can trigger OOMs).
     19 
     20 #ifndef mozilla_SegmentedVector_h
     21 #define mozilla_SegmentedVector_h
     22 
     23 #include <new>  // for placement new
     24 #include <utility>
     25 
     26 #include "mozilla/AllocPolicy.h"
     27 #include "mozilla/Attributes.h"
     28 #include "mozilla/LinkedList.h"
     29 #include "mozilla/MemoryReporting.h"
     30 #include "mozilla/OperatorNewExtensions.h"
     31 
     32 #ifdef IMPL_LIBXUL
     33 #  include "mozilla/Likely.h"
     34 #  include "mozilla/mozalloc_oom.h"
     35 #endif  // IMPL_LIBXUL
     36 
     37 namespace mozilla {
     38 
     39 // |IdealSegmentSize| specifies how big each segment will be in bytes (or as
     40 // close as is possible). Use the following guidelines to choose a size.
     41 //
     42 // - It should be a power-of-two, to avoid slop.
     43 //
     44 // - It should not be too small, so that segment allocations are infrequent,
     45 //   and so that per-segment bookkeeping overhead is low. Typically each
     46 //   segment should be able to hold hundreds of elements, at least.
     47 //
     48 // - It should not be too large, so that OOMs are unlikely when allocating
     49 //   segments, and so that not too much space is wasted when the final segment
     50 //   is not full.
     51 //
     52 // The ideal size depends on how the SegmentedVector is used and the size of
     53 // |T|, but reasonable sizes include 1024, 4096 (the default), 8192, and 16384.
     54 //
     55 template <typename T, size_t IdealSegmentSize = 4096,
     56          typename AllocPolicy = MallocAllocPolicy>
     57 class SegmentedVector : private AllocPolicy {
     58  template <size_t SegmentCapacity>
     59  struct SegmentImpl
     60      : public mozilla::LinkedListElement<SegmentImpl<SegmentCapacity>> {
     61   private:
     62    uint32_t mLength;
     63    alignas(T) MOZ_INIT_OUTSIDE_CTOR
     64        unsigned char mData[sizeof(T) * SegmentCapacity];
     65 
     66    // Some versions of GCC treat it as a -Wstrict-aliasing violation (ergo a
     67    // -Werror compile error) to reinterpret_cast<> |mData| to |T*|, even
     68    // through |void*|.  Placing the latter cast in these separate functions
     69    // breaks the chain such that affected GCC versions no longer warn/error.
     70    void* RawData() { return mData; }
     71 
     72   public:
     73    SegmentImpl() : mLength(0) {}
     74 
     75    ~SegmentImpl() {
     76      for (uint32_t i = 0; i < mLength; i++) {
     77        (*this)[i].~T();
     78      }
     79    }
     80 
     81    uint32_t Length() const { return mLength; }
     82 
     83    T* Elems() { return reinterpret_cast<T*>(RawData()); }
     84 
     85    T& operator[](size_t aIndex) {
     86      MOZ_ASSERT(aIndex < mLength);
     87      return Elems()[aIndex];
     88    }
     89 
     90    const T& operator[](size_t aIndex) const {
     91      MOZ_ASSERT(aIndex < mLength);
     92      return Elems()[aIndex];
     93    }
     94 
     95    template <typename U>
     96    void Append(U&& aU) {
     97      MOZ_ASSERT(mLength < SegmentCapacity);
     98      // Pre-increment mLength so that the bounds-check in operator[] passes.
     99      mLength++;
    100      T* elem = &(*this)[mLength - 1];
    101      new (KnownNotNull, elem) T(std::forward<U>(aU));
    102    }
    103 
    104    void PopLast() {
    105      MOZ_ASSERT(mLength > 0);
    106      (*this)[mLength - 1].~T();
    107      mLength--;
    108    }
    109  };
    110 
    111  // See how many we elements we can fit in a segment of IdealSegmentSize. If
    112  // IdealSegmentSize is too small, it'll be just one. The +1 is because
    113  // kSingleElementSegmentSize already accounts for one element.
    114  static const size_t kSingleElementSegmentSize = sizeof(SegmentImpl<1>);
    115  static const size_t kSegmentCapacity =
    116      kSingleElementSegmentSize <= IdealSegmentSize
    117          ? (IdealSegmentSize - kSingleElementSegmentSize) / sizeof(T) + 1
    118          : 1;
    119 
    120 public:
    121  typedef SegmentImpl<kSegmentCapacity> Segment;
    122 
    123  // The |aIdealSegmentSize| is only for sanity checking. If it's specified, we
    124  // check that the actual segment size is as close as possible to it. This
    125  // serves as a sanity check for SegmentedVectorCapacity's capacity
    126  // computation.
    127  explicit SegmentedVector(size_t aIdealSegmentSize = 0) {
    128    // The difference between the actual segment size and the ideal segment
    129    // size should be less than the size of a single element... unless the
    130    // ideal size was too small, in which case the capacity should be one.
    131    MOZ_ASSERT_IF(
    132        aIdealSegmentSize != 0,
    133        (sizeof(Segment) > aIdealSegmentSize && kSegmentCapacity == 1) ||
    134            aIdealSegmentSize - sizeof(Segment) < sizeof(T));
    135  }
    136 
    137  SegmentedVector(SegmentedVector&& aOther)
    138      : mSegments(std::move(aOther.mSegments)) {}
    139  SegmentedVector& operator=(SegmentedVector&& aOther) {
    140    if (&aOther != this) {
    141      this->~SegmentedVector();
    142      new (this) SegmentedVector(std::move(aOther));
    143    }
    144    return *this;
    145  }
    146 
    147  ~SegmentedVector() { Clear(); }
    148 
    149  bool IsEmpty() const { return !mSegments.getFirst(); }
    150 
    151  // Note that this is O(n) rather than O(1), but the constant factor is very
    152  // small because it only has to do one addition per segment.
    153  size_t Length() const {
    154    size_t n = 0;
    155    for (auto segment = mSegments.getFirst(); segment;
    156         segment = segment->getNext()) {
    157      n += segment->Length();
    158    }
    159    return n;
    160  }
    161 
    162  // Returns false if the allocation failed. (If you are using an infallible
    163  // allocation policy, use InfallibleAppend() instead.)
    164  template <typename U>
    165  [[nodiscard]] bool Append(U&& aU) {
    166    Segment* last = mSegments.getLast();
    167    if (!last || last->Length() == kSegmentCapacity) {
    168      last = this->template pod_malloc<Segment>(1);
    169      if (!last) {
    170        return false;
    171      }
    172      new (KnownNotNull, last) Segment();
    173      mSegments.insertBack(last);
    174    }
    175    last->Append(std::forward<U>(aU));
    176    return true;
    177  }
    178 
    179  // You should probably only use this instead of Append() if you are using an
    180  // infallible allocation policy. It will crash if the allocation fails.
    181  template <typename U>
    182  void InfallibleAppend(U&& aU) {
    183    bool ok = Append(std::forward<U>(aU));
    184 
    185 #ifdef IMPL_LIBXUL
    186    if (MOZ_UNLIKELY(!ok)) {
    187      mozalloc_handle_oom(sizeof(Segment));
    188    }
    189 #else
    190    MOZ_RELEASE_ASSERT(ok);
    191 #endif  // MOZ_INTERNAL_API
    192  }
    193 
    194  void Clear() {
    195    Segment* segment;
    196    while ((segment = mSegments.popFirst())) {
    197      segment->~Segment();
    198      this->free_(segment, 1);
    199    }
    200  }
    201 
    202  T& GetLast() {
    203    MOZ_ASSERT(!IsEmpty());
    204    Segment* last = mSegments.getLast();
    205    return (*last)[last->Length() - 1];
    206  }
    207 
    208  const T& GetLast() const {
    209    MOZ_ASSERT(!IsEmpty());
    210    Segment* last = mSegments.getLast();
    211    return (*last)[last->Length() - 1];
    212  }
    213 
    214  void PopLast() {
    215    MOZ_ASSERT(!IsEmpty());
    216    Segment* last = mSegments.getLast();
    217    last->PopLast();
    218    if (!last->Length()) {
    219      mSegments.popLast();
    220      last->~Segment();
    221      this->free_(last, 1);
    222    }
    223  }
    224 
    225  // Equivalent to calling |PopLast| |aNumElements| times, but potentially
    226  // more efficient. It is safe to call this even when aNumElements > Length().
    227  void PopLastN(uint32_t aNumElements) {
    228    Segment* last;
    229 
    230    // Pop full segments for as long as we can.  Note that this loop
    231    // cleanly handles the case when the initial last segment is not
    232    // full and we are popping more elements than said segment contains.
    233    do {
    234      last = mSegments.getLast();
    235 
    236      // The list is empty.  We're all done.
    237      if (!last) {
    238        return;
    239      }
    240 
    241      // Check to see if the list contains too many elements.  Handle
    242      // that in the epilogue.
    243      uint32_t segmentLen = last->Length();
    244      if (segmentLen > aNumElements) {
    245        break;
    246      }
    247 
    248      // Destroying the segment destroys all elements contained therein.
    249      mSegments.popLast();
    250      last->~Segment();
    251      this->free_(last, 1);
    252 
    253      MOZ_ASSERT(aNumElements >= segmentLen);
    254      aNumElements -= segmentLen;
    255      if (aNumElements == 0) {
    256        return;
    257      }
    258    } while (true);
    259 
    260    // Handle the case where the last segment contains more elements
    261    // than we want to pop.
    262    MOZ_ASSERT(last);
    263    MOZ_ASSERT(last == mSegments.getLast());
    264    MOZ_ASSERT(aNumElements < last->Length());
    265    for (uint32_t i = 0; i < aNumElements; ++i) {
    266      last->PopLast();
    267    }
    268    MOZ_ASSERT(last->Length() != 0);
    269  }
    270 
    271  // Use this class to iterate over a SegmentedVector, like so:
    272  //
    273  //  for (auto iter = v.Iter(); !iter.Done(); iter.Next()) {
    274  //    MyElem& elem = iter.Get();
    275  //    f(elem);
    276  //  }
    277  //
    278  // Note, adding new entries to the SegmentedVector while using iterators
    279  // is supported, but removing is not!
    280  // If an iterator has entered Done() state, adding more entries to the
    281  // vector doesn't affect it.
    282  class IterImpl {
    283    friend class SegmentedVector;
    284 
    285    Segment* mSegment;
    286    size_t mIndex;
    287 
    288    explicit IterImpl(SegmentedVector* aVector, bool aFromFirst)
    289        : mSegment(aFromFirst ? aVector->mSegments.getFirst()
    290                              : aVector->mSegments.getLast()),
    291          mIndex(aFromFirst ? 0 : (mSegment ? mSegment->Length() - 1 : 0)) {
    292      MOZ_ASSERT_IF(mSegment, mSegment->Length() > 0);
    293    }
    294 
    295   public:
    296    bool Done() const {
    297      MOZ_ASSERT_IF(mSegment, mSegment->isInList());
    298      MOZ_ASSERT_IF(mSegment, mIndex < mSegment->Length());
    299      return !mSegment;
    300    }
    301 
    302    T& Get() {
    303      MOZ_ASSERT(!Done());
    304      return (*mSegment)[mIndex];
    305    }
    306 
    307    const T& Get() const {
    308      MOZ_ASSERT(!Done());
    309      return (*mSegment)[mIndex];
    310    }
    311 
    312    void Next() {
    313      MOZ_ASSERT(!Done());
    314      mIndex++;
    315      if (mIndex == mSegment->Length()) {
    316        mSegment = mSegment->getNext();
    317        mIndex = 0;
    318      }
    319    }
    320 
    321    void Prev() {
    322      MOZ_ASSERT(!Done());
    323      if (mIndex == 0) {
    324        mSegment = mSegment->getPrevious();
    325        if (mSegment) {
    326          mIndex = mSegment->Length() - 1;
    327        }
    328      } else {
    329        --mIndex;
    330      }
    331    }
    332  };
    333 
    334  IterImpl Iter() { return IterImpl(this, true); }
    335  IterImpl IterFromLast() { return IterImpl(this, false); }
    336 
    337  // Measure the memory consumption of the vector excluding |this|. Note that
    338  // it only measures the vector itself. If the vector elements contain
    339  // pointers to other memory blocks, those blocks must be measured separately
    340  // during a subsequent iteration over the vector.
    341  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
    342    return mSegments.sizeOfExcludingThis(aMallocSizeOf);
    343  }
    344 
    345  // Like sizeOfExcludingThis(), but measures |this| as well.
    346  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
    347    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    348  }
    349 
    350 private:
    351  mozilla::LinkedList<Segment> mSegments;
    352 };
    353 
    354 }  // namespace mozilla
    355 
    356 #endif /* mozilla_SegmentedVector_h */