Bug 1700051: part 13) Reduce accessibility of `mozInlineSpellStatus`'s constructor...
[gecko.git] / mfbt / BufferList.h
blob06662c70fc2a4c7c4bf73e1e2edc73578e8249d5
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_BufferList_h
8 #define mozilla_BufferList_h
10 #include <algorithm>
11 #include <cstdint>
12 #include <cstring>
14 #include "mozilla/Assertions.h"
15 #include "mozilla/Attributes.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/Vector.h"
20 // BufferList represents a sequence of buffers of data. A BufferList can choose
21 // to own its buffers or not. The class handles writing to the buffers,
22 // iterating over them, and reading data out. Unlike SegmentedVector, the
23 // buffers may be of unequal size. Like SegmentedVector, BufferList is a nice
24 // way to avoid large contiguous allocations (which can trigger OOMs).
26 class InfallibleAllocPolicy;
28 namespace mozilla {
30 template <typename AllocPolicy>
31 class BufferList : private AllocPolicy {
32 // Each buffer in a BufferList has a size and a capacity. The first mSize
33 // bytes are initialized and the remaining |mCapacity - mSize| bytes are free.
34 struct Segment {
35 char* mData;
36 size_t mSize;
37 size_t mCapacity;
39 Segment(char* aData, size_t aSize, size_t aCapacity)
40 : mData(aData), mSize(aSize), mCapacity(aCapacity) {}
42 Segment(const Segment&) = delete;
43 Segment& operator=(const Segment&) = delete;
45 Segment(Segment&&) = default;
46 Segment& operator=(Segment&&) = default;
48 char* Start() const { return mData; }
49 char* End() const { return mData + mSize; }
52 template <typename OtherAllocPolicy>
53 friend class BufferList;
55 public:
56 // For the convenience of callers, all segments are required to be a multiple
57 // of 8 bytes in capacity. Also, every buffer except the last one is required
58 // to be full (i.e., size == capacity). Therefore, a byte at offset N within
59 // the BufferList and stored in memory at an address A will satisfy
60 // (N % Align == A % Align) if Align == 2, 4, or 8.
61 static const size_t kSegmentAlignment = 8;
63 // Allocate a BufferList. The BufferList will free all its buffers when it is
64 // destroyed. If an infallible allocator is used, an initial buffer of size
65 // aInitialSize and capacity aInitialCapacity is allocated automatically. This
66 // data will be contiguous and can be accessed via |Start()|. If a fallible
67 // alloc policy is used, aInitialSize must be 0, and the fallible |Init()|
68 // method may be called instead. Subsequent buffers will be allocated with
69 // capacity aStandardCapacity.
70 BufferList(size_t aInitialSize, size_t aInitialCapacity,
71 size_t aStandardCapacity, AllocPolicy aAP = AllocPolicy())
72 : AllocPolicy(aAP),
73 mOwning(true),
74 mSegments(aAP),
75 mSize(0),
76 mStandardCapacity(aStandardCapacity) {
77 MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0);
78 MOZ_ASSERT(aStandardCapacity % kSegmentAlignment == 0);
80 if (aInitialCapacity) {
81 MOZ_ASSERT((aInitialSize == 0 ||
82 std::is_same_v<AllocPolicy, InfallibleAllocPolicy>),
83 "BufferList may only be constructed with an initial size when "
84 "using an infallible alloc policy");
86 AllocateSegment(aInitialSize, aInitialCapacity);
90 BufferList(const BufferList& aOther) = delete;
92 BufferList(BufferList&& aOther)
93 : mOwning(aOther.mOwning),
94 mSegments(std::move(aOther.mSegments)),
95 mSize(aOther.mSize),
96 mStandardCapacity(aOther.mStandardCapacity) {
97 aOther.mSegments.clear();
98 aOther.mSize = 0;
101 BufferList& operator=(const BufferList& aOther) = delete;
103 BufferList& operator=(BufferList&& aOther) {
104 Clear();
106 mOwning = aOther.mOwning;
107 mSegments = std::move(aOther.mSegments);
108 mSize = aOther.mSize;
109 aOther.mSegments.clear();
110 aOther.mSize = 0;
111 return *this;
114 ~BufferList() { Clear(); }
116 // Initializes the BufferList with a segment of the given size and capacity.
117 // May only be called once, before any segments have been allocated.
118 bool Init(size_t aInitialSize, size_t aInitialCapacity) {
119 MOZ_ASSERT(mSegments.empty());
120 MOZ_ASSERT(aInitialCapacity != 0);
121 MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0);
123 return AllocateSegment(aInitialSize, aInitialCapacity);
126 bool CopyFrom(const BufferList& aOther) {
127 MOZ_ASSERT(mOwning);
129 Clear();
131 // We don't make an exact copy of aOther. Instead, create a single segment
132 // with enough space to hold all data in aOther.
133 if (!Init(aOther.mSize, (aOther.mSize + kSegmentAlignment - 1) &
134 ~(kSegmentAlignment - 1))) {
135 return false;
138 size_t offset = 0;
139 for (const Segment& segment : aOther.mSegments) {
140 memcpy(Start() + offset, segment.mData, segment.mSize);
141 offset += segment.mSize;
143 MOZ_ASSERT(offset == mSize);
145 return true;
148 // Returns the sum of the sizes of all the buffers.
149 size_t Size() const { return mSize; }
151 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
152 size_t size = mSegments.sizeOfExcludingThis(aMallocSizeOf);
153 for (Segment& segment : mSegments) {
154 size += aMallocSizeOf(segment.Start());
156 return size;
159 void Clear() {
160 if (mOwning) {
161 for (Segment& segment : mSegments) {
162 this->free_(segment.mData, segment.mCapacity);
165 mSegments.clear();
167 mSize = 0;
170 // Iterates over bytes in the segments. You can advance it by as many bytes as
171 // you choose.
172 class IterImpl {
173 // Invariants:
174 // (0) mSegment <= bufferList.mSegments.length()
175 // (1) mData <= mDataEnd
176 // (2) If mSegment is not the last segment, mData < mDataEnd
177 uintptr_t mSegment;
178 char* mData;
179 char* mDataEnd;
181 friend class BufferList;
183 public:
184 explicit IterImpl(const BufferList& aBuffers)
185 : mSegment(0), mData(nullptr), mDataEnd(nullptr) {
186 if (!aBuffers.mSegments.empty()) {
187 mData = aBuffers.mSegments[0].Start();
188 mDataEnd = aBuffers.mSegments[0].End();
192 // Returns a pointer to the raw data. It is valid to access up to
193 // RemainingInSegment bytes of this buffer.
194 char* Data() const {
195 MOZ_RELEASE_ASSERT(!Done());
196 return mData;
199 // Returns true if the memory in the range [Data(), Data() + aBytes) is all
200 // part of one contiguous buffer.
201 bool HasRoomFor(size_t aBytes) const {
202 MOZ_RELEASE_ASSERT(mData <= mDataEnd);
203 return size_t(mDataEnd - mData) >= aBytes;
206 // Returns the maximum value aBytes for which HasRoomFor(aBytes) will be
207 // true.
208 size_t RemainingInSegment() const {
209 MOZ_RELEASE_ASSERT(mData <= mDataEnd);
210 return mDataEnd - mData;
213 bool HasBytesAvailable(const BufferList& aBuffers, uint32_t aBytes) const {
214 if (RemainingInSegment() >= aBytes) {
215 return true;
217 aBytes -= RemainingInSegment();
218 for (size_t i = mSegment + 1; i < aBuffers.mSegments.length(); i++) {
219 if (aBuffers.mSegments[i].mSize >= aBytes) {
220 return true;
222 aBytes -= aBuffers.mSegments[i].mSize;
225 return false;
228 // Advances the iterator by aBytes bytes. aBytes must be less than
229 // RemainingInSegment(). If advancing by aBytes takes the iterator to the
230 // end of a buffer, it will be moved to the beginning of the next buffer
231 // unless it is the last buffer.
232 void Advance(const BufferList& aBuffers, size_t aBytes) {
233 const Segment& segment = aBuffers.mSegments[mSegment];
234 MOZ_RELEASE_ASSERT(segment.Start() <= mData);
235 MOZ_RELEASE_ASSERT(mData <= mDataEnd);
236 MOZ_RELEASE_ASSERT(mDataEnd == segment.End());
238 MOZ_RELEASE_ASSERT(HasRoomFor(aBytes));
239 mData += aBytes;
241 if (mData == mDataEnd && mSegment + 1 < aBuffers.mSegments.length()) {
242 mSegment++;
243 const Segment& nextSegment = aBuffers.mSegments[mSegment];
244 mData = nextSegment.Start();
245 mDataEnd = nextSegment.End();
246 MOZ_RELEASE_ASSERT(mData < mDataEnd);
250 // Advance the iterator by aBytes, possibly crossing segments. This function
251 // returns false if it runs out of buffers to advance through. Otherwise it
252 // returns true.
253 bool AdvanceAcrossSegments(const BufferList& aBuffers, size_t aBytes) {
254 size_t bytes = aBytes;
255 while (bytes) {
256 size_t toAdvance = std::min(bytes, RemainingInSegment());
257 if (!toAdvance) {
258 return false;
260 Advance(aBuffers, toAdvance);
261 bytes -= toAdvance;
263 return true;
266 // Returns true when the iterator reaches the end of the BufferList.
267 bool Done() const { return mData == mDataEnd; }
269 private:
270 // Count the bytes we would need to advance in order to reach aTarget.
271 size_t BytesUntil(const BufferList& aBuffers,
272 const IterImpl& aTarget) const {
273 size_t offset = 0;
275 MOZ_ASSERT(aTarget.IsIn(aBuffers));
276 MOZ_ASSERT(mSegment <= aTarget.mSegment);
278 char* data = mData;
279 for (uintptr_t segment = mSegment; segment < aTarget.mSegment;) {
280 offset += aBuffers.mSegments[segment].End() - data;
281 data = aBuffers.mSegments[++segment].mData;
284 MOZ_RELEASE_ASSERT(IsIn(aBuffers));
285 MOZ_RELEASE_ASSERT(aTarget.mData >= data);
287 offset += aTarget.mData - data;
288 return offset;
291 bool IsIn(const BufferList& aBuffers) const {
292 return mSegment < aBuffers.mSegments.length() &&
293 mData >= aBuffers.mSegments[mSegment].mData &&
294 mData < aBuffers.mSegments[mSegment].End();
298 // Special convenience method that returns Iter().Data().
299 char* Start() {
300 MOZ_RELEASE_ASSERT(!mSegments.empty());
301 return mSegments[0].mData;
303 const char* Start() const { return mSegments[0].mData; }
305 IterImpl Iter() const { return IterImpl(*this); }
307 // Copies aSize bytes from aData into the BufferList. The storage for these
308 // bytes may be split across multiple buffers. Size() is increased by aSize.
309 [[nodiscard]] inline bool WriteBytes(const char* aData, size_t aSize);
311 // Allocates a buffer of at most |aMaxBytes| bytes and, if successful, returns
312 // that buffer, and places its size in |aSize|. If unsuccessful, returns null
313 // and leaves |aSize| undefined.
314 inline char* AllocateBytes(size_t aMaxSize, size_t* aSize);
316 // Copies possibly non-contiguous byte range starting at aIter into
317 // aData. aIter is advanced by aSize bytes. Returns false if it runs out of
318 // data before aSize.
319 inline bool ReadBytes(IterImpl& aIter, char* aData, size_t aSize) const;
321 // Return a new BufferList that shares storage with this BufferList. The new
322 // BufferList is read-only. It allows iteration over aSize bytes starting at
323 // aIter. Borrow can fail, in which case *aSuccess will be false upon
324 // return. The borrowed BufferList can use a different AllocPolicy than the
325 // original one. However, it is not responsible for freeing buffers, so the
326 // AllocPolicy is only used for the buffer vector.
327 template <typename BorrowingAllocPolicy>
328 BufferList<BorrowingAllocPolicy> Borrow(
329 IterImpl& aIter, size_t aSize, bool* aSuccess,
330 BorrowingAllocPolicy aAP = BorrowingAllocPolicy()) const;
332 // Return a new BufferList and move storage from this BufferList to it. The
333 // new BufferList owns the buffers. Move can fail, in which case *aSuccess
334 // will be false upon return. The new BufferList can use a different
335 // AllocPolicy than the original one. The new OtherAllocPolicy is responsible
336 // for freeing buffers, so the OtherAllocPolicy must use freeing method
337 // compatible to the original one.
338 template <typename OtherAllocPolicy>
339 BufferList<OtherAllocPolicy> MoveFallible(
340 bool* aSuccess, OtherAllocPolicy aAP = OtherAllocPolicy());
342 // Return a new BufferList that adopts the byte range starting at Iter so that
343 // range [aIter, aIter + aSize) is transplanted to the returned BufferList.
344 // Contents of the buffer before aIter + aSize is left undefined.
345 // Extract can fail, in which case *aSuccess will be false upon return. The
346 // moved buffers are erased from the original BufferList. In case of extract
347 // fails, the original BufferList is intact. All other iterators except aIter
348 // are invalidated.
349 // This method requires aIter and aSize to be 8-byte aligned.
350 BufferList Extract(IterImpl& aIter, size_t aSize, bool* aSuccess);
352 // Return the number of bytes from 'start' to 'end', two iterators within
353 // this BufferList.
354 size_t RangeLength(const IterImpl& start, const IterImpl& end) const {
355 MOZ_ASSERT(start.IsIn(*this) && end.IsIn(*this));
356 return start.BytesUntil(*this, end);
359 // This takes ownership of the data
360 void* WriteBytesZeroCopy(char* aData, size_t aSize, size_t aCapacity) {
361 MOZ_ASSERT(aCapacity != 0);
362 MOZ_ASSERT(aSize <= aCapacity);
363 MOZ_ASSERT(mOwning);
365 if (!mSegments.append(Segment(aData, aSize, aCapacity))) {
366 this->free_(aData, aCapacity);
367 return nullptr;
369 mSize += aSize;
370 return aData;
373 private:
374 explicit BufferList(AllocPolicy aAP)
375 : AllocPolicy(aAP), mOwning(false), mSize(0), mStandardCapacity(0) {}
377 char* AllocateSegment(size_t aSize, size_t aCapacity) {
378 MOZ_RELEASE_ASSERT(mOwning);
379 MOZ_ASSERT(aCapacity != 0);
380 MOZ_ASSERT(aSize <= aCapacity);
382 char* data = this->template pod_malloc<char>(aCapacity);
383 if (!data) {
384 return nullptr;
386 if (!mSegments.append(Segment(data, aSize, aCapacity))) {
387 this->free_(data, aCapacity);
388 return nullptr;
390 mSize += aSize;
391 return data;
394 bool mOwning;
395 Vector<Segment, 1, AllocPolicy> mSegments;
396 size_t mSize;
397 size_t mStandardCapacity;
400 template <typename AllocPolicy>
401 [[nodiscard]] bool BufferList<AllocPolicy>::WriteBytes(const char* aData,
402 size_t aSize) {
403 MOZ_RELEASE_ASSERT(mOwning);
404 MOZ_RELEASE_ASSERT(mStandardCapacity);
406 size_t copied = 0;
407 while (copied < aSize) {
408 size_t toCopy;
409 char* data = AllocateBytes(aSize - copied, &toCopy);
410 if (!data) {
411 return false;
413 memcpy(data, aData + copied, toCopy);
414 copied += toCopy;
417 return true;
420 template <typename AllocPolicy>
421 char* BufferList<AllocPolicy>::AllocateBytes(size_t aMaxSize, size_t* aSize) {
422 MOZ_RELEASE_ASSERT(mOwning);
423 MOZ_RELEASE_ASSERT(mStandardCapacity);
425 if (!mSegments.empty()) {
426 Segment& lastSegment = mSegments.back();
428 size_t capacity = lastSegment.mCapacity - lastSegment.mSize;
429 if (capacity) {
430 size_t size = std::min(aMaxSize, capacity);
431 char* data = lastSegment.mData + lastSegment.mSize;
433 lastSegment.mSize += size;
434 mSize += size;
436 *aSize = size;
437 return data;
441 size_t size = std::min(aMaxSize, mStandardCapacity);
442 char* data = AllocateSegment(size, mStandardCapacity);
443 if (data) {
444 *aSize = size;
446 return data;
449 template <typename AllocPolicy>
450 bool BufferList<AllocPolicy>::ReadBytes(IterImpl& aIter, char* aData,
451 size_t aSize) const {
452 size_t copied = 0;
453 size_t remaining = aSize;
454 while (remaining) {
455 size_t toCopy = std::min(aIter.RemainingInSegment(), remaining);
456 if (!toCopy) {
457 // We've run out of data in the last segment.
458 return false;
460 memcpy(aData + copied, aIter.Data(), toCopy);
461 copied += toCopy;
462 remaining -= toCopy;
464 aIter.Advance(*this, toCopy);
467 return true;
470 template <typename AllocPolicy>
471 template <typename BorrowingAllocPolicy>
472 BufferList<BorrowingAllocPolicy> BufferList<AllocPolicy>::Borrow(
473 IterImpl& aIter, size_t aSize, bool* aSuccess,
474 BorrowingAllocPolicy aAP) const {
475 BufferList<BorrowingAllocPolicy> result(aAP);
477 size_t size = aSize;
478 while (size) {
479 size_t toAdvance = std::min(size, aIter.RemainingInSegment());
481 if (!toAdvance || !result.mSegments.append(
482 typename BufferList<BorrowingAllocPolicy>::Segment(
483 aIter.mData, toAdvance, toAdvance))) {
484 *aSuccess = false;
485 return result;
487 aIter.Advance(*this, toAdvance);
488 size -= toAdvance;
491 result.mSize = aSize;
492 *aSuccess = true;
493 return result;
496 template <typename AllocPolicy>
497 template <typename OtherAllocPolicy>
498 BufferList<OtherAllocPolicy> BufferList<AllocPolicy>::MoveFallible(
499 bool* aSuccess, OtherAllocPolicy aAP) {
500 BufferList<OtherAllocPolicy> result(0, 0, mStandardCapacity, aAP);
502 IterImpl iter = Iter();
503 while (!iter.Done()) {
504 size_t toAdvance = iter.RemainingInSegment();
506 if (!toAdvance ||
507 !result.mSegments.append(typename BufferList<OtherAllocPolicy>::Segment(
508 iter.mData, toAdvance, toAdvance))) {
509 *aSuccess = false;
510 result.mSegments.clear();
511 return result;
513 iter.Advance(*this, toAdvance);
516 result.mSize = mSize;
517 mSegments.clear();
518 mSize = 0;
519 *aSuccess = true;
520 return result;
523 template <typename AllocPolicy>
524 BufferList<AllocPolicy> BufferList<AllocPolicy>::Extract(IterImpl& aIter,
525 size_t aSize,
526 bool* aSuccess) {
527 MOZ_RELEASE_ASSERT(aSize);
528 MOZ_RELEASE_ASSERT(mOwning);
529 MOZ_ASSERT(aSize % kSegmentAlignment == 0);
530 MOZ_ASSERT(intptr_t(aIter.mData) % kSegmentAlignment == 0);
532 auto failure = [this, aSuccess]() {
533 *aSuccess = false;
534 return BufferList(0, 0, mStandardCapacity);
537 // Number of segments we'll need to copy data from to satisfy the request.
538 size_t segmentsNeeded = 0;
539 // If this is None then the last segment is a full segment, otherwise we need
540 // to copy this many bytes.
541 Maybe<size_t> lastSegmentSize;
543 // Copy of the iterator to walk the BufferList and see how many segments we
544 // need to copy.
545 IterImpl iter = aIter;
546 size_t remaining = aSize;
547 while (!iter.Done() && remaining &&
548 remaining >= iter.RemainingInSegment()) {
549 remaining -= iter.RemainingInSegment();
550 iter.Advance(*this, iter.RemainingInSegment());
551 segmentsNeeded++;
554 if (remaining) {
555 if (iter.Done()) {
556 // We reached the end of the BufferList and there wasn't enough data to
557 // satisfy the request.
558 return failure();
560 lastSegmentSize.emplace(remaining);
561 // The last block also counts as a segment. This makes the conditionals
562 // on segmentsNeeded work in the rest of the function.
563 segmentsNeeded++;
567 BufferList result(0, 0, mStandardCapacity);
568 if (!result.mSegments.reserve(segmentsNeeded + lastSegmentSize.isSome())) {
569 return failure();
572 // Copy the first segment, it's special because we can't just steal the
573 // entire Segment struct from this->mSegments.
574 size_t firstSegmentSize = std::min(aSize, aIter.RemainingInSegment());
575 if (!result.WriteBytes(aIter.Data(), firstSegmentSize)) {
576 return failure();
578 aIter.Advance(*this, firstSegmentSize);
579 segmentsNeeded--;
581 // The entirety of the request wasn't in the first segment, now copy the
582 // rest.
583 if (segmentsNeeded) {
584 char* finalSegment = nullptr;
585 // Pre-allocate the final segment so that if this fails, we return before
586 // we delete the elements from |this->mSegments|.
587 if (lastSegmentSize.isSome()) {
588 MOZ_RELEASE_ASSERT(mStandardCapacity >= *lastSegmentSize);
589 finalSegment = this->template pod_malloc<char>(mStandardCapacity);
590 if (!finalSegment) {
591 return failure();
595 size_t copyStart = aIter.mSegment;
596 // Copy segments from this over to the result and remove them from our
597 // storage. Not needed if the only segment we need to copy is the last
598 // partial one.
599 size_t segmentsToCopy = segmentsNeeded - lastSegmentSize.isSome();
600 for (size_t i = 0; i < segmentsToCopy; ++i) {
601 result.mSegments.infallibleAppend(Segment(
602 mSegments[aIter.mSegment].mData, mSegments[aIter.mSegment].mSize,
603 mSegments[aIter.mSegment].mCapacity));
604 aIter.Advance(*this, aIter.RemainingInSegment());
606 // Due to the way IterImpl works, there are two cases here: (1) if we've
607 // consumed the entirety of the BufferList, then the iterator is pointed at
608 // the end of the final segment, (2) otherwise it is pointed at the start
609 // of the next segment. We want to verify that we really consumed all
610 // |segmentsToCopy| segments.
611 MOZ_RELEASE_ASSERT(
612 (aIter.mSegment == copyStart + segmentsToCopy) ||
613 (aIter.Done() && aIter.mSegment == copyStart + segmentsToCopy - 1));
614 mSegments.erase(mSegments.begin() + copyStart,
615 mSegments.begin() + copyStart + segmentsToCopy);
617 // Reset the iter's position for what we just deleted.
618 aIter.mSegment -= segmentsToCopy;
620 if (lastSegmentSize.isSome()) {
621 // We called reserve() on result.mSegments so infallibleAppend is safe.
622 result.mSegments.infallibleAppend(
623 Segment(finalSegment, 0, mStandardCapacity));
624 bool r = result.WriteBytes(aIter.Data(), *lastSegmentSize);
625 MOZ_RELEASE_ASSERT(r);
626 aIter.Advance(*this, *lastSegmentSize);
630 mSize -= aSize;
631 result.mSize = aSize;
633 *aSuccess = true;
634 return result;
637 } // namespace mozilla
639 #endif /* mozilla_BufferList_h */